factorforge-cds 3.2.8__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.8/src/factorforge_cds.egg-info → factorforge_cds-3.3.0}/PKG-INFO +4 -4
  2. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/README.md +2 -2
  3. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/pyproject.toml +2 -2
  4. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/analysis/feasibility.py +8 -11
  6. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/analysis/metrics.py +1 -1
  7. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/cli/main.py +2 -2
  8. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/__init__.py +1 -1
  9. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/__init__.py +1 -1
  10. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/optimizer.py +18 -2
  11. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/reverse_translator.py +7 -5
  12. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/rule_engine.py +5 -5
  13. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/scoring.py +18 -27
  14. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/utils.py +7 -7
  15. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0/src/factorforge_cds.egg-info}/PKG-INFO +4 -4
  16. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_benchmark_codon_table_metadata.py +3 -6
  17. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_registry_production_sync.py +13 -16
  18. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_worked_example.py +2 -3
  19. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/LICENSE +0 -0
  20. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/setup.cfg +0 -0
  21. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/__main__.py +0 -0
  22. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/analysis/__init__.py +0 -0
  23. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/cli/__init__.py +0 -0
  24. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/cli/legacy_cli.py +0 -0
  25. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/__init__.py +0 -0
  26. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/exporter.py +0 -0
  27. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/optimizer.py +0 -0
  28. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/core/interfaces/validator.py +0 -0
  29. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/nbenthamiana_codons.json +0 -0
  30. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/nbenthamiana_golden_set.json +0 -0
  31. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/ntabacum_codons.json +0 -0
  32. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/templates/high_expression.json +0 -0
  33. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/templates/standard_expression.json +0 -0
  34. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/data/wolffia_globosa_codons.json +0 -0
  35. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/database.py +0 -0
  36. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
  37. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/construct_builder.py +0 -0
  38. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/exporter.py +0 -0
  39. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/pipeline.py +0 -0
  40. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/__init__.py +0 -0
  41. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/rules/domesticator.py +0 -0
  42. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/scoring_ml.py +0 -0
  43. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/profile/validator.py +0 -0
  44. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/engines/registry.py +0 -0
  45. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/io/__init__.py +0 -0
  46. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/io/fasta.py +0 -0
  47. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/io/validation.py +0 -0
  48. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/__init__.py +0 -0
  49. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/annotate.py +0 -0
  50. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/kd_scale.py +0 -0
  51. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/risk_classifier.py +0 -0
  52. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/sp_predict.py +0 -0
  53. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/protein_risk/tm_predict.py +0 -0
  54. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/registry/__init__.py +0 -0
  55. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/registry/registry_loader.py +0 -0
  56. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/schemas/__init__.py +0 -0
  57. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/schemas/design_package.py +0 -0
  58. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/schemas/design_package.schema.json +0 -0
  59. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/__init__.py +0 -0
  60. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/construct_id.py +0 -0
  61. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/exceptions.py +0 -0
  62. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/restriction_sites.py +0 -0
  63. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/sequence_validator.py +0 -0
  64. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/utils/validation.py +0 -0
  65. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/validation/__init__.py +0 -0
  66. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/validation/cli.py +0 -0
  67. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/validation/package_generator.py +0 -0
  68. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/validation_registry.py +0 -0
  69. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge/validation_report.py +0 -0
  70. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/SOURCES.txt +0 -0
  71. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  72. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
  73. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/requires.txt +0 -0
  74. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  75. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_baselines.py +0 -0
  76. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_benchmark_regression.py +0 -0
  77. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_benchmark_scoring.py +0 -0
  78. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_benchmark_smoke.py +0 -0
  79. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_cai.py +0 -0
  80. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_codon_table_manifest.py +0 -0
  81. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_database.py +0 -0
  82. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_design_package_schema.py +0 -0
  83. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_design_package_semantics.py +0 -0
  84. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_design_package_serialization.py +0 -0
  85. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_docs_consistency.py +0 -0
  86. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_fasta_io.py +0 -0
  87. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_gc_content.py +0 -0
  88. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_host_profile_metadata.py +0 -0
  89. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_iupac_validation.py +0 -0
  90. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_legacy_cli.py +0 -0
  91. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_no_raw_sequence_logging.py +0 -0
  92. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_openbio_missing_metric_contract.py +0 -0
  93. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_parameter_registry.py +0 -0
  94. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_protein_risk.py +0 -0
  95. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_restriction_sites.py +0 -0
  96. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_sequence_validator.py +0 -0
  97. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_translation_integrity.py +0 -0
  98. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_validation_contract_compat.py +0 -0
  99. {factorforge_cds-3.2.8 → factorforge_cds-3.3.0}/tests/test_validation_registry.py +0 -0
  100. {factorforge_cds-3.2.8 → 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.8
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.8 (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.8 (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.8"
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.8"
7
+ __version__ = "3.3.0"
8
8
  __author__ = "Eijex"
9
9
 
10
10
  # Auto-register engines (safe when running from source tree)
@@ -14,19 +14,16 @@ from factorforge.analysis.metrics import (
14
14
  )
15
15
 
16
16
 
17
- # Defaults calibrated to nbenthamiana profile engine output distribution
18
- # (internal benchmark, n=49): avg CAI=0.76, avg GC=60.1% (range 55-71%).
19
- # DEFAULT_CAI_TARGET=0.82 aligns with industry practice (>0.8) and is achievable.
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.
20
22
  # Exported as named constants so tests/test_registry_production_sync.py can
21
23
  # strictly compare them against the registry (single source of truth).
22
- #
23
- # DEFAULT_GC_LOW/HIGH provisionally reverted from the Job 168/v3.3.0
24
- # native-genome-composition anchor (40-47%, released as part of v3.2.7) back
25
- # to the legacy engine-output-calibrated band, pending an MFE re-sensitivity +
26
- # 2x2 factorial recheck. See scoring.py's GC_OPT_MIN/MAX comment.
27
24
  DEFAULT_CAI_TARGET: float = 0.82
28
- DEFAULT_GC_LOW: float = 55.0
29
- DEFAULT_GC_HIGH: float = 65.0
25
+ DEFAULT_GC_LOW: float = 40.0
26
+ DEFAULT_GC_HIGH: float = 47.0
30
27
 
31
28
 
32
29
  AA_TO_CODONS: dict[str, list[str]] = {}
@@ -121,7 +118,7 @@ def analyze_feasibility(
121
118
  if not protein:
122
119
  raise ValueError("protein_sequence must not be empty")
123
120
 
124
- ranges = gc_ranges or [(55.0, 65.0), (50.0, 65.0), (40.0, 65.0)]
121
+ ranges = gc_ranges or [(40.0, 47.0), (40.0, 55.0), (40.0, 65.0)]
125
122
  normalized_ranges = [
126
123
  (_normalize_gc_bound(low), _normalize_gc_bound(high)) for low, high in ranges
127
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"
@@ -164,8 +164,8 @@ def list_engines():
164
164
  type=click.Choice(["feasibility_best"], case_sensitive=False),
165
165
  help="DP objective",
166
166
  )
167
- @click.option("--gc-min", type=float, default=55.0, help="Minimum target GC percentage")
168
- @click.option("--gc-max", type=float, default=65.0, help="Maximum target GC percentage")
167
+ @click.option("--gc-min", type=float, default=40.0, help="Minimum target GC percentage")
168
+ @click.option("--gc-max", type=float, default=47.0, help="Maximum target GC percentage")
169
169
  @click.option("--template", "construct_template", help="Construct template name")
170
170
  @click.option("--output", "-o", help="Output file")
171
171
  @click.option("--format", "output_format", default="fasta", help="Output format (fasta, genbank)")
@@ -13,7 +13,7 @@ def register_builtin_engines() -> None:
13
13
  "profile",
14
14
  RuleBasedOptimizer,
15
15
  metadata={
16
- "version": "3.2.8",
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.8"
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.8"
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.
@@ -376,10 +376,12 @@ class ReverseTranslator:
376
376
  - Preferred codon ratio: 70%
377
377
  - Target GC: host composition band, resolved via
378
378
  resolve_host_gc_range() (engines/profile/scoring.py
379
- GC_OPT_MIN/MAX). nbenthamiana currently uses the legacy 55-65%
380
- band; the native genome-composition anchor (40-47%, _analysis/025)
381
- was provisionally reverted on 2026-06-29 pending an MFE
382
- re-sensitivity + 2x2 factorial recheck.
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.
383
385
  """
384
386
  _host_gc_min, _host_gc_max = resolve_host_gc_range(self.host)
385
387
  target_gc_min = kwargs.get("target_gc_min", _host_gc_min)
@@ -454,7 +456,7 @@ class ReverseTranslator:
454
456
 
455
457
  Targets the caller-supplied ``target_gc`` if provided, otherwise the
456
458
  active host's composition midpoint (resolve_host_gc_range(self.host);
457
- 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
458
460
  hosts pending their own host-specific analysis). To target a different
459
461
  GC (e.g. for specific vector requirements), pass target_gc explicitly.
460
462
 
@@ -358,11 +358,11 @@ 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, currently 55-65% for nbenthamiana
362
- — the native genome-composition anchor, ~40-47%, _analysis/025, was
363
- provisionally reverted on 2026-06-29 pending an MFE re-sensitivity +
364
- 2x2 factorial recheck) and the API/DP gc_min/gc_max constraints. The
365
- wide 25-75% local band is an independent synthesis/hairpin-risk guard
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
366
  (registry `gc_extreme_local_window`), not derived from or coupled to
367
367
  the global GC target — narrowing it is a separate decision from the
368
368
  global band retarget and is out of scope here.
@@ -11,27 +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
- # Benchmark (internal, n=49): balanced profile output average GC% = 60.1%
16
- # (range 55-71%). The genome-wide average (~42%) reflects all genes, not the
17
- # high-expression codon table which exhibits 3rd-position GC bias.
18
- # These constants define the acceptable band — sequences within [GC_OPT_MIN, GC_OPT_MAX]
19
- # receive full GC score; outside the band the score decays linearly.
20
- #
21
- # Job 168/v3.3.0 (released as part of v3.2.7, see CHANGELOG) moved this band to
22
- # 40-47% (native genome-composition anchor, _analysis/025). Provisionally
23
- # reverted here pending an MFE re-sensitivity + 2x2 factorial recheck
24
- # (_analysis, scope TBD as of 2026-06-29) — see eijex-workspace
25
- # _version/factorforge-version-sequencing-plan.md. Not a rejection of the
26
- # 40-47% anchor, just not yet re-confirmed as the production default.
27
- GC_OPT_MIN = 55.0
28
- GC_OPT_MAX = 65.0
29
- GC_OPT_MID = 60.0 # kept for gc_target point-scoring and viral_delivery centering
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.
20
+ GC_OPT_MIN = 40.0
21
+ GC_OPT_MAX = 47.0
22
+ GC_OPT_MID = 43.5 # kept for gc_target point-scoring and viral_delivery centering
30
23
  GC_DECAY_WIDTH = 20.0 # percentage points outside band before score reaches 0.0
31
24
 
32
- # Host-isolation fix (Job 168/v3.3.0, kept): ntabacum/BY-2 must not silently
33
- # inherit whatever band nbenthamiana uses. Both currently resolve to the same
34
- # GC_RANGE_DEFAULT band pending the nbenthamiana re-check above.
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.
35
28
  GC_RANGE_DEFAULT: tuple[float, float] = (55.0, 65.0)
36
29
  GC_RANGES_BY_HOST: dict[str, tuple[float, float]] = {
37
30
  "nbenthamiana": (GC_OPT_MIN, GC_OPT_MAX),
@@ -41,7 +34,7 @@ GC_RANGES_BY_HOST: dict[str, tuple[float, float]] = {
41
34
  def resolve_host_gc_range(host: str | None) -> tuple[float, float]:
42
35
  """Resolve the (gc_min, gc_max) composition band for a host.
43
36
 
44
- nbenthamiana uses the _analysis/025 native genome-composition anchor.
37
+ nbenthamiana uses the reference-policy audit native genome-composition anchor.
45
38
  Any other host (including unknown ones) keeps the pre-v3.3.0 global
46
39
  default band until it gets its own host-specific analysis.
47
40
  """
@@ -146,7 +139,7 @@ def _check_vienna_available() -> bool:
146
139
  return _vienna_available
147
140
 
148
141
 
149
- # 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 /
150
143
  # O(n^2) memory — there was previously no length guard anywhere in the
151
144
  # calculate_mfe() call chain, so a single request at/under the existing
152
145
  # public API length limits (5000aa/15000bp) could pin a CPU core for minutes
@@ -179,7 +172,7 @@ def calculate_mfe(sequence: str) -> float | None:
179
172
  "Sequence length (%d nt) exceeds MFE_MAX_SEQUENCE_LENGTH (%d nt); "
180
173
  "skipping global MFE calculation to avoid an unbounded ViennaRNA "
181
174
  "RNA.fold() runtime (O(n^3)). MFE scoring falls back to a neutral, "
182
- "zero-weighted contribution for this candidate (170-fix).",
175
+ "zero-weighted contribution for this candidate.",
183
176
  len(sequence), MFE_MAX_SEQUENCE_LENGTH,
184
177
  )
185
178
  return None
@@ -200,8 +193,7 @@ def normalize_mfe(mfe: float, seq_length: int) -> float:
200
193
  """
201
194
  Normalize MFE to 0-1 range where 1 = no structure (favorable).
202
195
 
203
- Clamp range calibrated empirically in analysis 011
204
- (eijex-workspace/_analysis/2026-06-26/011-mfe-clamp-calibration):
196
+ Clamp range calibrated empirically from internal computational audit data:
205
197
  measured MFE/nt across 135 FactorForge outputs (N. benthamiana +
206
198
  BY-2 hosts, 5 profiles) ranged -0.4064 to -0.1338 (combined 5th/95th
207
199
  percentile -0.3839/-0.1760). The range below widens that empirical
@@ -215,8 +207,7 @@ def normalize_mfe(mfe: float, seq_length: int) -> float:
215
207
  Returns:
216
208
  Normalized MFE score (0-1) under this computational normalization,
217
209
  where a higher value represents a less negative whole-CDS MFE/nt.
218
- This is a Tier-0 computational heuristic only (see
219
- eijex-validationHub/docs/CLAIM_EVIDENCE_BENCHMARK_MODEL.md §4) — no
210
+ This is a Tier-0 computational heuristic only — no
220
211
  biological interpretation (translation efficiency, mRNA stability,
221
212
  or expression outcome) is implied or validated.
222
213
  """
@@ -422,7 +413,7 @@ def compute_mfe_evidence(
422
413
  elif not _check_vienna_available():
423
414
  reason = "MFE was not computed because ViennaRNA is unavailable in this environment."
424
415
  elif len(sequence) > MFE_MAX_SEQUENCE_LENGTH:
425
- # 170-fix: distinguish a deliberate length-based skip from an actual
416
+ # Distinguish a deliberate length-based skip from an actual
426
417
  # fold failure — the generic "computation failed" message below would
427
418
  # otherwise mislead a caller into thinking something is broken.
428
419
  reason = (
@@ -115,13 +115,13 @@ 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) introduced a host -> production-default
119
- # codon table file override mechanism and pointed nbenthamiana at the NbeV1.1
120
- # LAB-strain derived table (released as part of v3.2.7). Provisionally
121
- # reverted to empty (falls back to the legacy {host}_codons.json convention)
122
- # pending an MFE re-sensitivity + 2x2 factorial recheck. The NbeV1.1 table
123
- # remains on disk and selectable; see data/reference/active_codon_reference.json.
124
- _HOST_CODON_TABLE_OVERRIDES: dict[str, str] = {}
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.
122
+ _HOST_CODON_TABLE_OVERRIDES: dict[str, str] = {
123
+ "nbenthamiana": "profiles/nbev11_cds_hc_derived_codons.json",
124
+ }
125
125
 
126
126
 
127
127
  def resolve_host_codon_table_path(host: str, codon_tables_dir: Path) -> Path:
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.2.8
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.8 (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
@@ -103,18 +103,16 @@ def test_codon_reference_active_sync_with_active_reference_file():
103
103
  assert registry_active["sha256"] == hashlib.sha256(table_path.read_bytes()).hexdigest()
104
104
 
105
105
 
106
- def test_codon_reference_active_sync_with_legacy_manifest():
106
+ def test_codon_reference_active_sync_with_v2_manifest():
107
107
  """registry's codon_reference.active block must match the schema-conformant
108
- legacy (v1) manifest file's facts (asset_type, sha256, source_status) — v1
109
- is the production default again as of the v3.2.7 GC-band/codon-reference
110
- revert, pending an MFE re-sensitivity + 2x2 factorial recheck of v2."""
108
+ NbeV1.1 HC (v2) manifest file's facts now that v2 is the production default."""
111
109
  import json
112
110
  from pathlib import Path
113
111
 
114
112
  registry_active = _resolve("codon_reference.active")
115
113
  manifest = json.loads(
116
114
  (Path(__file__).resolve().parents[1] / "data" / "reference"
117
- / "codon_table_manifest.json").read_text(encoding="utf-8")
115
+ / "codon_table_manifest_nbev11_hc_v2.json").read_text(encoding="utf-8")
118
116
  )
119
117
  assert registry_active["id"] == manifest["codon_table_id"]
120
118
  assert registry_active["sha256"] == manifest["sha256"]
@@ -122,22 +120,21 @@ def test_codon_reference_active_sync_with_legacy_manifest():
122
120
  assert registry_active["source_status"] == manifest["source_status"]
123
121
 
124
122
 
125
- def test_codon_reference_candidate_sync_with_v2_manifest():
126
- """registry's codon_reference.candidate block (the provisionally
127
- un-promoted v2 asset) must stay in sync with its own manifest file even
128
- while not active."""
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."""
129
126
  import json
130
127
  from pathlib import Path
131
128
 
132
- registry_candidate = _resolve("codon_reference.candidate")
129
+ registry_historical = _resolve("codon_reference.historical_comparator")
133
130
  manifest = json.loads(
134
131
  (Path(__file__).resolve().parents[1] / "data" / "reference"
135
- / "codon_table_manifest_nbev11_hc_v2.json").read_text(encoding="utf-8")
132
+ / "codon_table_manifest.json").read_text(encoding="utf-8")
136
133
  )
137
- assert registry_candidate["id"] == manifest["codon_table_id"]
138
- assert registry_candidate["sha256"] == manifest["sha256"]
139
- assert registry_candidate["asset_type"] == manifest["asset_type"]
140
- assert registry_candidate["source_status"] == manifest["source_status"]
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"]
141
138
 
142
139
 
143
140
  def test_codon_reference_active_table_sha256_matches_production_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
@@ -85,8 +85,7 @@ def test_run_example_v2_smoke_succeeds():
85
85
  """run_example_v2_smoke.py (tracks whatever the current production
86
86
  default actually is) must exit 0 and report a provenance ID that matches
87
87
  data/reference/active_codon_reference.json — not hardcoded to v2, since
88
- the default was provisionally reverted to v1 on 2026-06-29 pending an MFE
89
- re-sensitivity + 2x2 factorial recheck (Job 168 / v3.3.0, _analysis/025).
88
+ the example is aligned to the current v3.3.0 production-default reference policy.
90
89
 
91
90
  No frozen-output comparison — this only checks that the current-default
92
91
  path runs end-to-end and reports correct provenance.
File without changes