factorforge-cds 3.1.4__tar.gz → 3.1.6__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 (62) hide show
  1. {factorforge_cds-3.1.4/src/factorforge_cds.egg-info → factorforge_cds-3.1.6}/PKG-INFO +12 -7
  2. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/README.md +7 -5
  3. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/pyproject.toml +9 -2
  4. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/analysis/metrics.py +3 -1
  6. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/cli/main.py +117 -1
  7. factorforge_cds-3.1.6/src/factorforge/data/nbenthamiana_codons.json +528 -0
  8. factorforge_cds-3.1.6/src/factorforge/data/nbenthamiana_golden_set.json +100 -0
  9. factorforge_cds-3.1.6/src/factorforge/data/ntabacum_codons.json +528 -0
  10. factorforge_cds-3.1.6/src/factorforge/data/templates/high_expression.json +34 -0
  11. factorforge_cds-3.1.6/src/factorforge/data/templates/standard_expression.json +34 -0
  12. factorforge_cds-3.1.6/src/factorforge/data/wolffia_globosa_codons.json +522 -0
  13. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/database.py +1 -4
  14. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/__init__.py +1 -1
  15. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/__init__.py +1 -1
  16. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/optimizer.py +15 -5
  17. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/pipeline.py +32 -16
  18. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/rules/domesticator.py +9 -6
  19. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/rules/reverse_translator.py +4 -1
  20. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/rules/rule_engine.py +8 -2
  21. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/scoring.py +33 -5
  22. factorforge_cds-3.1.6/src/factorforge/engines/profile/scoring_ml.py +208 -0
  23. factorforge_cds-3.1.6/src/factorforge/schemas/design_package.schema.json +373 -0
  24. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6/src/factorforge_cds.egg-info}/PKG-INFO +12 -7
  25. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge_cds.egg-info/SOURCES.txt +8 -0
  26. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge_cds.egg-info/requires.txt +4 -0
  27. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/LICENSE +0 -0
  28. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/setup.cfg +0 -0
  29. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/__main__.py +0 -0
  30. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/analysis/__init__.py +0 -0
  31. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/analysis/feasibility.py +0 -0
  32. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/cli/__init__.py +0 -0
  33. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/cli/legacy_cli.py +0 -0
  34. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/core/interfaces/__init__.py +0 -0
  35. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/core/interfaces/exporter.py +0 -0
  36. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/core/interfaces/optimizer.py +0 -0
  37. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/core/interfaces/validator.py +0 -0
  38. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
  39. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/construct_builder.py +0 -0
  40. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/exporter.py +0 -0
  41. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/rules/__init__.py +0 -0
  42. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/utils.py +0 -0
  43. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/profile/validator.py +0 -0
  44. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/engines/registry.py +0 -0
  45. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/schemas/__init__.py +0 -0
  46. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/schemas/design_package.py +0 -0
  47. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/__init__.py +0 -0
  48. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/construct_id.py +0 -0
  49. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/exceptions.py +0 -0
  50. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/restriction_sites.py +0 -0
  51. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/sequence_validator.py +0 -0
  52. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/utils/validation.py +0 -0
  53. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/validation/__init__.py +0 -0
  54. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/validation/cli.py +0 -0
  55. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge/validation/package_generator.py +0 -0
  56. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  57. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
  58. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  59. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/tests/test_database.py +0 -0
  60. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/tests/test_legacy_cli.py +0 -0
  61. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/tests/test_restriction_sites.py +0 -0
  62. {factorforge_cds-3.1.4 → factorforge_cds-3.1.6}/tests/test_sequence_validator.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: factorforge-cds
3
- Version: 3.1.4
3
+ Version: 3.1.6
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-Expression: AGPL-3.0-only
7
7
  Project-URL: Homepage, https://factorforge-cds.vercel.app
8
8
  Project-URL: Repository, https://github.com/eijex/factorforge-cds
9
9
  Project-URL: Issues, https://github.com/eijex/factorforge-cds/issues
10
- Keywords: codon optimization,CDS design,synthetic biology,bioinformatics,Nicotiana benthamiana,constraint optimization,dynamic programming
10
+ Keywords: codon optimization,CDS design,synthetic biology,bioinformatics,Nicotiana benthamiana,Nicotiana tabacum,Tobacco BY-2,constraint optimization,dynamic programming
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Programming Language :: Python :: 3
@@ -24,19 +24,24 @@ Requires-Dist: pytest>=7.0; extra == "dev"
24
24
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
25
  Requires-Dist: ruff>=0.1; extra == "dev"
26
26
  Requires-Dist: pyyaml>=6.0; extra == "dev"
27
+ Provides-Extra: ml
28
+ Requires-Dist: transformers>=4.40; extra == "ml"
29
+ Requires-Dist: torch>=2.0; extra == "ml"
27
30
  Dynamic: license-file
28
31
 
29
32
  # FactorForge
30
33
 
31
- **Open-source constraint-based CDS design engine for *Nicotiana benthamiana* expression workflows.**
34
+ **Open-source constraint-based CDS design engine for plant expression workflows, with initial focus on *Nicotiana benthamiana* and Tobacco BY-2.**
32
35
 
33
36
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
34
37
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
35
- [![Version](https://img.shields.io/badge/version-3.1.4-green.svg)](https://github.com/eijex/factorforge-cds/releases)
36
38
  [![PyPI](https://img.shields.io/pypi/v/factorforge-cds.svg)](https://pypi.org/project/factorforge-cds/)
39
+ [![CI](https://github.com/eijex/factorforge-cds/actions/workflows/ci.yml/badge.svg)](https://github.com/eijex/factorforge-cds/actions/workflows/ci.yml)
40
+ [![codecov](https://codecov.io/gh/eijex/factorforge-cds/branch/main/graph/badge.svg)](https://codecov.io/gh/eijex/factorforge-cds)
41
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20407331.svg)](https://doi.org/10.5281/zenodo.20407331)
37
42
  [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
38
43
 
39
- FactorForge optimizes protein sequences into *N. benthamiana*-compatible CDS by maximizing CAI, controlling GC content, eliminating PolyA signals, and producing MoClo/Golden Gate-ready constructs.
44
+ FactorForge optimizes protein sequences into host-compatible CDS by maximizing CAI, controlling GC content, eliminating PolyA signals, and producing MoClo/Golden Gate-ready constructs. Supports *N. benthamiana* (agroinfiltration) and Tobacco BY-2 (`--host by2`, bioreactor/cGMP workflows).
40
45
 
41
46
  **→ [Full Documentation](https://eijex.github.io/factorforge-cds/)**
42
47
 
@@ -86,7 +91,7 @@ FactorForge has gone through several implementation generations before the curre
86
91
  | **v2** — Rule-Based Engine | Internal → Production | Deterministic, constraint-aware design engine; became the foundation for the public release |
87
92
  | **v3-alpha** — ML Prototype | Archived | ML-based design attempt; performance was insufficient for production use; preserved under `archive/v3-ml-prototype/` |
88
93
  | **v3.0+** — Current release | Public | Open-source release of the matured v2 engine under `factorforge.engines.profile` |
89
- | **v4** — ML Research Track | Planned | Future ML re-attempt with improved data and architecture |
94
+ | **v3.7+** — ML Engine | Planned | ML-based design as `--engine ml`; added once sufficient wet-lab data is available |
90
95
 
91
96
  The `archive/` directory preserves all three earlier tracks for provenance. None are installed or exposed by the current package.
92
97
 
@@ -101,7 +106,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
101
106
  ## Citing
102
107
 
103
108
  ```
104
- FactorForge v3.1.4 (2026). Open-source constraint-based CDS design engine.
109
+ FactorForge v3.1.6 (2026). Open-source constraint-based CDS design engine.
105
110
  Eijex. https://github.com/eijex/factorforge-cds
106
111
  ```
107
112
 
@@ -1,14 +1,16 @@
1
1
  # FactorForge
2
2
 
3
- **Open-source constraint-based CDS design engine for *Nicotiana benthamiana* expression workflows.**
3
+ **Open-source constraint-based CDS design engine for plant expression workflows, with initial focus on *Nicotiana benthamiana* and Tobacco BY-2.**
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/)
7
- [![Version](https://img.shields.io/badge/version-3.1.4-green.svg)](https://github.com/eijex/factorforge-cds/releases)
8
7
  [![PyPI](https://img.shields.io/pypi/v/factorforge-cds.svg)](https://pypi.org/project/factorforge-cds/)
8
+ [![CI](https://github.com/eijex/factorforge-cds/actions/workflows/ci.yml/badge.svg)](https://github.com/eijex/factorforge-cds/actions/workflows/ci.yml)
9
+ [![codecov](https://codecov.io/gh/eijex/factorforge-cds/branch/main/graph/badge.svg)](https://codecov.io/gh/eijex/factorforge-cds)
10
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20407331.svg)](https://doi.org/10.5281/zenodo.20407331)
9
11
  [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
10
12
 
11
- FactorForge optimizes protein sequences into *N. benthamiana*-compatible CDS by maximizing CAI, controlling GC content, eliminating PolyA signals, and producing MoClo/Golden Gate-ready constructs.
13
+ FactorForge optimizes protein sequences into host-compatible CDS by maximizing CAI, controlling GC content, eliminating PolyA signals, and producing MoClo/Golden Gate-ready constructs. Supports *N. benthamiana* (agroinfiltration) and Tobacco BY-2 (`--host by2`, bioreactor/cGMP workflows).
12
14
 
13
15
  **→ [Full Documentation](https://eijex.github.io/factorforge-cds/)**
14
16
 
@@ -58,7 +60,7 @@ FactorForge has gone through several implementation generations before the curre
58
60
  | **v2** — Rule-Based Engine | Internal → Production | Deterministic, constraint-aware design engine; became the foundation for the public release |
59
61
  | **v3-alpha** — ML Prototype | Archived | ML-based design attempt; performance was insufficient for production use; preserved under `archive/v3-ml-prototype/` |
60
62
  | **v3.0+** — Current release | Public | Open-source release of the matured v2 engine under `factorforge.engines.profile` |
61
- | **v4** — ML Research Track | Planned | Future ML re-attempt with improved data and architecture |
63
+ | **v3.7+** — ML Engine | Planned | ML-based design as `--engine ml`; added once sufficient wet-lab data is available |
62
64
 
63
65
  The `archive/` directory preserves all three earlier tracks for provenance. None are installed or exposed by the current package.
64
66
 
@@ -73,7 +75,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
73
75
  ## Citing
74
76
 
75
77
  ```
76
- FactorForge v3.1.4 (2026). Open-source constraint-based CDS design engine.
78
+ FactorForge v3.1.6 (2026). Open-source constraint-based CDS design engine.
77
79
  Eijex. https://github.com/eijex/factorforge-cds
78
80
  ```
79
81
 
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "factorforge-cds"
7
- version = "3.1.4"
7
+ version = "3.1.6"
8
8
  description = "FactorForge - open-source constraint-based CDS design engine by Eijex."
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-only"
11
11
  license-files = ["LICENSE"]
12
12
  requires-python = ">=3.10"
13
13
  authors = [{ name = "Eijex", email = "eijex.lab@gmail.com" }]
14
- keywords = ["codon optimization", "CDS design", "synthetic biology", "bioinformatics", "Nicotiana benthamiana", "constraint optimization", "dynamic programming"]
14
+ keywords = ["codon optimization", "CDS design", "synthetic biology", "bioinformatics", "Nicotiana benthamiana", "Nicotiana tabacum", "Tobacco BY-2", "constraint optimization", "dynamic programming"]
15
15
  classifiers = [
16
16
  "Development Status :: 4 - Beta",
17
17
  "Intended Audience :: Science/Research",
@@ -33,6 +33,10 @@ dev = [
33
33
  "ruff>=0.1",
34
34
  "pyyaml>=6.0",
35
35
  ]
36
+ ml = [
37
+ "transformers>=4.40",
38
+ "torch>=2.0",
39
+ ]
36
40
 
37
41
  [project.urls]
38
42
  Homepage = "https://factorforge-cds.vercel.app"
@@ -46,6 +50,9 @@ factorforge-validate = "factorforge.validation.cli:main"
46
50
  [tool.setuptools.packages.find]
47
51
  where = ["src"]
48
52
 
53
+ [tool.setuptools.package-data]
54
+ "factorforge" = ["data/*.json", "data/templates/*.json", "schemas/*.json"]
55
+
49
56
  [tool.pytest.ini_options]
50
57
  testpaths = ["tests"]
51
58
  norecursedirs = ["archive"]
@@ -4,7 +4,7 @@ FactorForge - Codon Optimization Platform
4
4
  profile: constraint-aware rule/profile engine
5
5
  """
6
6
 
7
- __version__ = "3.1.4"
7
+ __version__ = "3.1.6"
8
8
  __author__ = "Eijex"
9
9
 
10
10
  # Auto-register engines (safe when running from source tree)
@@ -9,6 +9,8 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ from factorforge.engines.profile.utils import get_data_path
13
+
12
14
 
13
15
  STANDARD_GENETIC_CODE: dict[str, str] = {
14
16
  "TTT": "F",
@@ -90,7 +92,7 @@ class CodonUsageTable:
90
92
 
91
93
 
92
94
  def _default_codon_table_path() -> Path:
93
- return Path(__file__).resolve().parents[3] / "data" / "nbenthamiana_codons.json"
95
+ return get_data_path() / "nbenthamiana_codons.json"
94
96
 
95
97
 
96
98
  def load_codon_usage_table(path: Path | None = None) -> CodonUsageTable:
@@ -16,6 +16,8 @@ from factorforge import __version__
16
16
  from factorforge.engines.registry import EngineRegistry
17
17
  from factorforge.engines.profile.utils import parse_fasta_records
18
18
 
19
+ HOST_MAP = {"nbenthamiana": "nbenthamiana", "by2": "ntabacum"}
20
+
19
21
 
20
22
  def _configure_stdio() -> None:
21
23
  """Best-effort UTF-8 for Windows consoles."""
@@ -79,6 +81,49 @@ def _format_dp_fasta(sequence_id: str, dna_sequence: str, cai: float, gc: float)
79
81
  return f"{header}\n{_wrap_sequence(dna_sequence)}\n"
80
82
 
81
83
 
84
+ def _option_was_explicitly_set(option_name: str) -> bool:
85
+ """Return whether an option was provided on the command line."""
86
+ ctx = click.get_current_context(silent=True)
87
+ if ctx is None:
88
+ return False
89
+ get_parameter_source = getattr(ctx, "get_parameter_source", None)
90
+ if not callable(get_parameter_source):
91
+ return False
92
+ return get_parameter_source(option_name) == click.core.ParameterSource.COMMANDLINE
93
+
94
+
95
+ def _engine_option_was_explicitly_set() -> bool:
96
+ """Return whether --engine/-e was provided on the command line."""
97
+ return _option_was_explicitly_set("engine")
98
+
99
+
100
+ def _format_profile_fasta(sequence_id: str, profile: str, result) -> str:
101
+ """Format a profile optimization result as FASTA."""
102
+ cai = float(result.metrics.get("cai", 0.0))
103
+ gc = float(result.metrics.get("gc_percent", result.metrics.get("gc_content", 0.0)))
104
+ score = float(result.metrics.get("score", 0.0))
105
+ header = f">{sequence_id}|profile={profile}|cai={cai:.3f}|gc={gc:.2f}|score={score:.3f}"
106
+ return f"{header}\n{_wrap_sequence(result.sequence)}\n"
107
+
108
+
109
+ def _format_profile_comparison_table(profile_results) -> str:
110
+ """Format profile optimization metrics as a comparison table."""
111
+ divider = "─" * 45
112
+ lines = [
113
+ "Profile comparison results:",
114
+ divider,
115
+ f"{'Profile':<18}{'CAI':>7} {'GC%':>7} {'Score':>8}",
116
+ divider,
117
+ ]
118
+ for profile_name, result in profile_results:
119
+ cai = float(result.metrics.get("cai", 0.0))
120
+ gc = float(result.metrics.get("gc_percent", result.metrics.get("gc_content", 0.0)))
121
+ score = float(result.metrics.get("score", 0.0))
122
+ lines.append(f"{profile_name:<18}{cai:>7.3f} {gc:>7.2f} {score:>8.3f}")
123
+ lines.append(divider)
124
+ return "\n".join(lines)
125
+
126
+
82
127
  @click.group()
83
128
  @click.version_option(version=__version__)
84
129
  def cli():
@@ -106,6 +151,12 @@ def list_engines():
106
151
  type=click.Choice(["dp", "profile"], case_sensitive=False),
107
152
  help="Engine (dp, profile)",
108
153
  )
154
+ @click.option(
155
+ "--host",
156
+ default="nbenthamiana",
157
+ type=click.Choice(["nbenthamiana", "by2"], case_sensitive=False),
158
+ help="Expression host: nbenthamiana (default) or by2 (Tobacco BY-2 / N. tabacum)",
159
+ )
109
160
  @click.option("--profile", "-p", default="balanced", help="Optimization profile")
110
161
  @click.option(
111
162
  "--objective",
@@ -118,6 +169,13 @@ def list_engines():
118
169
  @click.option("--template", "construct_template", help="Construct template name")
119
170
  @click.option("--output", "-o", help="Output file")
120
171
  @click.option("--format", "output_format", default="fasta", help="Output format (fasta, genbank)")
172
+ @click.option(
173
+ "--compare-profiles",
174
+ help=(
175
+ "Comma-separated profiles to compare "
176
+ "(e.g. balanced,high_cai,gc_target). Implies --engine profile."
177
+ ),
178
+ )
121
179
  @click.option(
122
180
  "--scan-mode",
123
181
  default="full",
@@ -129,6 +187,7 @@ def list_engines():
129
187
  def optimize(
130
188
  input_file,
131
189
  engine,
190
+ host,
132
191
  profile,
133
192
  objective,
134
193
  gc_min,
@@ -136,11 +195,28 @@ def optimize(
136
195
  construct_template,
137
196
  output,
138
197
  output_format,
198
+ compare_profiles,
139
199
  scan_mode,
140
200
  scan_include,
141
201
  scan_exclude,
142
202
  ):
143
203
  """Optimize protein sequence"""
204
+ compare_profile_list = _parse_csv_option(compare_profiles)
205
+ engine = engine.lower()
206
+ host_value = host.lower()
207
+ internal_host = HOST_MAP[host_value]
208
+ host_was_explicit = _option_was_explicitly_set("host")
209
+
210
+ if host_was_explicit and engine == "dp" and _engine_option_was_explicitly_set():
211
+ raise click.UsageError("--host is only supported with --engine profile")
212
+ if host_was_explicit and engine == "dp" and internal_host != "nbenthamiana":
213
+ engine = "profile"
214
+
215
+ if compare_profile_list:
216
+ if engine == "dp" and _engine_option_was_explicitly_set():
217
+ raise click.UsageError("--compare-profiles cannot be used with --engine dp.")
218
+ engine = "profile"
219
+
144
220
  try:
145
221
  # Read file
146
222
  with open(input_file, encoding="utf-8") as f:
@@ -156,6 +232,38 @@ def optimize(
156
232
  if len(fasta_records) == 1:
157
233
  sequence = fasta_records[0][1]
158
234
 
235
+ if compare_profile_list:
236
+ if fasta_records is not None and len(fasta_records) > 1:
237
+ raise ValueError("Profile comparison requires a single input sequence.")
238
+ if construct_template:
239
+ raise ValueError("Profile comparison does not support --template mode.")
240
+ if output_format.lower() != "fasta":
241
+ raise ValueError("Profile comparison only supports FASTA output.")
242
+
243
+ optimizer = EngineRegistry.get("profile")
244
+ profile_results = []
245
+ for profile_name in compare_profile_list:
246
+ result = optimizer.optimize(
247
+ sequence,
248
+ profile=profile_name,
249
+ host=internal_host,
250
+ scan_mode=scan_mode,
251
+ scan_include=scan_include_list,
252
+ scan_exclude=scan_exclude_list,
253
+ )
254
+ profile_results.append((profile_name, result))
255
+
256
+ if output:
257
+ first_profile, first_result = profile_results[0]
258
+ sequence_id = Path(input_file).stem or "factorforge_profile"
259
+ fasta = _format_profile_fasta(sequence_id, first_profile, first_result)
260
+ with open(output, "w", encoding="utf-8") as f:
261
+ f.write(fasta)
262
+ click.echo(f"Saved to: {output}")
263
+
264
+ click.echo(_format_profile_comparison_table(profile_results))
265
+ return
266
+
159
267
  if fasta_records is not None and len(fasta_records) > 1:
160
268
  if engine == "dp":
161
269
  raise ValueError("Multi-FASTA input requires --engine profile.")
@@ -170,6 +278,7 @@ def optimize(
170
278
  results = optimizer.optimize_batch(
171
279
  payload,
172
280
  profile=profile,
281
+ host=internal_host,
173
282
  scan_mode=scan_mode,
174
283
  scan_include=scan_include_list,
175
284
  scan_exclude=scan_exclude_list,
@@ -179,6 +288,7 @@ def optimize(
179
288
  optimizer.optimize(
180
289
  seq,
181
290
  profile=profile,
291
+ host=internal_host,
182
292
  scan_mode=scan_mode,
183
293
  scan_include=scan_include_list,
184
294
  scan_exclude=scan_exclude_list,
@@ -246,9 +356,14 @@ def optimize(
246
356
  if engine == "profile" and construct_template:
247
357
  from factorforge.engines.profile.pipeline import OptimizationPipeline
248
358
 
249
- pipeline = OptimizationPipeline(profile=profile, construct_template=construct_template)
359
+ pipeline = OptimizationPipeline(
360
+ profile=profile,
361
+ construct_template=construct_template,
362
+ host=internal_host,
363
+ )
250
364
  result = pipeline.run(
251
365
  sequence,
366
+ host=internal_host,
252
367
  scan_mode=scan_mode,
253
368
  scan_include=scan_include_list,
254
369
  scan_exclude=scan_exclude_list,
@@ -278,6 +393,7 @@ def optimize(
278
393
  result = optimizer.optimize(
279
394
  sequence,
280
395
  profile=profile,
396
+ host=internal_host,
281
397
  scan_mode=scan_mode,
282
398
  scan_include=scan_include_list,
283
399
  scan_exclude=scan_exclude_list,