factorforge-cds 3.1.5__tar.gz → 3.1.7__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.5/src/factorforge_cds.egg-info → factorforge_cds-3.1.7}/PKG-INFO +16 -11
  2. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/README.md +10 -8
  3. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/pyproject.toml +7 -3
  4. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/__init__.py +1 -1
  5. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/cli/main.py +117 -1
  6. factorforge_cds-3.1.7/src/factorforge/data/ntabacum_codons.json +528 -0
  7. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/database.py +1 -4
  8. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/__init__.py +1 -1
  9. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/__init__.py +1 -1
  10. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/optimizer.py +15 -5
  11. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/pipeline.py +29 -14
  12. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/rules/domesticator.py +8 -3
  13. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/rules/reverse_translator.py +4 -1
  14. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/rules/rule_engine.py +8 -2
  15. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/scoring.py +33 -5
  16. factorforge_cds-3.1.7/src/factorforge/engines/profile/scoring_ml.py +208 -0
  17. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7/src/factorforge_cds.egg-info}/PKG-INFO +16 -11
  18. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge_cds.egg-info/SOURCES.txt +2 -0
  19. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge_cds.egg-info/requires.txt +4 -0
  20. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/LICENSE +0 -0
  21. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/setup.cfg +0 -0
  22. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/__main__.py +0 -0
  23. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/analysis/__init__.py +0 -0
  24. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/analysis/feasibility.py +0 -0
  25. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/analysis/metrics.py +0 -0
  26. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/cli/__init__.py +0 -0
  27. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/cli/legacy_cli.py +0 -0
  28. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/core/interfaces/__init__.py +0 -0
  29. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/core/interfaces/exporter.py +0 -0
  30. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/core/interfaces/optimizer.py +0 -0
  31. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/core/interfaces/validator.py +0 -0
  32. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/data/nbenthamiana_codons.json +0 -0
  33. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/data/nbenthamiana_golden_set.json +0 -0
  34. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/data/templates/high_expression.json +0 -0
  35. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/data/templates/standard_expression.json +0 -0
  36. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/data/wolffia_globosa_codons.json +0 -0
  37. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
  38. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/construct_builder.py +0 -0
  39. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/exporter.py +0 -0
  40. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/rules/__init__.py +0 -0
  41. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/utils.py +0 -0
  42. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/profile/validator.py +0 -0
  43. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/engines/registry.py +0 -0
  44. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/schemas/__init__.py +0 -0
  45. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/schemas/design_package.py +0 -0
  46. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/schemas/design_package.schema.json +0 -0
  47. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/__init__.py +0 -0
  48. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/construct_id.py +0 -0
  49. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/exceptions.py +0 -0
  50. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/restriction_sites.py +0 -0
  51. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/sequence_validator.py +0 -0
  52. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/utils/validation.py +0 -0
  53. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/validation/__init__.py +0 -0
  54. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/validation/cli.py +0 -0
  55. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge/validation/package_generator.py +0 -0
  56. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
  57. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
  58. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/src/factorforge_cds.egg-info/top_level.txt +0 -0
  59. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/tests/test_database.py +0 -0
  60. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/tests/test_legacy_cli.py +0 -0
  61. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/tests/test_restriction_sites.py +0 -0
  62. {factorforge_cds-3.1.5 → factorforge_cds-3.1.7}/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.5
3
+ Version: 3.1.7
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
- Project-URL: Homepage, https://factorforge-cds.vercel.app
7
+ Project-URL: Homepage, https://factorforge.eijex.com
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,20 +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.5-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)
37
41
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20407331.svg)](https://doi.org/10.5281/zenodo.20407331)
38
- [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
42
+ [![Web App](https://img.shields.io/badge/web-factorforge.eijex.com-brightgreen.svg)](https://factorforge.eijex.com)
39
43
 
40
- 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).
41
45
 
42
46
  **→ [Full Documentation](https://eijex.github.io/factorforge-cds/)**
43
47
 
@@ -50,7 +54,7 @@ pip install factorforge-cds
50
54
  factorforge optimize my_protein.fasta -o output.fasta
51
55
  ```
52
56
 
53
- Or use the **[web app](https://factorforge-cds.vercel.app)** — no installation required.
57
+ Or use the **[web app](https://factorforge.eijex.com)** — no installation required.
54
58
 
55
59
  ---
56
60
 
@@ -58,9 +62,10 @@ Or use the **[web app](https://factorforge-cds.vercel.app)** — no installation
58
62
 
59
63
  | Method | Description | Link |
60
64
  |--------|-------------|------|
61
- | **Web App** | No installation, demo & light use | [factorforge-cds.vercel.app](https://factorforge-cds.vercel.app) |
65
+ | **Web App** | No installation, demo & light use | [factorforge.eijex.com](https://factorforge.eijex.com) |
62
66
  | **CLI / Python** | Local use, batch processing, data privacy | `pip install factorforge-cds` |
63
67
  | **Docker** | Full web interface locally | `docker pull ghcr.io/eijex/factorforge-cds:latest` |
68
+ | **Eijex MCP** | AI agent access (Claude Code, Cursor) | [mcp.eijex.com](https://mcp.eijex.com) |
64
69
 
65
70
  ---
66
71
 
@@ -102,7 +107,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
102
107
  ## Citing
103
108
 
104
109
  ```
105
- FactorForge v3.1.5 (2026). Open-source constraint-based CDS design engine.
110
+ FactorForge v3.1.7 (2026). Open-source constraint-based CDS design engine.
106
111
  Eijex. https://github.com/eijex/factorforge-cds
107
112
  ```
108
113
 
@@ -132,4 +137,4 @@ GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
132
137
  - **Wet-lab Results** — [Submit via Google Form](https://docs.google.com/forms/d/e/1FAIpQLSeSx-wYvF6YwHhSPdLMl-L44frCugdm25X_eDz50OaqTD66qA/viewform?usp=header) (recommended) or [GitHub Issue](https://github.com/eijex/factorforge-cds/issues/new?template=wet_lab_result.yml)
133
138
  - **GitHub Issues** — bugs, features: [github.com/eijex/factorforge-cds/issues](https://github.com/eijex/factorforge-cds/issues)
134
139
  - **Email** — eijex.lab@gmail.com
135
- - **Web** — [factorforge-cds.vercel.app](https://factorforge-cds.vercel.app)
140
+ - **Web** — [factorforge.eijex.com](https://factorforge.eijex.com)
@@ -1,15 +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.5-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)
9
10
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20407331.svg)](https://doi.org/10.5281/zenodo.20407331)
10
- [![Web App](https://img.shields.io/badge/web-factorforge--cds.vercel.app-brightgreen.svg)](https://factorforge-cds.vercel.app)
11
+ [![Web App](https://img.shields.io/badge/web-factorforge.eijex.com-brightgreen.svg)](https://factorforge.eijex.com)
11
12
 
12
- 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).
13
14
 
14
15
  **→ [Full Documentation](https://eijex.github.io/factorforge-cds/)**
15
16
 
@@ -22,7 +23,7 @@ pip install factorforge-cds
22
23
  factorforge optimize my_protein.fasta -o output.fasta
23
24
  ```
24
25
 
25
- Or use the **[web app](https://factorforge-cds.vercel.app)** — no installation required.
26
+ Or use the **[web app](https://factorforge.eijex.com)** — no installation required.
26
27
 
27
28
  ---
28
29
 
@@ -30,9 +31,10 @@ Or use the **[web app](https://factorforge-cds.vercel.app)** — no installation
30
31
 
31
32
  | Method | Description | Link |
32
33
  |--------|-------------|------|
33
- | **Web App** | No installation, demo & light use | [factorforge-cds.vercel.app](https://factorforge-cds.vercel.app) |
34
+ | **Web App** | No installation, demo & light use | [factorforge.eijex.com](https://factorforge.eijex.com) |
34
35
  | **CLI / Python** | Local use, batch processing, data privacy | `pip install factorforge-cds` |
35
36
  | **Docker** | Full web interface locally | `docker pull ghcr.io/eijex/factorforge-cds:latest` |
37
+ | **Eijex MCP** | AI agent access (Claude Code, Cursor) | [mcp.eijex.com](https://mcp.eijex.com) |
36
38
 
37
39
  ---
38
40
 
@@ -74,7 +76,7 @@ FactorForge predictions are **in-silico only** and have not been experimentally
74
76
  ## Citing
75
77
 
76
78
  ```
77
- FactorForge v3.1.5 (2026). Open-source constraint-based CDS design engine.
79
+ FactorForge v3.1.7 (2026). Open-source constraint-based CDS design engine.
78
80
  Eijex. https://github.com/eijex/factorforge-cds
79
81
  ```
80
82
 
@@ -104,4 +106,4 @@ GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
104
106
  - **Wet-lab Results** — [Submit via Google Form](https://docs.google.com/forms/d/e/1FAIpQLSeSx-wYvF6YwHhSPdLMl-L44frCugdm25X_eDz50OaqTD66qA/viewform?usp=header) (recommended) or [GitHub Issue](https://github.com/eijex/factorforge-cds/issues/new?template=wet_lab_result.yml)
105
107
  - **GitHub Issues** — bugs, features: [github.com/eijex/factorforge-cds/issues](https://github.com/eijex/factorforge-cds/issues)
106
108
  - **Email** — eijex.lab@gmail.com
107
- - **Web** — [factorforge-cds.vercel.app](https://factorforge-cds.vercel.app)
109
+ - **Web** — [factorforge.eijex.com](https://factorforge.eijex.com)
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "factorforge-cds"
7
- version = "3.1.5"
7
+ version = "3.1.7"
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,9 +33,13 @@ 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
- Homepage = "https://factorforge-cds.vercel.app"
42
+ Homepage = "https://factorforge.eijex.com"
39
43
  Repository = "https://github.com/eijex/factorforge-cds"
40
44
  Issues = "https://github.com/eijex/factorforge-cds/issues"
41
45
 
@@ -4,7 +4,7 @@ FactorForge - Codon Optimization Platform
4
4
  profile: constraint-aware rule/profile engine
5
5
  """
6
6
 
7
- __version__ = "3.1.5"
7
+ __version__ = "3.1.7"
8
8
  __author__ = "Eijex"
9
9
 
10
10
  # Auto-register engines (safe when running from source tree)
@@ -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,