factorforge-cds 3.2.7__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {factorforge_cds-3.2.7/src/factorforge_cds.egg-info → factorforge_cds-3.3.0}/PKG-INFO +4 -4
  2. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/README.md +2 -2
  3. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/pyproject.toml +2 -2
  4. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/analysis/feasibility.py +6 -12
  6. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/analysis/metrics.py +1 -1
  7. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/__init__.py +1 -1
  8. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/__init__.py +1 -1
  9. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/optimizer.py +18 -2
  10. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/reverse_translator.py +9 -6
  11. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/rule_engine.py +8 -6
  12. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/scoring.py +15 -20
  13. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/utils.py +4 -5
  14. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0/src/factorforge_cds.egg-info}/PKG-INFO +4 -4
  15. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_benchmark_codon_table_metadata.py +3 -6
  16. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_registry_production_sync.py +19 -2
  17. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_worked_example.py +9 -5
  18. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/LICENSE +0 -0
  19. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/setup.cfg +0 -0
  20. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/__main__.py +0 -0
  21. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/analysis/__init__.py +0 -0
  22. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/cli/__init__.py +0 -0
  23. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/cli/legacy_cli.py +0 -0
  24. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/cli/main.py +0 -0
  25. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/__init__.py +0 -0
  26. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/exporter.py +0 -0
  27. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/optimizer.py +0 -0
  28. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/validator.py +0 -0
  29. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/nbenthamiana_codons.json +0 -0
  30. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/nbenthamiana_golden_set.json +0 -0
  31. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/ntabacum_codons.json +0 -0
  32. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/templates/high_expression.json +0 -0
  33. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/templates/standard_expression.json +0 -0
  34. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/data/wolffia_globosa_codons.json +0 -0
  35. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/database.py +0 -0
  36. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
  37. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/construct_builder.py +0 -0
  38. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/exporter.py +0 -0
  39. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/pipeline.py +0 -0
  40. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/__init__.py +0 -0
  41. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/domesticator.py +0 -0
  42. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/scoring_ml.py +0 -0
  43. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/validator.py +0 -0
  44. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/engines/registry.py +0 -0
  45. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/io/__init__.py +0 -0
  46. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/io/fasta.py +0 -0
  47. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/io/validation.py +0 -0
  48. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/__init__.py +0 -0
  49. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/annotate.py +0 -0
  50. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/kd_scale.py +0 -0
  51. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/risk_classifier.py +0 -0
  52. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/sp_predict.py +0 -0
  53. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/tm_predict.py +0 -0
  54. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/registry/__init__.py +0 -0
  55. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/registry/registry_loader.py +0 -0
  56. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/schemas/__init__.py +0 -0
  57. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/schemas/design_package.py +0 -0
  58. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/schemas/design_package.schema.json +0 -0
  59. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/__init__.py +0 -0
  60. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/construct_id.py +0 -0
  61. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/exceptions.py +0 -0
  62. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/restriction_sites.py +0 -0
  63. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/sequence_validator.py +0 -0
  64. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/utils/validation.py +0 -0
  65. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/validation/__init__.py +0 -0
  66. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/validation/cli.py +0 -0
  67. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/validation/package_generator.py +0 -0
  68. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/validation_registry.py +0 -0
  69. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge/validation_report.py +0 -0
  70. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/SOURCES.txt +0 -0
  71. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  72. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
  73. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/requires.txt +0 -0
  74. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  75. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_baselines.py +0 -0
  76. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_benchmark_regression.py +0 -0
  77. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_benchmark_scoring.py +0 -0
  78. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_benchmark_smoke.py +0 -0
  79. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_cai.py +0 -0
  80. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_codon_table_manifest.py +0 -0
  81. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_database.py +0 -0
  82. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_design_package_schema.py +0 -0
  83. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_design_package_semantics.py +0 -0
  84. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_design_package_serialization.py +0 -0
  85. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_docs_consistency.py +0 -0
  86. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_fasta_io.py +0 -0
  87. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_gc_content.py +0 -0
  88. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_host_profile_metadata.py +0 -0
  89. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_iupac_validation.py +0 -0
  90. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_legacy_cli.py +0 -0
  91. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_no_raw_sequence_logging.py +0 -0
  92. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_openbio_missing_metric_contract.py +0 -0
  93. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_parameter_registry.py +0 -0
  94. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_protein_risk.py +0 -0
  95. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_restriction_sites.py +0 -0
  96. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_sequence_validator.py +0 -0
  97. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_translation_integrity.py +0 -0
  98. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_validation_contract_compat.py +0 -0
  99. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_validation_registry.py +0 -0
  100. {factorforge_cds-3.2.7 → factorforge_cds-3.3.0}/tests/test_validation_report.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.2.7
4
- Summary: FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex.
3
+ Version: 3.3.0
4
+ Summary: FactorForge - open-source CDS design and pre-synthesis sequence review by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
7
7
  Project-URL: Homepage, https://factorforge.eijex.com
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
 
34
34
  # FactorForge
35
35
 
36
- **Open-source constraint-based CDS design and pre-synthesis sequence review engine for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
36
+ **Open-source constraint-based CDS design and pre-synthesis sequence review for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
37
37
 
38
38
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
39
39
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
@@ -93,7 +93,7 @@ FactorForge outputs are **in-silico only** and have not been experimentally vali
93
93
  ## Citing
94
94
 
95
95
  ```
96
- FactorForge v3.2.7 (2026). Open-source constraint-based CDS design engine.
96
+ FactorForge v3.3.0 (2026). Open-source constraint-based CDS design and sequence review.
97
97
  Eijex. https://github.com/eijex/factorforge-cds
98
98
  ```
99
99
 
@@ -1,6 +1,6 @@
1
1
  # FactorForge
2
2
 
3
- **Open-source constraint-based CDS design and pre-synthesis sequence review engine for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
3
+ **Open-source constraint-based CDS design and pre-synthesis sequence review for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
4
4
 
5
5
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
6
6
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
@@ -60,7 +60,7 @@ FactorForge outputs are **in-silico only** and have not been experimentally vali
60
60
  ## Citing
61
61
 
62
62
  ```
63
- FactorForge v3.2.7 (2026). Open-source constraint-based CDS design engine.
63
+ FactorForge v3.3.0 (2026). Open-source constraint-based CDS design and sequence review.
64
64
  Eijex. https://github.com/eijex/factorforge-cds
65
65
  ```
66
66
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "factorforge-cds"
7
- version = "3.2.7"
8
- description = "FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex."
7
+ version = "3.3.0"
8
+ description = "FactorForge - open-source CDS design and pre-synthesis sequence review by Eijex."
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-only"
11
11
  license-files = ["LICENSE"]
@@ -4,7 +4,7 @@ FactorForge - Codon Optimization Platform
4
4
  profile: constraint-aware rule/profile engine
5
5
  """
6
6
 
7
- __version__ = "3.2.7"
7
+ __version__ = "3.3.0"
8
8
  __author__ = "Eijex"
9
9
 
10
10
  # Auto-register engines (safe when running from source tree)
@@ -14,13 +14,11 @@ from factorforge.analysis.metrics import (
14
14
  )
15
15
 
16
16
 
17
- # DEFAULT_CAI_TARGET=0.82 aligns with industry practice (>0.8) and is achievable
18
- # (internal benchmark, n=49, avg CAI=0.76).
19
- # DEFAULT_GC_LOW/HIGH = native genome-composition anchor for N. benthamiana CDS
20
- # (_analysis/025 STEP 2: 004 endogenous n=10 measured range 40-47%, cross-checked
21
- # against nbev11_cds_hc/all and qld183_v103 derived-asset GC ~42.8-43.1% and
22
- # external ground truth ~44%). NOT an empirically validated expression optimum —
23
- # this is a composition anchor, not a target to maximize toward.
17
+ # Defaults for the current N. benthamiana software-default codon reference.
18
+ # DEFAULT_CAI_TARGET=0.82 remains a soft in-silico target aligned with
19
+ # industry practice (>0.8). DEFAULT_GC_LOW/HIGH track the NbeV1.1
20
+ # high-confidence CDS-derived native composition band. These are not wet-lab
21
+ # validation, expression/yield prediction, or biological-superiority claims.
24
22
  # Exported as named constants so tests/test_registry_production_sync.py can
25
23
  # strictly compare them against the registry (single source of truth).
26
24
  DEFAULT_CAI_TARGET: float = 0.82
@@ -120,11 +118,7 @@ def analyze_feasibility(
120
118
  if not protein:
121
119
  raise ValueError("protein_sequence must not be empty")
122
120
 
123
- # Default exploration ranges: genome-grounded native anchor first, then
124
- # progressively wider windows. (55.0, 65.0) is retained ONLY as an explicit
125
- # non-native/high-GC option (matches legacy engine-output-calibrated band) —
126
- # callers must opt in explicitly to it, it is not the production default.
127
- ranges = gc_ranges or [(40.0, 47.0), (35.0, 50.0), (55.0, 65.0)]
121
+ ranges = gc_ranges or [(40.0, 47.0), (40.0, 55.0), (40.0, 65.0)]
128
122
  normalized_ranges = [
129
123
  (_normalize_gc_bound(low), _normalize_gc_bound(high)) for low, high in ranges
130
124
  ]
@@ -98,7 +98,7 @@ class CodonUsageTable:
98
98
 
99
99
 
100
100
  def _default_codon_table_path() -> Path:
101
- # Job 168 / v3.3.0 (_analysis/025): production default switched from the
101
+ # v3.3.0 reference-policy update: production default switched from the
102
102
  # legacy Kazusa/SGN-derived table to the NbeV1.1 LAB-strain high-confidence
103
103
  # derived table. See data/reference/active_codon_reference.json.
104
104
  return get_data_path() / "profiles" / "nbev11_cds_hc_derived_codons.json"
@@ -13,7 +13,7 @@ def register_builtin_engines() -> None:
13
13
  "profile",
14
14
  RuleBasedOptimizer,
15
15
  metadata={
16
- "version": "3.2.7",
16
+ "version": "3.3.0",
17
17
  "engine_type": "profile_rule_based",
18
18
  "role": "stable_profile_engine",
19
19
  "stable": True,
@@ -5,7 +5,7 @@ Production system (2026)
5
5
  Plant-specific rule-based optimization
6
6
  """
7
7
 
8
- __version__ = "3.2.7"
8
+ __version__ = "3.3.0"
9
9
 
10
10
  from .optimizer import RuleBasedOptimizer
11
11
  from .pipeline import OptimizationPipeline
@@ -10,7 +10,7 @@ from factorforge.core.interfaces import OptimizationResult, OptimizerEngine
10
10
  from .exporter import SequenceExporter
11
11
  from .rules.reverse_translator import OptimizationProfile, ReverseTranslator
12
12
  from .rules.rule_engine import RuleEngine
13
- from .scoring import calculate_composite_score, compute_mfe_evidence
13
+ from .scoring import calculate_composite_score, compute_mfe_evidence, resolve_host_gc_range
14
14
  from .validator import InputValidator
15
15
 
16
16
  logger = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class RuleBasedOptimizer(OptimizerEngine):
20
20
  """Profile-based rule optimization engine."""
21
21
 
22
22
  name = "Profile-based"
23
- version = "3.2.7"
23
+ version = "3.3.0"
24
24
 
25
25
  def __init__(self, codon_table_path: str | None = None) -> None:
26
26
  """
@@ -157,6 +157,22 @@ class RuleBasedOptimizer(OptimizerEngine):
157
157
  "score": candidates[0]["score"],
158
158
  "violations": sum(len(v) for v in scan_results.values()),
159
159
  }
160
+ if profile_value == "balanced":
161
+ host_gc_min, host_gc_max = resolve_host_gc_range(host)
162
+ requested_gc_min_percent = float(kwargs.get("target_gc_min", host_gc_min))
163
+ requested_gc_max_percent = float(kwargs.get("target_gc_max", host_gc_max))
164
+ achieved_gc_percent = metrics["gc_percent"]
165
+ metrics.update(
166
+ {
167
+ "gc_target_reached": (
168
+ requested_gc_min_percent
169
+ <= achieved_gc_percent
170
+ <= requested_gc_max_percent
171
+ ),
172
+ "requested_gc_min_percent": requested_gc_min_percent,
173
+ "requested_gc_max_percent": requested_gc_max_percent,
174
+ }
175
+ )
160
176
  # MFE provenance: expose whether MFE was actually computed so downstream
161
177
  # artifacts (API response, Design Package) never report an uncomputed
162
178
  # MFE as a misleading 0.0 (016 audit). Score value is unchanged.
@@ -374,11 +374,14 @@ class ReverseTranslator:
374
374
  Balanced profile: CAI first, GC balanced
375
375
 
376
376
  - Preferred codon ratio: 70%
377
- - Target GC: host composition band (nbenthamiana: 40-47%, native
378
- genome-composition anchor; see _analysis/025 STEP 2 and
379
- engines/profile/scoring.py GC_OPT_MIN/MAX. Not an empirically
380
- validated expression optimum. Other hosts: resolve_host_gc_range()
381
- default, unchanged from pre-v3.3.0 behavior.)
377
+ - Target GC: host composition band, resolved via
378
+ resolve_host_gc_range() (engines/profile/scoring.py
379
+ GC_OPT_MIN/MAX). N. benthamiana currently uses the NbeV1.1
380
+ high-confidence CDS-derived native-composition band (40-47%);
381
+ other hosts retain their configured defaults.
382
+ - GC target requests are best-effort sampling targets, not guarantees:
383
+ achieved GC can be dominated by the codon table's frequency-weighted
384
+ sampling distribution.
382
385
  """
383
386
  _host_gc_min, _host_gc_max = resolve_host_gc_range(self.host)
384
387
  target_gc_min = kwargs.get("target_gc_min", _host_gc_min)
@@ -453,7 +456,7 @@ class ReverseTranslator:
453
456
 
454
457
  Targets the caller-supplied ``target_gc`` if provided, otherwise the
455
458
  active host's composition midpoint (resolve_host_gc_range(self.host);
456
- 43.5% for nbenthamiana since v3.3.0 / _analysis/025, 60.0% for other
459
+ 43.5% for nbenthamiana since v3.3.0 / reference-policy audit, 60.0% for other
457
460
  hosts pending their own host-specific analysis). To target a different
458
461
  GC (e.g. for specific vector requirements), pass target_gc explicitly.
459
462
 
@@ -358,12 +358,14 @@ class RuleEngine:
358
358
 
359
359
  This is a LOCAL synthesis/extreme-window guard (default 25-75% over a
360
360
  50 bp window), NOT the global GC target. Global GC is governed separately
361
- by the scoring band (GC_OPT_MIN/MAX, native genome-composition anchor
362
- ~40-47%, see _analysis/025 STEP 2) and the API/DP gc_min/gc_max
363
- constraints. The wide 25-75% local band is an independent synthesis/
364
- hairpin-risk guard (registry `gc_extreme_local_window`), not derived from
365
- or coupled to the global GC target — narrowing it is a separate decision
366
- from the global band retarget and is out of scope here.
361
+ by the scoring band (GC_OPT_MIN/MAX; N. benthamiana currently uses
362
+ the NbeV1.1 high-confidence CDS-derived native-composition anchor,
363
+ 40-47%, while other hosts retain their own configured defaults) and
364
+ the API/DP gc_min/gc_max constraints. The wide 25-75% local band is
365
+ an independent synthesis/hairpin-risk guard
366
+ (registry `gc_extreme_local_window`), not derived from or coupled to
367
+ the global GC target — narrowing it is a separate decision from the
368
+ global band retarget and is out of scope here.
367
369
 
368
370
  Args:
369
371
  seq: DNA sequence
@@ -11,23 +11,20 @@ from typing import Any
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
- # GC band for N. benthamiana codon-optimized sequences.
15
- # Native genome-composition anchor (_analysis/025 STEP 2: 004 endogenous CDS
16
- # n=10 measured range 40-47%; cross-checked against nbev11_cds_hc/all and
17
- # qld183_v103 derived-asset GC ~42.8-43.1% and external ground truth ~44%).
18
- # NOT an empirically validated expression optimum — this is a composition
19
- # anchor, not a target to maximize toward. Sequences within [GC_OPT_MIN,
20
- # GC_OPT_MAX] receive full GC score; outside the band the score decays linearly.
14
+ # GC band for the current N. benthamiana software-default codon reference.
15
+ # NbeV1.1 high-confidence CDS-derived codon usage anchors the public default
16
+ # to the native genome-derived composition band. This is a configured
17
+ # in-silico reference/GC policy, not wet-lab validation or expression/yield
18
+ # prediction. Sequences inside [GC_OPT_MIN, GC_OPT_MAX] receive full GC score;
19
+ # outside the band the score decays linearly.
21
20
  GC_OPT_MIN = 40.0
22
21
  GC_OPT_MAX = 47.0
23
22
  GC_OPT_MID = 43.5 # kept for gc_target point-scoring and viral_delivery centering
24
23
  GC_DECAY_WIDTH = 20.0 # percentage points outside band before score reaches 0.0
25
24
 
26
- # Job 168 / v3.3.0 (_analysis/025) scoped the genome-composition re-derivation
27
- # to N. benthamiana only. Other hosts (e.g. ntabacum/BY-2) keep the pre-v3.3.0
28
- # global default (55-65%, internal benchmark n=49 avg GC=60.1%) until they get
29
- # their own host-specific genome-composition analysis — they must NOT silently
30
- # inherit GC_OPT_MIN/MAX, which is an N.-benthamiana-specific anchor.
25
+ # Host-isolation fix: ntabacum/BY-2 must not silently inherit the NbeV1.1
26
+ # N. benthamiana band. Other hosts keep the pre-v3.3.0 global default until
27
+ # they get their own host-specific analysis.
31
28
  GC_RANGE_DEFAULT: tuple[float, float] = (55.0, 65.0)
32
29
  GC_RANGES_BY_HOST: dict[str, tuple[float, float]] = {
33
30
  "nbenthamiana": (GC_OPT_MIN, GC_OPT_MAX),
@@ -37,7 +34,7 @@ GC_RANGES_BY_HOST: dict[str, tuple[float, float]] = {
37
34
  def resolve_host_gc_range(host: str | None) -> tuple[float, float]:
38
35
  """Resolve the (gc_min, gc_max) composition band for a host.
39
36
 
40
- nbenthamiana uses the _analysis/025 native genome-composition anchor.
37
+ nbenthamiana uses the reference-policy audit native genome-composition anchor.
41
38
  Any other host (including unknown ones) keeps the pre-v3.3.0 global
42
39
  default band until it gets its own host-specific analysis.
43
40
  """
@@ -142,7 +139,7 @@ def _check_vienna_available() -> bool:
142
139
  return _vienna_available
143
140
 
144
141
 
145
- # 170-fix: ViennaRNA's RNA.fold() uses Zuker's MFE algorithm, O(n^3) time /
142
+ # ViennaRNA's RNA.fold() uses Zuker's MFE algorithm, O(n^3) time /
146
143
  # O(n^2) memory — there was previously no length guard anywhere in the
147
144
  # calculate_mfe() call chain, so a single request at/under the existing
148
145
  # public API length limits (5000aa/15000bp) could pin a CPU core for minutes
@@ -175,7 +172,7 @@ def calculate_mfe(sequence: str) -> float | None:
175
172
  "Sequence length (%d nt) exceeds MFE_MAX_SEQUENCE_LENGTH (%d nt); "
176
173
  "skipping global MFE calculation to avoid an unbounded ViennaRNA "
177
174
  "RNA.fold() runtime (O(n^3)). MFE scoring falls back to a neutral, "
178
- "zero-weighted contribution for this candidate (170-fix).",
175
+ "zero-weighted contribution for this candidate.",
179
176
  len(sequence), MFE_MAX_SEQUENCE_LENGTH,
180
177
  )
181
178
  return None
@@ -196,8 +193,7 @@ def normalize_mfe(mfe: float, seq_length: int) -> float:
196
193
  """
197
194
  Normalize MFE to 0-1 range where 1 = no structure (favorable).
198
195
 
199
- Clamp range calibrated empirically in analysis 011
200
- (eijex-workspace/_analysis/2026-06-26/011-mfe-clamp-calibration):
196
+ Clamp range calibrated empirically from internal computational audit data:
201
197
  measured MFE/nt across 135 FactorForge outputs (N. benthamiana +
202
198
  BY-2 hosts, 5 profiles) ranged -0.4064 to -0.1338 (combined 5th/95th
203
199
  percentile -0.3839/-0.1760). The range below widens that empirical
@@ -211,8 +207,7 @@ def normalize_mfe(mfe: float, seq_length: int) -> float:
211
207
  Returns:
212
208
  Normalized MFE score (0-1) under this computational normalization,
213
209
  where a higher value represents a less negative whole-CDS MFE/nt.
214
- This is a Tier-0 computational heuristic only (see
215
- eijex-validationHub/docs/CLAIM_EVIDENCE_BENCHMARK_MODEL.md §4) — no
210
+ This is a Tier-0 computational heuristic only — no
216
211
  biological interpretation (translation efficiency, mRNA stability,
217
212
  or expression outcome) is implied or validated.
218
213
  """
@@ -418,7 +413,7 @@ def compute_mfe_evidence(
418
413
  elif not _check_vienna_available():
419
414
  reason = "MFE was not computed because ViennaRNA is unavailable in this environment."
420
415
  elif len(sequence) > MFE_MAX_SEQUENCE_LENGTH:
421
- # 170-fix: distinguish a deliberate length-based skip from an actual
416
+ # Distinguish a deliberate length-based skip from an actual
422
417
  # fold failure — the generic "computation failed" message below would
423
418
  # otherwise mislead a caller into thinking something is broken.
424
419
  reason = (
@@ -115,11 +115,10 @@ def calculate_dinucleotide_ratio(sequence: str, dinucleotide: str = "CG") -> flo
115
115
  return observed / expected
116
116
 
117
117
 
118
- # Job 168 / v3.3.0 (_analysis/025): host -> production-default codon table file
119
- # overrides. nbenthamiana moved from the legacy {host}_codons.json convention to
120
- # the NbeV1.1 LAB-strain derived table. See data/reference/active_codon_reference.json.
121
- # The legacy file itself (nbenthamiana_codons.json) is left untouched on disk for
122
- # historical benchmark replay (benchmarks/run_benchmark.py --codon-table-path).
118
+ # NbeV1.1 high-confidence CDS-derived codon usage is the production-default
119
+ # N. benthamiana software reference. This is a provenance-tracked in-silico
120
+ # default, not wet-lab validation or a biological-superiority claim; see
121
+ # data/reference/active_codon_reference.json.
123
122
  _HOST_CODON_TABLE_OVERRIDES: dict[str, str] = {
124
123
  "nbenthamiana": "profiles/nbev11_cds_hc_derived_codons.json",
125
124
  }
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.2.7
4
- Summary: FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex.
3
+ Version: 3.3.0
4
+ Summary: FactorForge - open-source CDS design and pre-synthesis sequence review by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
7
7
  Project-URL: Homepage, https://factorforge.eijex.com
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
 
34
34
  # FactorForge
35
35
 
36
- **Open-source constraint-based CDS design and pre-synthesis sequence review engine for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
36
+ **Open-source constraint-based CDS design and pre-synthesis sequence review for plant CDS workflows, with primary support for *Nicotiana benthamiana* (Tobacco BY-2: experimental).**
37
37
 
38
38
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
39
39
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
@@ -93,7 +93,7 @@ FactorForge outputs are **in-silico only** and have not been experimentally vali
93
93
  ## Citing
94
94
 
95
95
  ```
96
- FactorForge v3.2.7 (2026). Open-source constraint-based CDS design engine.
96
+ FactorForge v3.3.0 (2026). Open-source constraint-based CDS design and sequence review.
97
97
  Eijex. https://github.com/eijex/factorforge-cds
98
98
  ```
99
99
 
@@ -111,8 +111,8 @@ def test_smoke_summary_contains_codon_table_fields(tmp_path):
111
111
  assert field in data, f"Missing codon table field in smoke summary JSON: {field}"
112
112
 
113
113
  # No --codon-table-path override ⇒ codon_table_id must reflect whichever
114
- # asset is actually the current production default (Job 168 / v3.3.0,
115
- # _analysis/025 — see data/reference/active_codon_reference.json), not a
114
+ # asset is actually the current production default (v3.3.0 reference-policy update,
115
+ # reference-policy audit — see data/reference/active_codon_reference.json), not a
116
116
  # hardcoded legacy literal.
117
117
  active_ref = json.loads(
118
118
  (ROOT / "data" / "reference" / "active_codon_reference.json").read_text(encoding="utf-8")
@@ -148,9 +148,6 @@ def test_smoke_summary_contains_vienna_rna_active(tmp_path):
148
148
 
149
149
  def test_no_raw_genome_fasta_committed():
150
150
  """Raw genome/CDS/protein FASTA files must not be committed to the repo."""
151
- forbidden_patterns = ["*.fasta", "*.fa", "*.fna", "*.fastq"]
152
- allowed_fixtures = ROOT / "tests" / "fixtures"
153
-
154
151
  import subprocess
155
152
  result = subprocess.run(
156
153
  ["git", "ls-files", "--", "*.fasta", "*.fa", "*.fna", "*.fastq"],
@@ -168,6 +165,6 @@ def test_no_raw_genome_fasta_committed():
168
165
  if p and not any(p.startswith(pfx) for pfx in allowed_prefixes)
169
166
  ]
170
167
  assert not committed, (
171
- f"Raw FASTA/genome files must not be committed outside allowed locations:\n"
168
+ "Raw FASTA/genome files must not be committed outside allowed locations:\n"
172
169
  + "\n".join(committed)
173
170
  )
@@ -76,7 +76,7 @@ def test_aa_identity_policy_sync():
76
76
  assert ok["passed"] and ok["aa_identity"] == registry_val
77
77
 
78
78
 
79
- # ── codon_reference source-of-truth sync (Job 168 / v3.3.0, _analysis/025) ────
79
+ # ── codon_reference source-of-truth sync (v3.3.0 reference-policy update) ────
80
80
 
81
81
  def test_codon_reference_active_sync_with_active_reference_file():
82
82
  """registry's codon_reference.active block must match
@@ -105,7 +105,7 @@ def test_codon_reference_active_sync_with_active_reference_file():
105
105
 
106
106
  def test_codon_reference_active_sync_with_v2_manifest():
107
107
  """registry's codon_reference.active block must match the schema-conformant
108
- v2 manifest file's facts (asset_type, sha256, source_status)."""
108
+ NbeV1.1 HC (v2) manifest file's facts now that v2 is the production default."""
109
109
  import json
110
110
  from pathlib import Path
111
111
 
@@ -120,6 +120,23 @@ def test_codon_reference_active_sync_with_v2_manifest():
120
120
  assert registry_active["source_status"] == manifest["source_status"]
121
121
 
122
122
 
123
+ def test_codon_reference_historical_comparator_sync_with_legacy_manifest():
124
+ """registry's codon_reference.historical_comparator block must stay in sync
125
+ with the legacy v1 manifest retained for v3.2.x continuity/replay."""
126
+ import json
127
+ from pathlib import Path
128
+
129
+ registry_historical = _resolve("codon_reference.historical_comparator")
130
+ manifest = json.loads(
131
+ (Path(__file__).resolve().parents[1] / "data" / "reference"
132
+ / "codon_table_manifest.json").read_text(encoding="utf-8")
133
+ )
134
+ assert registry_historical["id"] == manifest["codon_table_id"]
135
+ assert registry_historical["sha256"] == manifest["sha256"]
136
+ assert registry_historical["asset_type"] == manifest["asset_type"]
137
+ assert registry_historical["source_status"] == manifest["source_status"]
138
+
139
+
123
140
  def test_codon_reference_active_table_sha256_matches_production_default():
124
141
  """The sha256 recorded for the active codon_reference must match the
125
142
  actual file the production engine resolves to by default."""
@@ -63,7 +63,7 @@ def test_sfgfp_sequence_no_x_residues():
63
63
  def test_run_example_deterministic():
64
64
  """run_example.py (no --freeze) must exit 0 when frozen outputs exist.
65
65
 
66
- Job 168 / v3.3.0 (_analysis/025): this script is explicitly pinned to the
66
+ v3.3.0 reference-policy update: this script is explicitly pinned to the
67
67
  legacy v1 codon reference (GC 55-65%) regardless of the engine's current
68
68
  production default, so this reproducibility check stays valid forever.
69
69
  See run_example_v2_smoke.py / test_run_example_v2_smoke_succeeds for the
@@ -82,10 +82,13 @@ def test_run_example_deterministic():
82
82
 
83
83
 
84
84
  def test_run_example_v2_smoke_succeeds():
85
- """run_example_v2_smoke.py (current production default) must exit 0.
85
+ """run_example_v2_smoke.py (tracks whatever the current production
86
+ default actually is) must exit 0 and report a provenance ID that matches
87
+ data/reference/active_codon_reference.json — not hardcoded to v2, since
88
+ the example is aligned to the current v3.3.0 production-default reference policy.
86
89
 
87
- No frozen-output comparison — this only checks that the v2 path runs
88
- end-to-end and reports correct provenance (Job 168 / v3.3.0, _analysis/025).
90
+ No frozen-output comparison — this only checks that the current-default
91
+ path runs end-to-end and reports correct provenance.
89
92
  """
90
93
  result = subprocess.run(
91
94
  [sys.executable, str(EXAMPLE_DIR / "run_example_v2_smoke.py")],
@@ -98,7 +101,8 @@ def test_run_example_v2_smoke_succeeds():
98
101
  f"stdout:{result.stdout}\nstderr:{result.stderr}"
99
102
  )
100
103
  assert "OK" in result.stdout
101
- assert "nbenthamiana_nbev11_hc_v2" in result.stdout
104
+ active_ref = _load(ROOT / "data" / "reference" / "active_codon_reference.json")
105
+ assert active_ref["active_codon_table_id"] in result.stdout
102
106
 
103
107
 
104
108
  # ---------------------------------------------------------------------------
File without changes