factorforge-cds 3.2.7__tar.gz → 3.2.8__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.2.8}/PKG-INFO +2 -2
  2. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/README.md +1 -1
  3. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/pyproject.toml +1 -1
  4. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/analysis/feasibility.py +11 -14
  6. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/cli/main.py +2 -2
  7. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/__init__.py +1 -1
  8. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/__init__.py +1 -1
  9. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/optimizer.py +1 -1
  10. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/rules/reverse_translator.py +6 -5
  11. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/rules/rule_engine.py +8 -6
  12. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/scoring.py +18 -14
  13. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/utils.py +7 -8
  14. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8/src/factorforge_cds.egg-info}/PKG-INFO +2 -2
  15. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_registry_production_sync.py +23 -3
  16. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_worked_example.py +10 -5
  17. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/LICENSE +0 -0
  18. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/setup.cfg +0 -0
  19. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/__main__.py +0 -0
  20. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/analysis/__init__.py +0 -0
  21. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/analysis/metrics.py +0 -0
  22. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/cli/__init__.py +0 -0
  23. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/cli/legacy_cli.py +0 -0
  24. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/core/interfaces/__init__.py +0 -0
  25. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/core/interfaces/exporter.py +0 -0
  26. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/core/interfaces/optimizer.py +0 -0
  27. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/core/interfaces/validator.py +0 -0
  28. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/nbenthamiana_codons.json +0 -0
  29. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/nbenthamiana_golden_set.json +0 -0
  30. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/ntabacum_codons.json +0 -0
  31. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/templates/high_expression.json +0 -0
  32. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/templates/standard_expression.json +0 -0
  33. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/data/wolffia_globosa_codons.json +0 -0
  34. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/database.py +0 -0
  35. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
  36. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/construct_builder.py +0 -0
  37. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/exporter.py +0 -0
  38. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/pipeline.py +0 -0
  39. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/rules/__init__.py +0 -0
  40. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/rules/domesticator.py +0 -0
  41. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/scoring_ml.py +0 -0
  42. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/profile/validator.py +0 -0
  43. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/engines/registry.py +0 -0
  44. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/io/__init__.py +0 -0
  45. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/io/fasta.py +0 -0
  46. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/io/validation.py +0 -0
  47. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/__init__.py +0 -0
  48. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/annotate.py +0 -0
  49. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/kd_scale.py +0 -0
  50. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/risk_classifier.py +0 -0
  51. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/sp_predict.py +0 -0
  52. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/protein_risk/tm_predict.py +0 -0
  53. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/registry/__init__.py +0 -0
  54. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/registry/registry_loader.py +0 -0
  55. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/schemas/__init__.py +0 -0
  56. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/schemas/design_package.py +0 -0
  57. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/schemas/design_package.schema.json +0 -0
  58. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/__init__.py +0 -0
  59. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/construct_id.py +0 -0
  60. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/exceptions.py +0 -0
  61. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/restriction_sites.py +0 -0
  62. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/sequence_validator.py +0 -0
  63. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/utils/validation.py +0 -0
  64. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/validation/__init__.py +0 -0
  65. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/validation/cli.py +0 -0
  66. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/validation/package_generator.py +0 -0
  67. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/validation_registry.py +0 -0
  68. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge/validation_report.py +0 -0
  69. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge_cds.egg-info/SOURCES.txt +0 -0
  70. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  71. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
  72. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge_cds.egg-info/requires.txt +0 -0
  73. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  74. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_baselines.py +0 -0
  75. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_benchmark_codon_table_metadata.py +0 -0
  76. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_benchmark_regression.py +0 -0
  77. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_benchmark_scoring.py +0 -0
  78. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_benchmark_smoke.py +0 -0
  79. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_cai.py +0 -0
  80. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_codon_table_manifest.py +0 -0
  81. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_database.py +0 -0
  82. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_design_package_schema.py +0 -0
  83. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_design_package_semantics.py +0 -0
  84. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_design_package_serialization.py +0 -0
  85. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_docs_consistency.py +0 -0
  86. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_fasta_io.py +0 -0
  87. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_gc_content.py +0 -0
  88. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_host_profile_metadata.py +0 -0
  89. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_iupac_validation.py +0 -0
  90. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_legacy_cli.py +0 -0
  91. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_no_raw_sequence_logging.py +0 -0
  92. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_openbio_missing_metric_contract.py +0 -0
  93. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_parameter_registry.py +0 -0
  94. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_protein_risk.py +0 -0
  95. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_restriction_sites.py +0 -0
  96. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_sequence_validator.py +0 -0
  97. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_translation_integrity.py +0 -0
  98. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_validation_contract_compat.py +0 -0
  99. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_validation_registry.py +0 -0
  100. {factorforge_cds-3.2.7 → factorforge_cds-3.2.8}/tests/test_validation_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.2.7
3
+ Version: 3.2.8
4
4
  Summary: FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
@@ -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.2.8 (2026). Open-source constraint-based CDS design engine.
97
97
  Eijex. https://github.com/eijex/factorforge-cds
98
98
  ```
99
99
 
@@ -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.2.8 (2026). Open-source constraint-based CDS design engine.
64
64
  Eijex. https://github.com/eijex/factorforge-cds
65
65
  ```
66
66
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "factorforge-cds"
7
- version = "3.2.7"
7
+ version = "3.2.8"
8
8
  description = "FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex."
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-only"
@@ -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.2.8"
8
8
  __author__ = "Eijex"
9
9
 
10
10
  # Auto-register engines (safe when running from source tree)
@@ -14,18 +14,19 @@ 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 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.
24
20
  # Exported as named constants so tests/test_registry_production_sync.py can
25
21
  # 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.
26
27
  DEFAULT_CAI_TARGET: float = 0.82
27
- DEFAULT_GC_LOW: float = 40.0
28
- DEFAULT_GC_HIGH: float = 47.0
28
+ DEFAULT_GC_LOW: float = 55.0
29
+ DEFAULT_GC_HIGH: float = 65.0
29
30
 
30
31
 
31
32
  AA_TO_CODONS: dict[str, list[str]] = {}
@@ -120,11 +121,7 @@ def analyze_feasibility(
120
121
  if not protein:
121
122
  raise ValueError("protein_sequence must not be empty")
122
123
 
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)]
124
+ ranges = gc_ranges or [(55.0, 65.0), (50.0, 65.0), (40.0, 65.0)]
128
125
  normalized_ranges = [
129
126
  (_normalize_gc_bound(low), _normalize_gc_bound(high)) for low, high in ranges
130
127
  ]
@@ -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=40.0, help="Minimum target GC percentage")
168
- @click.option("--gc-max", type=float, default=47.0, help="Maximum target GC percentage")
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")
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.7",
16
+ "version": "3.2.8",
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.2.8"
9
9
 
10
10
  from .optimizer import RuleBasedOptimizer
11
11
  from .pipeline import OptimizationPipeline
@@ -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.2.8"
24
24
 
25
25
  def __init__(self, codon_table_path: str | None = None) -> None:
26
26
  """
@@ -374,11 +374,12 @@ 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). 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.
382
383
  """
383
384
  _host_gc_min, _host_gc_max = resolve_host_gc_range(self.host)
384
385
  target_gc_min = kwargs.get("target_gc_min", _host_gc_min)
@@ -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, 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
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
@@ -12,22 +12,26 @@ from typing import Any
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
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 optimumthis 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.
21
- GC_OPT_MIN = 40.0
22
- GC_OPT_MAX = 47.0
23
- GC_OPT_MID = 43.5 # kept for gc_target point-scoring and viral_delivery centering
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 bandsequences 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
24
30
  GC_DECAY_WIDTH = 20.0 # percentage points outside band before score reaches 0.0
25
31
 
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.
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.
31
35
  GC_RANGE_DEFAULT: tuple[float, float] = (55.0, 65.0)
32
36
  GC_RANGES_BY_HOST: dict[str, tuple[float, float]] = {
33
37
  "nbenthamiana": (GC_OPT_MIN, GC_OPT_MAX),
@@ -115,14 +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): 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).
123
- _HOST_CODON_TABLE_OVERRIDES: dict[str, str] = {
124
- "nbenthamiana": "profiles/nbev11_cds_hc_derived_codons.json",
125
- }
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] = {}
126
125
 
127
126
 
128
127
  def resolve_host_codon_table_path(host: str, codon_tables_dir: Path) -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.2.7
3
+ Version: 3.2.8
4
4
  Summary: FactorForge - open-source CDS design and pre-synthesis sequence review engine by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
@@ -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.2.8 (2026). Open-source constraint-based CDS design engine.
97
97
  Eijex. https://github.com/eijex/factorforge-cds
98
98
  ```
99
99
 
@@ -103,16 +103,18 @@ 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_v2_manifest():
106
+ def test_codon_reference_active_sync_with_legacy_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
+ 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."""
109
111
  import json
110
112
  from pathlib import Path
111
113
 
112
114
  registry_active = _resolve("codon_reference.active")
113
115
  manifest = json.loads(
114
116
  (Path(__file__).resolve().parents[1] / "data" / "reference"
115
- / "codon_table_manifest_nbev11_hc_v2.json").read_text(encoding="utf-8")
117
+ / "codon_table_manifest.json").read_text(encoding="utf-8")
116
118
  )
117
119
  assert registry_active["id"] == manifest["codon_table_id"]
118
120
  assert registry_active["sha256"] == manifest["sha256"]
@@ -120,6 +122,24 @@ def test_codon_reference_active_sync_with_v2_manifest():
120
122
  assert registry_active["source_status"] == manifest["source_status"]
121
123
 
122
124
 
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."""
129
+ import json
130
+ from pathlib import Path
131
+
132
+ registry_candidate = _resolve("codon_reference.candidate")
133
+ manifest = json.loads(
134
+ (Path(__file__).resolve().parents[1] / "data" / "reference"
135
+ / "codon_table_manifest_nbev11_hc_v2.json").read_text(encoding="utf-8")
136
+ )
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"]
141
+
142
+
123
143
  def test_codon_reference_active_table_sha256_matches_production_default():
124
144
  """The sha256 recorded for the active codon_reference must match the
125
145
  actual file the production engine resolves to by default."""
@@ -82,10 +82,14 @@ 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.
86
-
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).
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.jsonnot 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).
90
+
91
+ No frozen-output comparison — this only checks that the current-default
92
+ path runs end-to-end and reports correct provenance.
89
93
  """
90
94
  result = subprocess.run(
91
95
  [sys.executable, str(EXAMPLE_DIR / "run_example_v2_smoke.py")],
@@ -98,7 +102,8 @@ def test_run_example_v2_smoke_succeeds():
98
102
  f"stdout:{result.stdout}\nstderr:{result.stderr}"
99
103
  )
100
104
  assert "OK" in result.stdout
101
- assert "nbenthamiana_nbev11_hc_v2" in result.stdout
105
+ active_ref = _load(ROOT / "data" / "reference" / "active_codon_reference.json")
106
+ assert active_ref["active_codon_table_id"] in result.stdout
102
107
 
103
108
 
104
109
  # ---------------------------------------------------------------------------
File without changes