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.
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/PKG-INFO +4 -3
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/README.md +2 -2
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/pyproject.toml +3 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/__init__.py +1 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/__init__.py +1 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/__init__.py +1 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/optimizer.py +1 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/reverse_translator.py +4 -3
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/scoring.py +4 -2
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/__init__.py +1 -1
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/pipeline.py +1 -1
- factorforge_cds-3.1.2/src/factorforge/schemas/__init__.py +3 -0
- factorforge_cds-3.1.2/src/factorforge/schemas/design_package.py +88 -0
- factorforge_cds-3.1.2/src/factorforge/validation/__init__.py +3 -0
- factorforge_cds-3.1.2/src/factorforge/validation/cli.py +99 -0
- factorforge_cds-3.1.2/src/factorforge/validation/package_generator.py +184 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/PKG-INFO +4 -3
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/SOURCES.txt +5 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/entry_points.txt +1 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/requires.txt +1 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/LICENSE +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/setup.cfg +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/__main__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/legacy_cli.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/cli/main.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/exporter.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/optimizer.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/validator.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/database.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/ml/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/ml/plant_optimizer.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/registry.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v1_archived/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/codon_table_builder.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/construct_builder.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/exporter.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/pipeline.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/domesticator.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/rule_engine.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/utils.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/validator.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/explain.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/constrained_decoder.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/v2_adapter.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/metrics.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/modeling_bart_decoder.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/synonym_mask.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/tokenizer.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/feasibility.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/ml/metrics.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/__init__.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/construct_id.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/exceptions.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/restriction_sites.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/sequence_validator.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/utils/validation.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/top_level.txt +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_compare_v1_v2.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_database.py +0 -0
- {factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/tests/test_restriction_sites.py +0 -0
- {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.
|
|
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)
|
|
239
240
|
[](https://www.python.org/)
|
|
240
|
-
[](https://github.com/eijex/factorforge-cds/releases)
|
|
241
242
|
[](https://pypi.org/project/factorforge-cds/)
|
|
242
243
|
[](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.
|
|
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)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
|
-
[](https://github.com/eijex/factorforge-cds/releases)
|
|
8
8
|
[](https://pypi.org/project/factorforge-cds/)
|
|
9
9
|
[](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.
|
|
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.
|
|
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"]
|
|
@@ -562,9 +562,10 @@ class ReverseTranslator:
|
|
|
562
562
|
if len(all_codons) <= 1:
|
|
563
563
|
continue
|
|
564
564
|
|
|
565
|
-
# Select from bottom
|
|
566
|
-
|
|
567
|
-
|
|
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 —
|
|
67
|
-
|
|
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
|
|
|
@@ -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.
|
|
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,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,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.
|
|
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)
|
|
239
240
|
[](https://www.python.org/)
|
|
240
|
-
[](https://github.com/eijex/factorforge-cds/releases)
|
|
241
242
|
[](https://pypi.org/project/factorforge-cds/)
|
|
242
243
|
[](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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/optimizer.py
RENAMED
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/core/interfaces/validator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/ml/plant_optimizer.py
RENAMED
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v1_archived/__init__.py
RENAMED
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/codon_table_builder.py
RENAMED
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/construct_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/__init__.py
RENAMED
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/domesticator.py
RENAMED
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v2/rules/rule_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/inference/v2_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge/engines/v3/modeling_bart_decoder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{factorforge_cds-3.1.1 → factorforge_cds-3.1.2}/src/factorforge_cds.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|