factorforge-cds 3.1.1__tar.gz → 3.1.2__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 (67) hide show
  1. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/PKG-INFO +4 -3
  2. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/README.md +2 -2
  3. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/pyproject.toml +3 -1
  4. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/__init__.py +1 -1
  6. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/__init__.py +1 -1
  7. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/optimizer.py +1 -1
  8. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/reverse_translator.py +4 -3
  9. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/scoring.py +4 -2
  10. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/__init__.py +1 -1
  11. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/pipeline.py +1 -1
  12. factorforge_cds-3.1.2/src/factorforge/schemas/__init__.py +3 -0
  13. factorforge_cds-3.1.2/src/factorforge/schemas/design_package.py +88 -0
  14. factorforge_cds-3.1.2/src/factorforge/validation/__init__.py +3 -0
  15. factorforge_cds-3.1.2/src/factorforge/validation/cli.py +99 -0
  16. factorforge_cds-3.1.2/src/factorforge/validation/package_generator.py +184 -0
  17. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/PKG-INFO +4 -3
  18. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/SOURCES.txt +5 -0
  19. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/entry_points.txt +1 -0
  20. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/requires.txt +1 -0
  21. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/LICENSE +0 -0
  22. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/setup.cfg +0 -0
  23. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/__main__.py +0 -0
  24. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/__init__.py +0 -0
  25. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/legacy_cli.py +0 -0
  26. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/main.py +0 -0
  27. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/__init__.py +0 -0
  28. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/exporter.py +0 -0
  29. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/optimizer.py +0 -0
  30. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/validator.py +0 -0
  31. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/database.py +0 -0
  32. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/ml/__init__.py +0 -0
  33. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/ml/plant_optimizer.py +0 -0
  34. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/registry.py +0 -0
  35. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v1_archived/__init__.py +0 -0
  36. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/codon_table_builder.py +0 -0
  37. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/construct_builder.py +0 -0
  38. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/exporter.py +0 -0
  39. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/pipeline.py +0 -0
  40. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/__init__.py +0 -0
  41. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/domesticator.py +0 -0
  42. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/rule_engine.py +0 -0
  43. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/utils.py +0 -0
  44. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/validator.py +0 -0
  45. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/explain.py +0 -0
  46. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/__init__.py +0 -0
  47. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/constrained_decoder.py +0 -0
  48. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/v2_adapter.py +0 -0
  49. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/metrics.py +0 -0
  50. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/modeling_bart_decoder.py +0 -0
  51. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/synonym_mask.py +0 -0
  52. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/tokenizer.py +0 -0
  53. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/__init__.py +0 -0
  54. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/feasibility.py +0 -0
  55. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/metrics.py +0 -0
  56. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/__init__.py +0 -0
  57. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/construct_id.py +0 -0
  58. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/exceptions.py +0 -0
  59. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/restriction_sites.py +0 -0
  60. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/sequence_validator.py +0 -0
  61. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/validation.py +0 -0
  62. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  63. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  64. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_compare_v1_v2.py +0 -0
  65. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_database.py +0 -0
  66. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_restriction_sites.py +0 -0
  67. {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_sequence_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.1.1
3
+ Version: 3.1.2
4
4
  Summary: FactorForge — open-source constraint-based CDS design engine by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License: Apache License
@@ -220,6 +220,7 @@ License-File: LICENSE
220
220
  Requires-Dist: biopython>=1.81
221
221
  Requires-Dist: requests>=2.31
222
222
  Requires-Dist: click>=8.0
223
+ Requires-Dist: pydantic>=2.0
223
224
  Provides-Extra: ml
224
225
  Requires-Dist: torch>=2.0; extra == "ml"
225
226
  Requires-Dist: transformers>=4.35; extra == "ml"
@@ -237,7 +238,7 @@ Dynamic: license-file
237
238
 
238
239
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
239
240
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
240
- [![Version](https://img.shields.io/badge/version-3.1.1-green.svg)](https://github.com/eijex/factorforge-cds/releases)
241
+ [![Version](https://img.shields.io/badge/version-3.1.2-green.svg)](https://github.com/eijex/factorforge-cds/releases)
241
242
  [![PyPI](https://img.shields.io/pypi/v/factorforge-cds.svg)](https://pypi.org/project/factorforge-cds/)
242
243
  [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
243
244
 
@@ -277,7 +278,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
277
278
  ## Citing
278
279
 
279
280
  ```
280
- FactorForge v3.1.1 (2026). Open-source constraint-based CDS design engine.
281
+ FactorForge v3.1.2 (2026). Open-source constraint-based CDS design engine.
281
282
  Eijex. https://github.com/eijex/factorforge-cds
282
283
  ```
283
284
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
6
6
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
7
- [![Version](https://img.shields.io/badge/version-3.1.1-green.svg)](https://github.com/eijex/factorforge-cds/releases)
7
+ [![Version](https://img.shields.io/badge/version-3.1.2-green.svg)](https://github.com/eijex/factorforge-cds/releases)
8
8
  [![PyPI](https://img.shields.io/pypi/v/factorforge-cds.svg)](https://pypi.org/project/factorforge-cds/)
9
9
  [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
10
10
 
@@ -44,7 +44,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
44
44
  ## Citing
45
45
 
46
46
  ```
47
- FactorForge v3.1.1 (2026). Open-source constraint-based CDS design engine.
47
+ FactorForge v3.1.2 (2026). Open-source constraint-based CDS design engine.
48
48
  Eijex. https://github.com/eijex/factorforge-cds
49
49
  ```
50
50
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "factorforge-cds"
7
- version = "3.1.1"
7
+ version = "3.1.2"
8
8
  description = "FactorForge — open-source constraint-based CDS design engine by Eijex."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "biopython>=1.81",
24
24
  "requests>=2.31",
25
25
  "click>=8.0",
26
+ "pydantic>=2.0",
26
27
  ]
27
28
 
28
29
  [project.optional-dependencies]
@@ -45,6 +46,7 @@ Issues = "https://github.com/eijex/factorforge-cds/issues"
45
46
 
46
47
  [project.scripts]
47
48
  factorforge = "factorforge.cli.main:main"
49
+ factorforge-validate = "factorforge.validation.cli:main"
48
50
 
49
51
  [tool.setuptools.packages.find]
50
52
  where = ["src"]
@@ -5,7 +5,7 @@ v2: Rule-based engine (Production)
5
5
  v3: ML engine (ESM2 + BART)
6
6
  """
7
7
 
8
- __version__ = "3.1.1"
8
+ __version__ = "3.1.2"
9
9
  __author__ = "Eijex"
10
10
 
11
11
  # Auto-register engines (safe when running from source tree)
@@ -29,7 +29,7 @@ def register_builtin_engines() -> None:
29
29
  "v2",
30
30
  RuleBasedOptimizer,
31
31
  metadata={
32
- "version": "3.1.1",
32
+ "version": "3.1.2",
33
33
  "engine_type": "rule_based",
34
34
  "role": "legacy_fallback",
35
35
  "stable": True,
@@ -5,7 +5,7 @@ Production system (2026)
5
5
  Plant-specific rule-based optimization
6
6
  """
7
7
 
8
- __version__ = "3.1.1"
8
+ __version__ = "3.1.2"
9
9
 
10
10
  from .optimizer import RuleBasedOptimizer
11
11
  from .pipeline import OptimizationPipeline
@@ -17,7 +17,7 @@ class RuleBasedOptimizer(OptimizerEngine):
17
17
  """Rule-based optimization engine"""
18
18
 
19
19
  name = "Rule-based"
20
- version = "3.1.1"
20
+ version = "3.1.2"
21
21
 
22
22
  def __init__(self) -> None:
23
23
  self.validator = InputValidator()
@@ -562,9 +562,10 @@ class ReverseTranslator:
562
562
  if len(all_codons) <= 1:
563
563
  continue
564
564
 
565
- # Select from bottom 50% by frequency (deoptimized)
566
- midpoint = max(1, len(all_codons) // 2)
567
- low_freq_codons = all_codons[midpoint:]
565
+ # Select from bottom 25% by frequency (mild deoptimization).
566
+ # PMC11718241: optimal tAI_ramp ~0.8-1.2 (max 20% slower than full sequence).
567
+ cutoff = max(1, (3 * len(all_codons)) // 4)
568
+ low_freq_codons = all_codons[cutoff:]
568
569
 
569
570
  if not low_freq_codons:
570
571
  continue
@@ -63,8 +63,10 @@ PROFILE_SCORING_CONFIGS: dict[str, ScoringConfig] = {
63
63
  "gc_target": ScoringConfig(w_cai=0.1, w_gc=0.7, w_mfe=0.2, gc_opt=GC_OPT_MID),
64
64
  "assembly_friendly": ScoringConfig(w_cai=0.5, w_gc=0.3, w_mfe=0.2, gc_opt=GC_OPT_MID),
65
65
  "ramp": ScoringConfig(w_cai=0.4, w_gc=0.3, w_mfe=0.3, gc_opt=GC_OPT_MID),
66
- # TRV viral-delivery profile — Li et al. (2026): prioritize MFE and viral-context GC target.
67
- "viral_delivery": ScoringConfig(w_cai=0.35, w_gc=0.25, w_mfe=0.40, gc_opt=47.5, use_mfe=True),
66
+ # TRV viral-delivery profile — GC target ~47.5% based on TRV genome composition.
67
+ # MFE weighted at 0.30 (Peccoud et al. 2024, PMC11718241: secondary structure shows
68
+ # weak univariate correlation with yield in tobacco viral expression).
69
+ "viral_delivery": ScoringConfig(w_cai=0.35, w_gc=0.35, w_mfe=0.30, gc_opt=47.5, use_mfe=True),
68
70
  }
69
71
 
70
72
 
@@ -4,7 +4,7 @@ FactorForge v3 - BART decoder scaffolding.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- __version__ = "3.1.1"
7
+ __version__ = "3.1.2"
8
8
 
9
9
  from .pipeline import V3Optimizer, V3Pipeline
10
10
  from .tokenizer import AATokenizer, CodonTokenizer
@@ -153,7 +153,7 @@ class V3Optimizer(OptimizerEngine):
153
153
  """Optimizer wrapper for v3 pipeline."""
154
154
 
155
155
  name = "v3 BART Decoder"
156
- version = "3.1.1"
156
+ version = "3.1.2"
157
157
 
158
158
  def __init__(self, pipeline: V3Pipeline | None = None) -> None:
159
159
  self.pipeline = pipeline or V3Pipeline()
@@ -0,0 +1,3 @@
1
+ from .design_package import DesignPackage
2
+
3
+ __all__ = ["DesignPackage"]
@@ -0,0 +1,88 @@
1
+ """Shared design package schema for FactorForge CDS artifacts."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class TargetInfo(BaseModel):
9
+ model_config = ConfigDict(extra="allow")
10
+
11
+ name: Optional[str] = None
12
+ uniprot_id: Optional[str] = None
13
+ species: Optional[str] = None
14
+ protein_class: Optional[str] = None
15
+
16
+
17
+ class ConstructPlan(BaseModel):
18
+ model_config = ConfigDict(extra="allow")
19
+
20
+ construct_type: Optional[str] = None
21
+ tag: Optional[str] = None
22
+ signal_peptide: Optional[str] = None
23
+ kozak: bool = True
24
+
25
+
26
+ class CdsDesign(BaseModel):
27
+ model_config = ConfigDict(extra="allow")
28
+
29
+ engine: str
30
+ product_version: str
31
+ host_profile: str
32
+ objective: str
33
+ profile: Optional[str] = None
34
+ input_length_aa: Optional[int] = None
35
+ output_length_nt: Optional[int] = None
36
+ cai: float
37
+ gc_percent: float
38
+
39
+
40
+ class ConstraintReport(BaseModel):
41
+ model_config = ConfigDict(extra="allow")
42
+
43
+ restriction_sites_removed: list[str] = Field(default_factory=list)
44
+ restriction_sites_unresolved: list[str] = Field(default_factory=list)
45
+ polya_warnings: int = 0
46
+ internal_stop_count: int = 0
47
+ codon_rarity_clusters: int = 0
48
+ aa_identity: float = 1.0
49
+
50
+
51
+ class ValidationStatus(BaseModel):
52
+ model_config = ConfigDict(extra="allow")
53
+
54
+ in_silico: str = "unchecked"
55
+ aa_identity_check: str = "unchecked"
56
+ gc_check: str = "unchecked"
57
+ polya_check: str = "unchecked"
58
+ moclo_check: str = "unchecked"
59
+
60
+
61
+ class Provenance(BaseModel):
62
+ model_config = ConfigDict(extra="allow")
63
+
64
+ input_sequence_hash: str
65
+ output_cds_hash: str
66
+ parameter_hash: str
67
+
68
+
69
+ class WetLabFeedback(BaseModel):
70
+ model_config = ConfigDict(extra="allow")
71
+
72
+ status: str = "pending"
73
+ submissions: list[Any] = Field(default_factory=list)
74
+
75
+
76
+ class DesignPackage(BaseModel):
77
+ model_config = ConfigDict(extra="allow")
78
+
79
+ design_package_version: str = "1.0"
80
+ construct_id: str
81
+ created_at: str
82
+ target: Optional[TargetInfo] = None
83
+ construct_plan: Optional[ConstructPlan] = None
84
+ cds_design: CdsDesign
85
+ constraint_report: Optional[ConstraintReport] = None
86
+ validation_status: Optional[ValidationStatus] = None
87
+ provenance: Provenance
88
+ wet_lab_feedback: WetLabFeedback = Field(default_factory=WetLabFeedback)
@@ -0,0 +1,3 @@
1
+ from .package_generator import ValidationPackageGenerator
2
+
3
+ __all__ = ["ValidationPackageGenerator"]
@@ -0,0 +1,99 @@
1
+ """factorforge-validate CLI: convert wet-lab results into validation packages."""
2
+
3
+ import argparse
4
+ import hashlib
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from factorforge.validation import ValidationPackageGenerator
9
+ from factorforge.validation.package_generator import WetLabResult
10
+
11
+
12
+ def _sequence_hash(args: argparse.Namespace) -> str:
13
+ if args.sequence_hash:
14
+ if args.sequence_hash.startswith("sha256:"):
15
+ return args.sequence_hash
16
+ return f"sha256:{args.sequence_hash}"
17
+ if args.sequence:
18
+ return "sha256:" + hashlib.sha256(args.sequence.encode()).hexdigest()
19
+
20
+ print("Error: --sequence or --sequence-hash is required.", file=sys.stderr)
21
+ sys.exit(1)
22
+
23
+
24
+ def main() -> None:
25
+ parser = argparse.ArgumentParser(
26
+ prog="factorforge-validate",
27
+ description="Generate a structured validation package from wet-lab results.",
28
+ )
29
+ parser.add_argument(
30
+ "--construct-id",
31
+ required=True,
32
+ help="Construct ID (e.g. CF-20260525-143022)",
33
+ )
34
+ parser.add_argument(
35
+ "--version",
36
+ required=True,
37
+ help="FactorForge product version (e.g. 3.1.1)",
38
+ )
39
+ parser.add_argument("--host-profile", default="nbenthamiana")
40
+ parser.add_argument("--profile", default=None)
41
+ parser.add_argument("--sequence", default=None, help="CDS sequence (for hash only)")
42
+ parser.add_argument(
43
+ "--sequence-hash",
44
+ default=None,
45
+ help="Pre-computed sha256 hash (alternative to --sequence)",
46
+ )
47
+ parser.add_argument("--protein-name", required=True)
48
+ parser.add_argument("--host-organism", default="N. benthamiana")
49
+ parser.add_argument("--promoter", default=None)
50
+ parser.add_argument("--subcellular-targeting", default=None)
51
+ parser.add_argument("--expression-system", required=True)
52
+ parser.add_argument("--harvest-timepoint", default=None)
53
+ parser.add_argument(
54
+ "--native-control",
55
+ default=None,
56
+ choices=["Yes", "No", "Not applicable"],
57
+ )
58
+ parser.add_argument(
59
+ "--comparison",
60
+ required=True,
61
+ choices=["FactorForge better", "Equivalent", "Worse", "Not compared"],
62
+ )
63
+ parser.add_argument(
64
+ "--expression-level",
65
+ default=None,
66
+ choices=["High", "Medium", "Low", "Not detected", "Not measured"],
67
+ )
68
+ parser.add_argument("--notes", default=None)
69
+ parser.add_argument("--institution", default=None)
70
+ parser.add_argument("--no-public", action="store_true", help="Opt out of public listing")
71
+ parser.add_argument("--output-dir", default="validation_package", help="Output directory")
72
+
73
+ args = parser.parse_args()
74
+ result = WetLabResult(
75
+ construct_id=args.construct_id,
76
+ factorforge_version=args.version,
77
+ host_profile=args.host_profile,
78
+ profile=args.profile,
79
+ sequence_hash=_sequence_hash(args),
80
+ protein_name=args.protein_name,
81
+ host_organism=args.host_organism,
82
+ promoter=args.promoter,
83
+ subcellular_targeting=args.subcellular_targeting,
84
+ expression_system=args.expression_system,
85
+ harvest_timepoint=args.harvest_timepoint,
86
+ native_control=args.native_control,
87
+ comparison=args.comparison,
88
+ expression_level=args.expression_level,
89
+ notes=args.notes,
90
+ institution=args.institution,
91
+ public_listing=not args.no_public,
92
+ )
93
+
94
+ output = ValidationPackageGenerator(Path(args.output_dir)).generate(result)
95
+ print(f"Validation package generated: {output}")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
@@ -0,0 +1,184 @@
1
+ """Generate structured wet-lab feedback packages for FactorForge designs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+
12
+ @dataclass
13
+ class WetLabResult:
14
+ construct_id: str
15
+ factorforge_version: str
16
+ host_profile: str
17
+ profile: Optional[str]
18
+ sequence_hash: str
19
+
20
+ protein_name: str
21
+ host_organism: str
22
+ promoter: Optional[str]
23
+ subcellular_targeting: Optional[str]
24
+ expression_system: str
25
+ harvest_timepoint: Optional[str]
26
+ native_control: Optional[str]
27
+
28
+ comparison: str
29
+ expression_level: Optional[str]
30
+ notes: Optional[str]
31
+
32
+ institution: Optional[str]
33
+ public_listing: bool = True
34
+
35
+ submitted_at: Optional[str] = None
36
+
37
+
38
+ class ValidationPackageGenerator:
39
+ """Create validation package files from structured wet-lab result metadata."""
40
+
41
+ def __init__(self, output_dir: Path):
42
+ self.output_dir = Path(output_dir)
43
+
44
+ def generate(self, result: WetLabResult) -> Path:
45
+ """Write validation package files and return the output directory."""
46
+ self.output_dir.mkdir(parents=True, exist_ok=True)
47
+ submitted_at = result.submitted_at or self._utc_now()
48
+ metadata = self._build_metadata(result, submitted_at)
49
+
50
+ (self.output_dir / "validation_metadata.json").write_text(
51
+ json.dumps(metadata, indent=2),
52
+ encoding="utf-8",
53
+ )
54
+ (self.output_dir / "validation_summary.txt").write_text(
55
+ self._render_summary(result, submitted_at),
56
+ encoding="utf-8",
57
+ )
58
+ (self.output_dir / "issue_body.md").write_text(
59
+ self._render_issue_body(result, submitted_at),
60
+ encoding="utf-8",
61
+ )
62
+ return self.output_dir
63
+
64
+ def _build_metadata(self, result: WetLabResult, submitted_at: str) -> dict:
65
+ return {
66
+ "construct_id": result.construct_id,
67
+ "factorforge_version": result.factorforge_version,
68
+ "host_profile": result.host_profile,
69
+ "profile": result.profile,
70
+ "submitted_at": submitted_at,
71
+ "submitter": {
72
+ "institution": result.institution,
73
+ },
74
+ "experiment": {
75
+ "protein_name": result.protein_name,
76
+ "host_organism": result.host_organism,
77
+ "promoter": result.promoter,
78
+ "subcellular_targeting": result.subcellular_targeting,
79
+ "expression_system": result.expression_system,
80
+ "harvest_timepoint": result.harvest_timepoint,
81
+ "native_control": result.native_control,
82
+ },
83
+ "result": {
84
+ "comparison": result.comparison,
85
+ "expression_level": result.expression_level,
86
+ "notes": result.notes or "",
87
+ },
88
+ "consent": {
89
+ "public_listing": result.public_listing,
90
+ },
91
+ "sequence_hash": result.sequence_hash,
92
+ }
93
+
94
+ def _render_summary(self, result: WetLabResult, submitted_at: str) -> str:
95
+ public_listing = "Yes" if result.public_listing else "No"
96
+ return "\n".join(
97
+ [
98
+ "FactorForge Validation Report",
99
+ "==============================",
100
+ f"Construct ID : {result.construct_id}",
101
+ f"FF Version : {result.factorforge_version}",
102
+ f"Host Profile : {result.host_profile}",
103
+ f"Profile : {self._display(result.profile)}",
104
+ f"Submitted : {submitted_at}",
105
+ "",
106
+ "--- Experiment ---",
107
+ f"Protein : {result.protein_name}",
108
+ f"Host Organism : {result.host_organism}",
109
+ f"Promoter : {self._display(result.promoter)}",
110
+ f"Targeting : {self._display(result.subcellular_targeting)}",
111
+ f"Assay : {result.expression_system}",
112
+ f"Harvest : {self._display(result.harvest_timepoint)}",
113
+ f"Native Control : {self._display(result.native_control)}",
114
+ "",
115
+ "--- Result ---",
116
+ f"Comparison : {result.comparison}",
117
+ f"Expression : {self._display(result.expression_level)}",
118
+ f"Notes : {self._display(result.notes)}",
119
+ "",
120
+ "--- Sequence ---",
121
+ f"Sequence Hash : {result.sequence_hash}",
122
+ "",
123
+ "--- Consent ---",
124
+ f"Public Listing : {public_listing}",
125
+ f"Institution : {self._display(result.institution)}",
126
+ "",
127
+ ]
128
+ )
129
+
130
+ def _render_issue_body(self, result: WetLabResult, submitted_at: str) -> str:
131
+ checked = "x" if result.public_listing else " "
132
+ return "\n".join(
133
+ [
134
+ "## Wet-lab Validation Result",
135
+ "",
136
+ "| Field | Value |",
137
+ "|-------|-------|",
138
+ f"| **Construct ID** | {result.construct_id} |",
139
+ f"| **FactorForge Version** | {result.factorforge_version} |",
140
+ f"| **Host Profile** | {result.host_profile} |",
141
+ f"| **Profile** | {self._display(result.profile)} |",
142
+ f"| **Submitted** | {submitted_at} |",
143
+ "",
144
+ "## Experiment Details",
145
+ "",
146
+ "| Field | Value |",
147
+ "|-------|-------|",
148
+ f"| **Protein** | {result.protein_name} |",
149
+ f"| **Host Organism** | {result.host_organism} |",
150
+ f"| **Promoter** | {self._display(result.promoter)} |",
151
+ f"| **Targeting** | {self._display(result.subcellular_targeting)} |",
152
+ f"| **Assay** | {result.expression_system} |",
153
+ f"| **Harvest** | {self._display(result.harvest_timepoint)} |",
154
+ f"| **Native Control** | {self._display(result.native_control)} |",
155
+ "",
156
+ "## Result",
157
+ "",
158
+ "| Field | Value |",
159
+ "|-------|-------|",
160
+ f"| **Comparison** | {result.comparison} |",
161
+ f"| **Expression** | {self._display(result.expression_level)} |",
162
+ f"| **Notes** | {self._display(result.notes)} |",
163
+ "",
164
+ "## Sequence Integrity",
165
+ "",
166
+ f"Sequence Hash: {result.sequence_hash}",
167
+ "",
168
+ "_Raw sequence not included per privacy policy._",
169
+ "",
170
+ "## Consent",
171
+ "",
172
+ f"- [{checked}] Public listing approved",
173
+ f"- Institution: {self._display(result.institution)}",
174
+ "",
175
+ ]
176
+ )
177
+
178
+ @staticmethod
179
+ def _display(value: Optional[str]) -> str:
180
+ return value if value else "-"
181
+
182
+ @staticmethod
183
+ def _utc_now() -> str:
184
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.1.1
3
+ Version: 3.1.2
4
4
  Summary: FactorForge — open-source constraint-based CDS design engine by Eijex.
5
5
  Author-email: Eijex <eijex.lab@gmail.com>
6
6
  License: Apache License
@@ -220,6 +220,7 @@ License-File: LICENSE
220
220
  Requires-Dist: biopython>=1.81
221
221
  Requires-Dist: requests>=2.31
222
222
  Requires-Dist: click>=8.0
223
+ Requires-Dist: pydantic>=2.0
223
224
  Provides-Extra: ml
224
225
  Requires-Dist: torch>=2.0; extra == "ml"
225
226
  Requires-Dist: transformers>=4.35; extra == "ml"
@@ -237,7 +238,7 @@ Dynamic: license-file
237
238
 
238
239
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
239
240
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
240
- [![Version](https://img.shields.io/badge/version-3.1.1-green.svg)](https://github.com/eijex/factorforge-cds/releases)
241
+ [![Version](https://img.shields.io/badge/version-3.1.2-green.svg)](https://github.com/eijex/factorforge-cds/releases)
241
242
  [![PyPI](https://img.shields.io/pypi/v/factorforge-cds.svg)](https://pypi.org/project/factorforge-cds/)
242
243
  [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
243
244
 
@@ -277,7 +278,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
277
278
  ## Citing
278
279
 
279
280
  ```
280
- FactorForge v3.1.1 (2026). Open-source constraint-based CDS design engine.
281
+ FactorForge v3.1.2 (2026). Open-source constraint-based CDS design engine.
281
282
  Eijex. https://github.com/eijex/factorforge-cds
282
283
  ```
283
284
 
@@ -42,12 +42,17 @@ src/factorforge/engines/v3/inference/v2_adapter.py
42
42
  src/factorforge/ml/__init__.py
43
43
  src/factorforge/ml/feasibility.py
44
44
  src/factorforge/ml/metrics.py
45
+ src/factorforge/schemas/__init__.py
46
+ src/factorforge/schemas/design_package.py
45
47
  src/factorforge/utils/__init__.py
46
48
  src/factorforge/utils/construct_id.py
47
49
  src/factorforge/utils/exceptions.py
48
50
  src/factorforge/utils/restriction_sites.py
49
51
  src/factorforge/utils/sequence_validator.py
50
52
  src/factorforge/utils/validation.py
53
+ src/factorforge/validation/__init__.py
54
+ src/factorforge/validation/cli.py
55
+ src/factorforge/validation/package_generator.py
51
56
  src/factorforge_cds.egg-info/PKG-INFO
52
57
  src/factorforge_cds.egg-info/SOURCES.txt
53
58
  src/factorforge_cds.egg-info/dependency_links.txt
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  factorforge = factorforge.cli.main:main
3
+ factorforge-validate = factorforge.validation.cli:main
@@ -1,6 +1,7 @@
1
1
  biopython>=1.81
2
2
  requests>=2.31
3
3
  click>=8.0
4
+ pydantic>=2.0
4
5
 
5
6
  [dev]
6
7
  pytest>=7.0
File without changes