diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..b4e74a6
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "python.defaultInterpreterPath": "/Users/gonzalovidal/opt/anaconda3/envs/GLLDB_py310/bin/python",
+ "python.terminal.activateEnvironment": true,
+ "jupyter.interactiveWindow.creationMode": "perFile",
+ "jupyter.notebookFileRoot": "${workspaceFolder}"
+}
diff --git a/AGENTS_log.md b/AGENTS_log.md
new file mode 100644
index 0000000..c62ef9b
--- /dev/null
+++ b/AGENTS_log.md
@@ -0,0 +1,301 @@
+# BuildCompiler Agent Handoff Log
+
+Last updated: 2026-07-11
+
+This log captures the recent BuildCompiler work so future agents can continue
+without rediscovering the same repository and PUDU context.
+
+## Repository Mental Model
+
+BuildCompiler is a workflow orchestrator. Keep stages independently callable:
+
+- `index_collections`
+- `domestication`
+- `assembly_lvl1`
+- `assembly_lvl2`
+- `transformation`
+- `plating`
+- `full_build`
+
+Stage contracts should stay explicit:
+
+- Inputs: SBOL objects/documents or JSON payloads.
+- Outputs: SBOL, JSON, and protocol files.
+- Avoid hidden side effects except explicitly named output files.
+- Prefer deterministic JSON ordering and deterministic generated artifacts.
+
+## Environment
+
+Primary tested environment:
+
+```bash
+/Users/gonzalovidal/opt/anaconda3/bin/conda run -n GLLDB_py310 python ...
+```
+
+Useful commands:
+
+```bash
+/Users/gonzalovidal/opt/anaconda3/bin/conda run -n GLLDB_py310 python -m pytest tests/unit/adapters/pudu/test_transformation_json.py tests/unit/adapters/pudu/test_plating_json.py tests/test_buildcompiler_transformation.py
+```
+
+`opentrons_simulate` is available inside `GLLDB_py310`:
+
+```bash
+/Users/gonzalovidal/opt/anaconda3/bin/conda run -n GLLDB_py310 which opentrons_simulate
+```
+
+PUDU local source tree used for integration:
+
+```text
+/Users/gonzalovidal/Documents/GitHub/PUDU
+```
+
+When using PUDU from notebooks/scripts without installing it, prepend:
+
+```python
+sys.path.insert(0, "/Users/gonzalovidal/Documents/GitHub/PUDU/src")
+```
+
+## Recent BuildCompiler Changes
+
+### Transformation input normalization
+
+File:
+
+- `src/buildcompiler/buildcompiler.py`
+
+Added `_normalize_transformation_inputs()` so `BuildCompiler.transformation()`
+accepts:
+
+- BuildCompiler `Plasmid` objects produced by `assembly_lvl1`/`assembly_lvl2`.
+- Raw `sbol2.ComponentDefinition` plasmids.
+- Dict payloads with `plasmid` or `plasmid_definition`.
+
+Reason: the active `BuildCompiler.transformation()` method referenced this
+missing helper and failed when called with level-1 assembly products.
+
+Regression test:
+
+- `tests/test_buildcompiler_transformation.py::test_transformation_accepts_lvl1_assembly_products`
+
+### PUDU transformation adapter
+
+Files:
+
+- `src/buildcompiler/adapters/pudu/transformation_json.py`
+- `src/buildcompiler/adapters/pudu/__init__.py`
+- `tests/unit/adapters/pudu/test_transformation_json.py`
+
+Confirmed PUDU transformation spec shape:
+
+```json
+[
+ {
+ "Strain": "https://SBOL2Build.org/composite_strain_1/1",
+ "Chassis": "https://sbolcanvas.org/DH5alpha/1",
+ "Plasmids": ["https://SBOL2Build.org/composite_plasmid_1/1"]
+ }
+]
+```
+
+Added `plasmid_locations_to_pudu_json()` for PUDU's assembly-output location map:
+
+```json
+{
+ "https://SBOL2Build.org/composite_plasmid_1/1": ["A1"]
+}
+```
+
+This map is consumed by PUDU transformation protocol generation as
+`--plasmid-locations transformation_input.json`.
+
+Note: the notebook now prefers the location map produced by simulating PUDU's
+assembly protocol, because that is the real handoff in the PUDU workflow.
+
+### PUDU plating adapter
+
+Files:
+
+- `src/buildcompiler/adapters/pudu/plating_json.py`
+- `tests/unit/adapters/pudu/test_plating_json.py`
+
+Updated `plating_to_pudu_json()` to match PUDU directly. PUDU expects
+thermocycler well names as keys:
+
+```json
+{
+ "bacterium_locations": {
+ "A1": [
+ "composite_strain_1",
+ "Competent_Cell_DH5alpha",
+ "composite_plasmid_1",
+ "Media_1"
+ ]
+ }
+}
+```
+
+Removed the old nested `advanced_parameters` wrapper from the adapter output.
+Optional advanced parameters now pass through as top-level PUDU parameters.
+
+## Notebook Added
+
+New notebook:
+
+```text
+notebooks/buildcompiler_transformation_quickstart.ipynb
+```
+
+It demonstrates the full offline/PUDU chain:
+
+1. Load local SBOL collections:
+ - `tests/test_files/CIDARMoCloParts_collection.xml`
+ - `tests/test_files/CIDARMoCloPlasmidsKit_collection.xml`
+ - `tests/test_files/Enzyme_Implementations_collection.xml`
+ - `tests/test_files/impl_test_collection.xml`
+2. Load `tests/test_files/abstract_design.xml`.
+3. Run BuildCompiler `assembly_lvl1`.
+4. Run BuildCompiler `transformation`.
+5. Write BuildCompiler SBOL and JSON artifacts.
+6. Generate PUDU assembly protocol.
+7. Run `opentrons_simulate pudu_assembly_protocol.py`.
+8. Use generated `transformation_input.json`.
+9. Generate PUDU transformation protocol.
+10. Run `opentrons_simulate pudu_transformation_protocol.py`.
+11. Use generated `plating_input.json`.
+12. Generate PUDU plating protocol.
+13. Run `opentrons_simulate pudu_plating_protocol.py`.
+14. Produce final `plating_layout.json` and `plating_layout.xlsx`.
+
+Important implementation detail: SBOL files are written with
+`Document.writeString()` and `Path.write_text()` rather than `Document.write()`
+to avoid PySBOL2's online validator in offline notebook runs.
+
+## Generated Notebook Artifacts
+
+Directory:
+
+```text
+notebooks/results/buildcompiler_transformation_quickstart/
+```
+
+Current generated files include:
+
+- `assembly_lvl1_pudu_input.json`
+- `transformation_lvl1_products.xml`
+- `transformation_products.xml`
+- `transformation_summary.json`
+- `transformation_lvl1_pudu_input.json`
+- `pudu_assembly_protocol.py`
+- `pudu_assembly_protocol.simulate.log`
+- `transformation_input.json`
+- `pudu_transformation_protocol.py`
+- `pudu_transformation_protocol.simulate.log`
+- `plating_input.json`
+- `pudu_plating_protocol.py`
+- `pudu_plating_protocol.simulate.log`
+- `plating_layout.json`
+- `plating_layout.xlsx`
+- `Loop Assembly.xlsx`
+
+The simulation logs showed the expected handoff:
+
+- Assembly simulation generated `transformation_input.json`.
+- Transformation simulation generated `plating_input.json`.
+- Plating simulation generated `plating_layout.json` and `plating_layout.xlsx`.
+
+## PUDU Reference Points
+
+Local files inspected:
+
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/docs/guide/workflow.rst`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/docs/api/transformation.rst`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/src/pudu/generate_protocol.py`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/src/pudu/transformation.py`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/src/pudu/plating.py`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/workflow_example/transformation_spec.json`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/workflow_example/transformation_input.json`
+- `/Users/gonzalovidal/Documents/GitHub/PUDU/workflow_example/plating_input.json`
+
+PUDU's documented flow:
+
+```text
+assembly_input.json
+ -> pudu_assembly_protocol.py
+ -> opentrons_simulate
+ -> transformation_input.json
+ -> pudu_transformation_protocol.py
+ -> opentrons_simulate
+ -> plating_input.json
+ -> pudu_plating_protocol.py
+ -> opentrons_simulate
+ -> plating_layout.json / plating_layout.xlsx
+```
+
+PUDU Python API used in notebook:
+
+```python
+from pudu.generate_protocol import detect_protocol_type, generate_protocol
+```
+
+PUDU CLI equivalent:
+
+```bash
+python -m pudu.generate_protocol assembly_input.json -o assembly_protocol.py --protocol-type assembly
+opentrons_simulate assembly_protocol.py
+
+python -m pudu.generate_protocol transformation_spec.json -o transformation_protocol.py --protocol-type transformation --plasmid-locations transformation_input.json
+opentrons_simulate transformation_protocol.py
+
+python -m pudu.generate_protocol plating_input.json -o plating_protocol.py --protocol-type plating
+opentrons_simulate plating_protocol.py
+```
+
+## Validation Performed
+
+Notebook execution:
+
+```bash
+/Users/gonzalovidal/opt/anaconda3/bin/conda run -n GLLDB_py310 python -c "import json; ns={}; nb=json.load(open('notebooks/buildcompiler_transformation_quickstart.ipynb')); [exec(''.join(cell.get('source', [])), ns) for cell in nb['cells'] if cell.get('cell_type') == 'code']"
+```
+
+Adapter and regression tests:
+
+```bash
+/Users/gonzalovidal/opt/anaconda3/bin/conda run -n GLLDB_py310 python -m pytest tests/unit/adapters/pudu/test_transformation_json.py tests/unit/adapters/pudu/test_plating_json.py tests/test_buildcompiler_transformation.py
+```
+
+Last observed targeted results:
+
+- PUDU transformation/plating adapter tests: passed.
+- BuildCompiler transformation regression tests: passed.
+- Full notebook simulation chain: passed.
+
+## Current Worktree Notes
+
+At the time this log was written, expected modified/untracked files included:
+
+- `src/buildcompiler/adapters/pudu/__init__.py`
+- `src/buildcompiler/adapters/pudu/transformation_json.py`
+- `src/buildcompiler/adapters/pudu/plating_json.py`
+- `src/buildcompiler/buildcompiler.py`
+- `tests/test_buildcompiler_transformation.py`
+- `tests/unit/adapters/pudu/test_transformation_json.py`
+- `tests/unit/adapters/pudu/test_plating_json.py`
+- `notebooks/buildcompiler_transformation_quickstart.ipynb`
+- `notebooks/results/buildcompiler_transformation_quickstart/`
+- `AGENTS_log.md`
+
+Do not revert unrelated user changes. If these files differ from this log,
+inspect before editing.
+
+## Next Work Suggestions
+
+- Add a dedicated notebook/test for full-build output feeding the same PUDU
+ protocol chain.
+- Decide whether generated notebook results should be committed or moved to an
+ ignored artifact path.
+- Consider a higher-level BuildCompiler helper that returns all PUDU artifacts
+ for a stage chain without requiring notebook glue code.
+- Add optional dependency documentation for PUDU and Opentrons simulation.
+- Keep PUDU as optional unless the package is intentionally added as a dependency.
diff --git a/docs/Tutorials.rst b/docs/Tutorials.rst
index 9e25bb6..98d58a7 100644
--- a/docs/Tutorials.rst
+++ b/docs/Tutorials.rst
@@ -34,4 +34,55 @@ Create golden gate assembly plan object with all the parts, the acceptor backbon
assembly_plan = s2b.golden_gate_assembly_plan('tutorial_assembly_plan', [promoter, rbs, cds, terminator], backbone, 'BsaI', assembly_doc)
- composites = assembly_plan.run()
\ No newline at end of file
+ composites = assembly_plan.run()
+
+Full build level-2 PUDU artifact package
+----------------------------------------
+
+The legacy artifact-producing compiler can run a level-2 design and package all
+generated SBOL, JSON, and PUDU protocol inputs into a single zip file. If the
+level-2 design is missing level-1 region inputs, ``full_build`` attempts level-1
+assembly, falls back to domestication for missing parts, then retries the
+downstream assemblies.
+
+.. code:: python
+
+ from pathlib import Path
+
+ import sbol2
+
+ from buildcompiler.buildcompiler import BuildCompiler
+
+ test_files = Path("tests/test_files")
+ collection_docs = []
+ for filename in (
+ "CIDARMoCloParts_collection.xml",
+ "CIDARMoCloPlasmidsKit_collection.xml",
+ "Enzyme_Implementations_collection.xml",
+ "impl_test_collection.xml",
+ ):
+ doc = sbol2.Document()
+ doc.read(str(test_files / filename))
+ collection_docs.append(doc)
+
+ lvl2_design_doc = sbol2.Document()
+ lvl2_design_doc.read(str(test_files / "ExampleLvl2_design.xml"))
+
+ compiler = BuildCompiler.from_local_documents(
+ collection_docs,
+ design_doc=lvl2_design_doc,
+ )
+ result = compiler.full_build(
+ designs=lvl2_design_doc,
+ results_dir="results/full_build_lvl2_pudu",
+ overwrite=True,
+ )
+
+ print(result["zip_path"])
+
+The zip archive includes ``full_build_manifest.json`` plus PUDU assembly,
+transformation, and plating inputs/scripts such as
+``assembly_lvl2_pudu_assembly_input.json``,
+``assembly_lvl1_pudu_assembly_input.json``,
+``domestication_pudu_assembly_input.json``,
+``pudu_transformation_protocol.py``, and ``pudu_plating_protocol.py``.
diff --git a/notebooks/buildcompiler_transformation_quickstart.ipynb b/notebooks/buildcompiler_transformation_quickstart.ipynb
new file mode 100644
index 0000000..e0467ef
--- /dev/null
+++ b/notebooks/buildcompiler_transformation_quickstart.ipynb
@@ -0,0 +1,324 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# BuildCompiler Transformation Quickstart\n",
+ "\n",
+ "This notebook runs a local level-1 assembly from `tests/test_files/abstract_design.xml`, then uses the assembly product as the input for chemical transformation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Imports and Local Paths\n",
+ "\n",
+ "Run this notebook from the repository checkout after installing BuildCompiler with `python -m pip install -e .`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "import os\n",
+ "import subprocess\n",
+ "import sys\n",
+ "from pathlib import Path\n",
+ "\n",
+ "import sbol2\n",
+ "\n",
+ "from buildcompiler.abstract_translator import extract_toplevel_definition\n",
+ "from buildcompiler.adapters.pudu import plating_to_pudu_json, transformations_to_pudu_json, write_assembly_pudu_input_json\n",
+ "from buildcompiler.buildcompiler import BuildCompiler\n",
+ "\n",
+ "\n",
+ "def find_repo_root(start: Path) -> Path:\n",
+ " for path in [start, *start.parents]:\n",
+ " if (path / \"pyproject.toml\").exists() and (path / \"tests\" / \"test_files\").exists():\n",
+ " return path\n",
+ " raise RuntimeError(\"Could not find BuildCompiler repository root.\")\n",
+ "\n",
+ "\n",
+ "REPO_ROOT = find_repo_root(Path.cwd().resolve())\n",
+ "TEST_FILES = REPO_ROOT / \"tests\" / \"test_files\"\n",
+ "RESULTS_DIR = REPO_ROOT / \"notebooks\" / \"results\" / \"buildcompiler_transformation_quickstart\"\n",
+ "PUDU_REPO = REPO_ROOT.parent / \"PUDU\"\n",
+ "PUDU_SRC = PUDU_REPO / \"src\"\n",
+ "RESULTS_DIR.mkdir(parents=True, exist_ok=True)\n",
+ "\n",
+ "print(\"Repository:\", REPO_ROOT)\n",
+ "print(\"Results:\", RESULTS_DIR)\n",
+ "print(\"PUDU source:\", PUDU_SRC if PUDU_SRC.exists() else \"not found\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Load Offline SBOL Collections\n",
+ "\n",
+ "The compiler indexes local plasmids, backbones, and enzyme implementations from these fixture collections."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "collection_paths = [\n",
+ " TEST_FILES / \"CIDARMoCloParts_collection.xml\",\n",
+ " TEST_FILES / \"CIDARMoCloPlasmidsKit_collection.xml\",\n",
+ " TEST_FILES / \"Enzyme_Implementations_collection.xml\",\n",
+ " TEST_FILES / \"impl_test_collection.xml\",\n",
+ "]\n",
+ "\n",
+ "collection_docs = []\n",
+ "for path in collection_paths:\n",
+ " doc = sbol2.Document()\n",
+ " doc.read(str(path))\n",
+ " collection_docs.append(doc)\n",
+ " print(path.name, \"components=\", len(doc.componentDefinitions), \"implementations=\", len(doc.implementations))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Build Level-1 Assembly Products\n",
+ "\n",
+ "`abstract_design.xml` is resolved against the local collections and assembled into a level-1 plasmid product."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "design_doc = sbol2.Document()\n",
+ "design_doc.read(str(TEST_FILES / \"abstract_design.xml\"))\n",
+ "design = extract_toplevel_definition(design_doc)\n",
+ "\n",
+ "compiler = BuildCompiler.from_local_documents(collection_docs, design_doc=design_doc)\n",
+ "\n",
+ "assembly_doc = sbol2.Document()\n",
+ "assembly_routes, assembly_doc = compiler.assembly_lvl1(\n",
+ " [design],\n",
+ " final_doc=assembly_doc,\n",
+ " product_name=\"transformation_lvl1\",\n",
+ ")\n",
+ "assembly_products = assembly_routes[design.identity]\n",
+ "\n",
+ "lvl1_sbol_path = RESULTS_DIR / \"transformation_lvl1_products.xml\"\n",
+ "lvl1_pudu_path = RESULTS_DIR / \"assembly_lvl1_pudu_input.json\"\n",
+ "lvl1_sbol_path.write_text(assembly_doc.writeString(), encoding=\"utf-8\")\n",
+ "write_assembly_pudu_input_json(compiler.last_assembly_pudu_json, lvl1_pudu_path)\n",
+ "\n",
+ "print(\"Design:\", design.displayId)\n",
+ "print(\"Assembly products:\", [product.plasmid_definition.displayId for product in assembly_products])\n",
+ "print(\"Level-1 SBOL:\", lvl1_sbol_path)\n",
+ "print(\"Level-1 assembly PUDU input:\", lvl1_pudu_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4. Transform the Level-1 Product\n",
+ "\n",
+ "The transformation stage consumes the structured level-1 assembly output directly and writes PUDU-compatible transformation inputs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "CHASSIS_NAME = \"E_coli_DH5alpha\"\n",
+ "PUDU_CHASSIS_URI = \"https://sbolcanvas.org/DH5alpha/1\"\n",
+ "\n",
+ "transformation_result = compiler.transformation(\n",
+ " assembly_products,\n",
+ " chassis_name=CHASSIS_NAME,\n",
+ " transformation_doc=assembly_doc,\n",
+ ")\n",
+ "\n",
+ "transformation_sbol_path = RESULTS_DIR / \"transformation_products.xml\"\n",
+ "transformation_summary_path = RESULTS_DIR / \"transformation_summary.json\"\n",
+ "transformation_pudu_path = RESULTS_DIR / \"transformation_lvl1_pudu_input.json\"\n",
+ "\n",
+ "transformation_sbol_path.write_text(assembly_doc.writeString(), encoding=\"utf-8\")\n",
+ "transformation_summary_path.write_text(json.dumps(transformation_result, indent=2), encoding=\"utf-8\")\n",
+ "\n",
+ "transformation_pudu = transformations_to_pudu_json(\n",
+ " strain_identities=[artifact[\"transformed_strain_module\"] for artifact in transformation_result[\"sbol_artifacts\"]],\n",
+ " chassis_identities=[PUDU_CHASSIS_URI for _ in transformation_result[\"sbol_artifacts\"]],\n",
+ " plasmid_sets=[[product.plasmid_definition.identity] for product in assembly_products],\n",
+ ")\n",
+ "transformation_pudu_path.write_text(json.dumps(transformation_pudu, indent=2), encoding=\"utf-8\")\n",
+ "\n",
+ "print(\"Transformation inputs:\", transformation_result[\"inputs\"])\n",
+ "print(\"Transformation SBOL:\", transformation_sbol_path)\n",
+ "print(\"Transformation summary:\", transformation_summary_path)\n",
+ "print(\"Transformation PUDU spec:\", transformation_pudu_path)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 5. Inspect the Generated Records"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(json.dumps(transformation_pudu, indent=2))\n",
+ "print(\"Transformation SBOL artifacts:\")\n",
+ "for artifact in transformation_result[\"sbol_artifacts\"]:\n",
+ " print(json.dumps(artifact, indent=2))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 6. Run the PUDU Assembly to Transformation to Plating Chain\n",
+ "\n",
+ "PUDU uses `opentrons_simulate` as the handoff between stages: assembly simulation writes `transformation_input.json`, transformation simulation writes `plating_input.json`, and plating simulation writes the final plating layout files."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if not PUDU_SRC.exists():\n",
+ " raise RuntimeError(f\"PUDU source tree not found at {PUDU_SRC}\")\n",
+ "if str(PUDU_SRC) not in sys.path:\n",
+ " sys.path.insert(0, str(PUDU_SRC))\n",
+ "\n",
+ "from pudu.generate_protocol import detect_protocol_type, generate_protocol\n",
+ "\n",
+ "\n",
+ "def run_opentrons_simulation(protocol_path: Path) -> Path:\n",
+ " env = os.environ.copy()\n",
+ " existing_pythonpath = env.get(\"PYTHONPATH\")\n",
+ " env[\"PYTHONPATH\"] = str(PUDU_SRC) if not existing_pythonpath else f\"{PUDU_SRC}{os.pathsep}{existing_pythonpath}\"\n",
+ " log_path = protocol_path.with_suffix(\".simulate.log\")\n",
+ " result = subprocess.run(\n",
+ " [\"opentrons_simulate\", protocol_path.name],\n",
+ " cwd=RESULTS_DIR,\n",
+ " env=env,\n",
+ " text=True,\n",
+ " capture_output=True,\n",
+ " check=True,\n",
+ " )\n",
+ " log_path.write_text(result.stdout + result.stderr, encoding=\"utf-8\")\n",
+ " return log_path\n",
+ "\n",
+ "\n",
+ "assembly_pudu = json.loads(lvl1_pudu_path.read_text(encoding=\"utf-8\"))\n",
+ "protocol_type, assembly_subtype = detect_protocol_type(assembly_pudu)\n",
+ "assert (protocol_type, assembly_subtype) == (\"assembly\", \"SBOL\")\n",
+ "pudu_assembly_protocol_path = RESULTS_DIR / \"pudu_assembly_protocol.py\"\n",
+ "pudu_assembly_protocol_path.write_text(\n",
+ " generate_protocol(\n",
+ " protocol_data=assembly_pudu,\n",
+ " protocol_type=\"assembly\",\n",
+ " assembly_subtype=\"SBOL\",\n",
+ " metadata={\n",
+ " \"protocolName\": \"BuildCompiler Level-1 Assembly\",\n",
+ " \"author\": \"BuildCompiler\",\n",
+ " \"description\": \"PUDU assembly generated from BuildCompiler level-1 input.\",\n",
+ " },\n",
+ " ),\n",
+ " encoding=\"utf-8\",\n",
+ ")\n",
+ "assembly_log_path = run_opentrons_simulation(pudu_assembly_protocol_path)\n",
+ "transformation_locations_path = RESULTS_DIR / \"transformation_input.json\"\n",
+ "plasmid_locations = json.loads(transformation_locations_path.read_text(encoding=\"utf-8\"))\n",
+ "\n",
+ "protocol_type, assembly_subtype = detect_protocol_type(transformation_pudu)\n",
+ "assert (protocol_type, assembly_subtype) == (\"transformation\", None)\n",
+ "\n",
+ "pudu_protocol_path = RESULTS_DIR / \"pudu_transformation_protocol.py\"\n",
+ "pudu_protocol_code = generate_protocol(\n",
+ " protocol_data=transformation_pudu,\n",
+ " protocol_type=\"transformation\",\n",
+ " plasmid_locations=plasmid_locations,\n",
+ " metadata={\n",
+ " \"protocolName\": \"BuildCompiler Level-1 Transformation\",\n",
+ " \"author\": \"BuildCompiler\",\n",
+ " \"description\": \"PUDU heat-shock transformation generated from BuildCompiler level-1 output.\",\n",
+ " },\n",
+ ")\n",
+ "pudu_protocol_path.write_text(pudu_protocol_code, encoding=\"utf-8\")\n",
+ "transformation_log_path = run_opentrons_simulation(pudu_protocol_path)\n",
+ "plating_input_path = RESULTS_DIR / \"plating_input.json\"\n",
+ "plating_input = json.loads(plating_input_path.read_text(encoding=\"utf-8\"))\n",
+ "plating_input = plating_to_pudu_json(bacterium_locations=plating_input[\"bacterium_locations\"])\n",
+ "plating_input_path.write_text(json.dumps(plating_input, indent=2), encoding=\"utf-8\")\n",
+ "\n",
+ "protocol_type, assembly_subtype = detect_protocol_type(plating_input)\n",
+ "assert (protocol_type, assembly_subtype) == (\"plating\", None)\n",
+ "pudu_plating_protocol_path = RESULTS_DIR / \"pudu_plating_protocol.py\"\n",
+ "pudu_plating_protocol_path.write_text(\n",
+ " generate_protocol(\n",
+ " protocol_data=plating_input,\n",
+ " protocol_type=\"plating\",\n",
+ " metadata={\n",
+ " \"protocolName\": \"BuildCompiler Plating\",\n",
+ " \"author\": \"BuildCompiler\",\n",
+ " \"description\": \"PUDU plating generated from BuildCompiler transformation output.\",\n",
+ " },\n",
+ " ),\n",
+ " encoding=\"utf-8\",\n",
+ ")\n",
+ "plating_log_path = run_opentrons_simulation(pudu_plating_protocol_path)\n",
+ "\n",
+ "print(\"Generated PUDU assembly protocol:\", pudu_assembly_protocol_path)\n",
+ "print(\"Assembly simulation log:\", assembly_log_path)\n",
+ "print(\"Assembly output for transformation:\", transformation_locations_path)\n",
+ "print(\"Generated PUDU protocol:\", pudu_protocol_path)\n",
+ "print(\"Transformation simulation log:\", transformation_log_path)\n",
+ "print(\"Transformation output for plating:\", plating_input_path)\n",
+ "print(\"Generated PUDU plating protocol:\", pudu_plating_protocol_path)\n",
+ "print(\"Plating simulation log:\", plating_log_path)\n",
+ "print(\"Final plating layout JSON:\", RESULTS_DIR / \"plating_layout.json\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/full_build_workflow_examples.ipynb b/notebooks/full_build_workflow_examples.ipynb
new file mode 100644
index 0000000..f631556
--- /dev/null
+++ b/notebooks/full_build_workflow_examples.ipynb
@@ -0,0 +1,373 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Full Build Workflow Examples\n",
+ "\n",
+ "This notebook shows two ways to use the legacy artifact-producing `BuildCompiler.full_build(...)` workflow:\n",
+ "\n",
+ "1. A level-1 design that assembles successfully in one pass, then generates transformation and plating artifacts.\n",
+ "2. A level-2 design example that triggers the recovery stack: assembly level 2, assembly level 1, domestication, transformation, and plating.\n",
+ "\n",
+ "`full_build(...)` returns a dictionary with `zip_path` / `artifact_zip`. The zip contains the manifest and all generated PUDU input JSON/scripts."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Imports and paths"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pathlib import Path\n",
+ "import json\n",
+ "import zipfile\n",
+ "from unittest.mock import patch\n",
+ "\n",
+ "import sbol2\n",
+ "\n",
+ "from buildcompiler.abstract_translator import extract_toplevel_definition\n",
+ "from buildcompiler.buildcompiler import BuildCompiler\n",
+ "from buildcompiler.constants import ENGINEERED_PLASMID"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ROOT = Path.cwd()\n",
+ "if not (ROOT / \"tests\" / \"test_files\").exists():\n",
+ " ROOT = ROOT.parent\n",
+ "\n",
+ "TEST_FILES = ROOT / \"tests\" / \"test_files\"\n",
+ "RESULTS_DIR = ROOT / \"notebooks\" / \"results\" / \"full_build_workflow_examples\"\n",
+ "RESULTS_DIR.mkdir(parents=True, exist_ok=True)\n",
+ "\n",
+ "def load_sbol(filename):\n",
+ " doc = sbol2.Document()\n",
+ " doc.read(str(TEST_FILES / filename))\n",
+ " return doc\n",
+ "\n",
+ "def list_zip(zip_path):\n",
+ " with zipfile.ZipFile(zip_path, \"r\") as archive:\n",
+ " return sorted(archive.namelist())\n",
+ "\n",
+ "def show_manifest(result):\n",
+ " manifest = json.loads(Path(result[\"manifest_path\"]).read_text())\n",
+ " print(json.dumps({\n",
+ " \"zip_path\": manifest[\"zip_path\"],\n",
+ " \"domestication\": manifest[\"domestication\"],\n",
+ " \"assembly_lvl1\": manifest[\"assembly_lvl1\"],\n",
+ " \"assembly_lvl2\": manifest[\"assembly_lvl2\"],\n",
+ " \"transformation_count\": len(manifest[\"transformation\"][\"successful\"]),\n",
+ " \"plating_count\": len(manifest[\"plating\"][\"successful\"]),\n",
+ " }, indent=2))\n",
+ "\n",
+ "print(TEST_FILES)\n",
+ "print(RESULTS_DIR)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Load local SBOL inventory fixtures\n",
+ "\n",
+ "The first example uses the local SBOL fixtures in `tests/test_files` so it can run without SynBioHub credentials."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "collection_docs = [\n",
+ " load_sbol(\"CIDARMoCloParts_collection.xml\"),\n",
+ " load_sbol(\"CIDARMoCloPlasmidsKit_collection.xml\"),\n",
+ " load_sbol(\"Enzyme_Implementations_collection.xml\"),\n",
+ " load_sbol(\"impl_test_collection.xml\"),\n",
+ "]\n",
+ "\n",
+ "len(collection_docs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 1: level-1 design succeeds in one pass\n",
+ "\n",
+ "This runs a direct level-1 full build. Since the fixture inventory contains the required part plasmids and backbone, the build should go straight through assembly level 1, transformation, plating, manifest writing, and zip packaging. Level 2 is recorded as skipped because this input is a level-1 design."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lvl1_design_doc = load_sbol(\"abstract_design.xml\")\n",
+ "lvl1_design = extract_toplevel_definition(lvl1_design_doc)\n",
+ "\n",
+ "lvl1_compiler = BuildCompiler.from_local_documents(\n",
+ " collection_docs,\n",
+ " design_doc=lvl1_design_doc,\n",
+ ")\n",
+ "\n",
+ "lvl1_result = lvl1_compiler.full_build(\n",
+ " designs=[lvl1_design],\n",
+ " results_dir=RESULTS_DIR / \"lvl1_success\",\n",
+ " overwrite=True,\n",
+ ")\n",
+ "\n",
+ "print(lvl1_result[\"zip_path\"])\n",
+ "show_manifest(lvl1_result)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "list_zip(lvl1_result[\"zip_path\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example 2: level-2 design triggers all stages\n",
+ "\n",
+ "The recovery path depends on the available inventory. To make the stage sequence deterministic in a notebook, this example uses a tiny SBOL level-2 design and patches the stage methods to simulate the exact condition we want to demonstrate:\n",
+ "\n",
+ "1. The first level-2 assembly attempt fails because level-1 regions are missing.\n",
+ "2. The first level-1 assembly attempt fails because a part needs domestication.\n",
+ "3. Domestication produces a part plasmid and PUDU assembly payload.\n",
+ "4. Level-1 assembly retries successfully.\n",
+ "5. Level-2 assembly retries successfully.\n",
+ "6. Transformation and plating artifacts are produced and packaged.\n",
+ "\n",
+ "For a real run, remove the patches and provide SBOL collections/designs that naturally produce this dependency stack."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_plasmid(doc, display_id):\n",
+ " plasmid = sbol2.ComponentDefinition(display_id)\n",
+ " plasmid.roles = [ENGINEERED_PLASMID]\n",
+ " doc.add(plasmid)\n",
+ " return plasmid\n",
+ "\n",
+ "def make_lvl2_doc():\n",
+ " doc = sbol2.Document()\n",
+ " tu = sbol2.ComponentDefinition(\"demo_tu\")\n",
+ " lvl2 = sbol2.ComponentDefinition(\"demo_lvl2_design\")\n",
+ " doc.add(tu)\n",
+ " doc.add(lvl2)\n",
+ " comp = lvl2.components.create(\"demo_tu_component\")\n",
+ " comp.definition = tu.identity\n",
+ " return doc\n",
+ "\n",
+ "trigger_doc = sbol2.Document()\n",
+ "trigger_compiler = BuildCompiler.from_local_documents([], design_doc=trigger_doc)\n",
+ "\n",
+ "missing_part = make_plasmid(trigger_compiler.sbol_doc, \"missing_promoter\")\n",
+ "domesticated = make_plasmid(trigger_compiler.sbol_doc, \"domesticated_missing_promoter\")\n",
+ "lvl1_product = make_plasmid(trigger_compiler.sbol_doc, \"assembled_demo_tu\")\n",
+ "lvl2_product = make_plasmid(trigger_compiler.sbol_doc, \"assembled_demo_lvl2\")\n",
+ "lvl2_design_doc = make_lvl2_doc()\n",
+ "\n",
+ "stage_calls = []"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def fake_assembly_lvl2(*args, **kwargs):\n",
+ " stage_calls.append(\"assembly_lvl2\")\n",
+ " if stage_calls.count(\"assembly_lvl2\") == 1:\n",
+ " raise RuntimeError(\"level-2 input is missing level-1 regions\")\n",
+ " trigger_compiler.last_assembly_pudu_json_by_stage = {\n",
+ " \"assembly_lvl2\": [\n",
+ " {\n",
+ " \"Product\": lvl2_product.identity,\n",
+ " \"Backbone\": \"demo_lvl2_backbone\",\n",
+ " \"PartsList\": [lvl1_product.identity],\n",
+ " \"Restriction Enzyme\": \"BbsI\",\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " return [lvl2_product], trigger_compiler.sbol_doc\n",
+ "\n",
+ "def fake_assembly_lvl1(*args, **kwargs):\n",
+ " stage_calls.append(\"assembly_lvl1\")\n",
+ " if stage_calls.count(\"assembly_lvl1\") == 1:\n",
+ " raise RuntimeError(\"level-1 input is missing a domesticated part\")\n",
+ " trigger_compiler.last_assembly_pudu_json_by_stage = {\n",
+ " \"assembly_lvl1\": [\n",
+ " {\n",
+ " \"Product\": lvl1_product.identity,\n",
+ " \"Backbone\": \"demo_lvl1_backbone\",\n",
+ " \"PartsList\": [domesticated.identity],\n",
+ " \"Restriction Enzyme\": \"BsaI\",\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " return [lvl1_product], trigger_compiler.sbol_doc\n",
+ "\n",
+ "def fake_domestication(parts):\n",
+ " stage_calls.append(\"domestication\")\n",
+ " trigger_compiler.last_assembly_pudu_json_by_stage = {\n",
+ " \"domestication\": [\n",
+ " {\n",
+ " \"Product\": domesticated.identity,\n",
+ " \"Backbone\": \"demo_domestication_backbone\",\n",
+ " \"PartsList\": [missing_part.identity],\n",
+ " \"Restriction Enzyme\": \"BsaI\",\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " return [domesticated]\n",
+ "\n",
+ "def fake_transformation(products, chassis_name=\"E_coli_DH5alpha\", transformation_doc=None):\n",
+ " stage_calls.append(\"transformation\")\n",
+ " product_id = products[0].identity\n",
+ " return {\n",
+ " \"stage\": \"transformation\",\n",
+ " \"chassis\": chassis_name,\n",
+ " \"sbol_artifacts\": [\n",
+ " {\n",
+ " \"transformed_strain_module\": f\"{product_id}_strain\",\n",
+ " \"transformed_strain_implementation\": f\"{product_id}_strain_impl\",\n",
+ " }\n",
+ " ],\n",
+ " }\n",
+ "\n",
+ "def fake_plating(*args, **kwargs):\n",
+ " stage_calls.append(\"plating\")\n",
+ " return {\n",
+ " \"stage\": \"plating\",\n",
+ " \"json_intermediate\": {\n",
+ " \"plating_data\": {\n",
+ " \"bacterium_locations\": {\"A1\": \"demo_transformed_strain\"}\n",
+ " }\n",
+ " },\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "with patch.object(trigger_compiler, \"assembly_lvl2\", side_effect=fake_assembly_lvl2), \\\n",
+ " patch.object(trigger_compiler, \"assembly_lvl1\", side_effect=fake_assembly_lvl1), \\\n",
+ " patch.object(trigger_compiler, \"domestication\", side_effect=fake_domestication), \\\n",
+ " patch.object(trigger_compiler, \"transformation\", side_effect=fake_transformation), \\\n",
+ " patch.object(trigger_compiler, \"plating\", side_effect=fake_plating), \\\n",
+ " patch.object(trigger_compiler, \"_find_missing_parts_for_lvl1\", return_value=[{\"part\": missing_part}]):\n",
+ " lvl2_result = trigger_compiler.full_build(\n",
+ " designs=lvl2_design_doc,\n",
+ " results_dir=RESULTS_DIR / \"lvl2_triggers_all_stages\",\n",
+ " overwrite=True,\n",
+ " )\n",
+ "\n",
+ "print(stage_calls)\n",
+ "print(lvl2_result[\"zip_path\"])\n",
+ "show_manifest(lvl2_result)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "list_zip(lvl2_result[\"zip_path\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Expected PUDU artifacts\n",
+ "\n",
+ "For the level-2 staged example, the zip should include at least these PUDU-facing files:\n",
+ "\n",
+ "- `assembly_lvl2_pudu_assembly_input.json`\n",
+ "- `assembly_lvl1_pudu_assembly_input.json`\n",
+ "- `domestication_pudu_assembly_input.json`\n",
+ "- `pudu_transformation_protocol.py`\n",
+ "- `transformation_pudu_input.json`\n",
+ "- `transformation_plasmid_locations.json`\n",
+ "- `pudu_plating_protocol.py`\n",
+ "- `plating_pudu_input.json`\n",
+ "- `full_build_manifest.json`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "expected = {\n",
+ " \"assembly_lvl2_pudu_assembly_input.json\",\n",
+ " \"assembly_lvl1_pudu_assembly_input.json\",\n",
+ " \"domestication_pudu_assembly_input.json\",\n",
+ " \"pudu_transformation_protocol.py\",\n",
+ " \"transformation_pudu_input.json\",\n",
+ " \"transformation_plasmid_locations.json\",\n",
+ " \"pudu_plating_protocol.py\",\n",
+ " \"plating_pudu_input.json\",\n",
+ " \"full_build_manifest.json\",\n",
+ "}\n",
+ "\n",
+ "names = set(list_zip(lvl2_result[\"zip_path\"]))\n",
+ "missing = sorted(expected - names)\n",
+ "print(\"Missing expected artifacts:\", missing)\n",
+ "assert not missing"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/Loop Assembly.xlsx b/notebooks/results/buildcompiler_transformation_quickstart/Loop Assembly.xlsx
new file mode 100644
index 0000000..47c835a
Binary files /dev/null and b/notebooks/results/buildcompiler_transformation_quickstart/Loop Assembly.xlsx differ
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/assembly_lvl1_pudu_input.json b/notebooks/results/buildcompiler_transformation_quickstart/assembly_lvl1_pudu_input.json
new file mode 100644
index 0000000..5be9b9c
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/assembly_lvl1_pudu_input.json
@@ -0,0 +1,13 @@
+[
+ {
+ "Product": "http://buildcompiler.org/standard_GFP_transformation_lvl1/1",
+ "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1",
+ "PartsList": [
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1"
+ ],
+ "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1"
+ }
+]
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/plating_input.json b/notebooks/results/buildcompiler_transformation_quickstart/plating_input.json
new file mode 100644
index 0000000..3e59b45
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/plating_input.json
@@ -0,0 +1,16 @@
+{
+ "bacterium_locations": {
+ "A1": [
+ "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1",
+ "Competent_Cell_DH5alpha",
+ "standard_GFP_transformation_lvl1",
+ "Media_1"
+ ],
+ "B1": [
+ "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1",
+ "Competent_Cell_DH5alpha",
+ "standard_GFP_transformation_lvl1",
+ "Media_1"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.json b/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.json
new file mode 100644
index 0000000..03cf209
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.json
@@ -0,0 +1,36 @@
+{
+ "agar_plates": {
+ "plate_1": {
+ "dilution_1": {
+ "ratio": "1/10",
+ "wells": {
+ "A1": {
+ "construct": "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1, Competent_Cell_DH5alpha, standard_GFP_transformation_lvl1, Media_1",
+ "source_well": "A1",
+ "replicate": 1
+ },
+ "B1": {
+ "construct": "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1, Competent_Cell_DH5alpha, standard_GFP_transformation_lvl1, Media_1",
+ "source_well": "B1",
+ "replicate": 1
+ }
+ }
+ },
+ "dilution_2": {
+ "ratio": "1/100",
+ "wells": {
+ "A7": {
+ "construct": "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1, Competent_Cell_DH5alpha, standard_GFP_transformation_lvl1, Media_1",
+ "source_well": "A1",
+ "replicate": 1
+ },
+ "B7": {
+ "construct": "E_coli_DH5alpha_with_standard_GFP_transformation_lvl1, Competent_Cell_DH5alpha, standard_GFP_transformation_lvl1, Media_1",
+ "source_well": "B1",
+ "replicate": 1
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.xlsx b/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.xlsx
new file mode 100644
index 0000000..de02328
Binary files /dev/null and b/notebooks/results/buildcompiler_transformation_quickstart/plating_layout.xlsx differ
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.py b/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.py
new file mode 100644
index 0000000..0071dfa
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.py
@@ -0,0 +1,91 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+
+# Protocol data
+assembly_data = [
+ {
+ 'Product': 'http://buildcompiler.org/standard_GFP_transformation_lvl1/1',
+ 'Backbone': 'https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1',
+ 'PartsList': [
+ 'https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1',
+ 'https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1',
+ 'https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1',
+ 'https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1'
+ ],
+ 'Restriction Enzyme': 'https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1'
+ }
+]
+
+# Protocol metadata
+metadata = {
+ 'protocolName': 'BuildCompiler Level-1 Assembly',
+ 'author': 'BuildCompiler',
+ 'description': 'PUDU assembly generated from BuildCompiler level-1 input.',
+ 'apiLevel': '2.21'
+}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+ """Main protocol execution function"""
+
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
+
+
+
+# ======================================================================
+# PARAMETER REFERENCE — SBOLLoopAssembly
+#
+# To customize your protocol, add any of the parameters below
+# to the SBOLLoopAssembly() constructor call in run() above.
+# Example: protocol_instance = SBOLLoopAssembly(
+# assembly_data=assembly_data,
+# replicates=3,
+# initial_tip='B1',
+# )
+# ======================================================================
+#
+# [SBOLLoopAssembly]
+# assembly_data Optional = None
+# json_params Optional = None
+# assemblies Optional = None
+#
+# [BaseAssembly]
+# volume_total_reaction float = 20
+# volume_part float = 2
+# volume_restriction_enzyme float = 2
+# volume_t4_dna_ligase float = 4
+# volume_t4_dna_ligase_buffer float = 2
+# replicates int = 1
+# thermocycler_starting_well int = 0
+# thermocycler_labware str = nest_96_wellplate_100ul_pcr_full_skirt
+# temperature_module_labware str = opentrons_24_aluminumblock_nest_1.5ml_snapcap
+# temperature_module_position str = 1
+# tiprack_labware str = opentrons_96_tiprack_20ul
+# tiprack_positions Optional = None
+# pipette str = p20_single_gen2
+# pipette_position str = left
+# initial_tip Optional = None
+# aspiration_rate float = 0.5
+# dispense_rate float = 1
+# take_picture bool = False
+# take_video bool = False
+# water_testing bool = False
+# output_xlsx bool = True
+# protocol_name str =
+#
+# ----------------------------------------------------------------------
+# Full parameter descriptions:
+#
+# [SBOLLoopAssembly]
+# SBOL Loop Assembly - handles explicit assembly dictionaries from SBOL format.
+# Each assembly dictionary represents one specific construct to build.
+#
+# [BaseAssembly]
+# Abstract base class for Loop Assembly protocols.
+# Contains shared hardware setup, liquid handling, and tip management functionality.
+#
+# [ABC]
+# Helper class that provides a standard way to create an ABC using
+# inheritance.
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.simulate.log b/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.simulate.log
new file mode 100644
index 0000000..eefb7f3
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_assembly_protocol.simulate.log
@@ -0,0 +1,152 @@
+Parts and reagents in temp_module
+{'Deionized Water': 'A1', 'T4 DNA Ligase Buffer': 'B1', 'T4 DNA Ligase': 'C1', 'Restriction Enzyme BsaI': 'D1', 'DVK_AE': 'A2', 'pB0015_DE': 'B2', 'pB0034_BC': 'C2', 'pE0040_CD': 'D2', 'pJ23100_AB': 'A3'}
+Assembled parts in thermocycler_module
+{'Replicate: 1, Product: standard_GFP_transformation_lvl1': 'A1'}
+DNA list for transformation protocol
+['standard_GFP_transformation_lvl1_rep1']
+Protocol requires 9 tips (1 racks)
+Loaded Deionized Water at position A1
+Loaded T4 DNA Ligase Buffer at position B1
+Loaded T4 DNA Ligase at position C1
+Loaded Restriction Enzyme BsaI at position D1
+Loaded DVK_AE at position A2
+Loaded pB0015_DE at position B2
+Loaded pB0034_BC at position C2
+Loaded pE0040_CD at position D2
+Loaded pJ23100_AB at position A3
+Opening Thermocycler lid
+Setting Temperature Module temperature to 4.0 °C (rounded off to nearest integer)
+Setting Thermocycler well block temperature to 4.0 °C
+Picking up tip from A1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Aspirating 2.0 uL from A1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from B1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from B1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from C1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 4.0 ul
+ Aspirating 4.0 uL from C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 4.0 uL into C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 4.0 uL from C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 4.0 uL into C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 4.0 uL from C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 4.0 uL into C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 4.0 uL from C1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 4.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from D1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from D1 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from E1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from A2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from F1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from A3 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from G1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from C2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from H1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from D2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from A2 of Opentrons OT-2 96 Tip Rack 20 µL on slot 2
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Aspirating 2.0 uL from B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+ Dispensing 2.0 uL into B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 7.56 uL/sec
+Aspirating 2.0 uL from B2 of Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on Temperature Module GEN1 on slot 1 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Aspirating 20.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Aspirating 20.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Take out the reagents since the temperature module will be turn off
+Closing Thermocycler lid
+Setting Thermocycler lid temperature to 42.0 °C
+Deactivating Temperature Module
+Thermocycler starting 75 repetitions of cycle composed of the following steps: [{'temperature': 42, 'hold_time_minutes': 2}, {'temperature': 16, 'hold_time_minutes': 5}]
+Thermocycler starting 1 repetitions of cycle composed of the following steps: [{'temperature': 60, 'hold_time_minutes': 10}, {'temperature': 80, 'hold_time_minutes': 10}]
+Setting Thermocycler well block temperature to 4.0 °C
+
+======================================================================
+Generated transformation_input.json for transformation protocol
+ Products: 1
+======================================================================
+/Users/gonzalovidal/.opentrons/robot_settings.json not found. Loading defaults
+Deck calibration not found.
+/Users/gonzalovidal/.opentrons/deck_calibration.json not found. Loading defaults
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.py b/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.py
new file mode 100644
index 0000000..52707ce
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.py
@@ -0,0 +1,125 @@
+from pudu.plating import Plating
+from opentrons import protocol_api
+
+
+# Protocol data
+plating_data = {
+ 'bacterium_locations': {
+ 'A1': [
+ 'E_coli_DH5alpha_with_standard_GFP_transformation_lvl1',
+ 'Competent_Cell_DH5alpha',
+ 'standard_GFP_transformation_lvl1',
+ 'Media_1'
+ ],
+ 'B1': [
+ 'E_coli_DH5alpha_with_standard_GFP_transformation_lvl1',
+ 'Competent_Cell_DH5alpha',
+ 'standard_GFP_transformation_lvl1',
+ 'Media_1'
+ ]
+ }
+}
+
+# Protocol metadata
+metadata = {
+ 'protocolName': 'BuildCompiler Plating',
+ 'author': 'BuildCompiler',
+ 'description': 'PUDU plating generated from BuildCompiler transformation output.',
+ 'apiLevel': '2.21'
+}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+ """Main protocol execution function"""
+
+ protocol_instance = Plating(plating_data=plating_data)
+ protocol_instance.run(protocol)
+
+
+
+# ======================================================================
+# PARAMETER REFERENCE — Plating
+#
+# To customize your protocol, add any of the parameters below
+# to the Plating() constructor call in run() above.
+# Example: protocol_instance = Plating(
+# plating_data=plating_data,
+# replicates=3,
+# initial_tip='B1',
+# )
+# ======================================================================
+#
+# [Plating]
+# plating_data Optional = None
+# json_params Optional = None
+# volume_total_reaction float = 20
+# volume_bacteria_transfer float = 2
+# volume_colony float = 4
+# dilution_factor float = 10
+# volume_lb float = 10000
+# replicates int = 1
+# number_dilutions int = 2
+# max_colonies int = 192
+# thermocycler_starting_well int = 0
+# thermocycler_labware str = biorad_96_wellplate_200ul_pcr
+# small_tiprack str = opentrons_96_filtertiprack_20ul
+# small_tiprack_position str = 9
+# initial_small_tip Optional = None
+# large_tiprack str = opentrons_96_filtertiprack_200ul
+# large_tiprack_position str = 1
+# initial_large_tip Optional = None
+# small_pipette str = p20_single_gen2
+# small_pipette_position str = left
+# large_pipette str = p300_single_gen2
+# large_pipette_position str = right
+# dilution_plate str = nest_96_wellplate_100ul_pcr_full_skirt
+# dilution_plate_position1 str = 2
+# dilution_plate_position2 str = 3
+# agar_plate str = nest_96_wellplate_100ul_pcr_full_skirt
+# agar_plate_position1 str = 5
+# agar_plate_position2 str = 6
+# tube_rack str = opentrons_15_tuberack_falcon_15ml_conical
+# tube_rack_position str = 4
+# lb_tube_position int = 0
+# aspiration_rate float = 0.5
+# dispense_rate float = 1
+# bacterium_locations Optional = None
+# protocol_name str = plating_layout
+#
+# ----------------------------------------------------------------------
+# Full parameter descriptions:
+#
+# [Plating]
+# Automated serial-dilution and spot-plating protocol for the Opentrons OT-2.
+#
+# Takes transformed bacteria from a thermocycler plate, performs up to two
+# sequential 10× (or custom) dilutions in a dilution plate, and spots each
+# dilution onto an agar plate. Supports multiple replicates and automatically
+# distributes across two physical plates when colony counts exceed 96.
+#
+# After simulation, writes a JSON and an Excel file mapping each agar-plate
+# well to the construct name, dilution ratio, and replicate number.
+#
+# Attributes:
+# volume_total_reaction: Volume of bacteria loaded in each thermocycler
+# source well, in µL. Used for liquid-tracking display only.
+# volume_bacteria_transfer: Volume transferred from each source well into
+# the dilution well, in µL.
+# volume_colony: Volume spotted from each dilution well onto the agar
+# plate per replicate, in µL.
+# dilution_factor: Serial dilution factor applied at each step (e.g. 10
+# for a 1:10 dilution). The LB volume pre-loaded into each dilution
+# well is ``volume_bacteria_transfer × (dilution_factor − 1)``.
+# volume_lb: Total LB volume in the stock tube, in µL. Used for liquid
+# tracking on the Opentrons deck visualiser.
+# replicates: Number of agar spots per construct per dilution step.
+# number_dilutions: Number of serial dilution steps to perform (max 2).
+# number_constructs: Number of unique constructs derived from
+# ``bacterium_locations``.
+# total_colonies: Total agar wells that will be plated
+# (``number_constructs × number_dilutions × replicates``).
+# max_colonies: Hard cap on ``total_colonies``; raises ``ValueError``
+# if exceeded.
+# bacterium_locations: Dict mapping thermocycler well names to construct
+# identifiers, e.g. ``{'A1': 'GFP_construct', 'B1': ['RFP', 'v2']}``.
+# protocol_name: Base name for output files (JSON and Excel).
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.simulate.log b/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.simulate.log
new file mode 100644
index 0000000..1aa3a42
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_plating_protocol.simulate.log
@@ -0,0 +1,105 @@
+Using one plate: 2 wells per dilution fits in each half
+Using one plate: 2 wells per dilution fits in each half
+Setting Thermocycler well block temperature to 4.0 °C
+Opening Thermocycler lid
+
+=== Step 1: Distributing LB to dilution wells ===
+Picking up tip from A1 of Opentrons OT-2 96 Filter Tip Rack 200 µL on slot 1
+Conical calculation: 10000µL remaining = 61.7mm height
+Distributing to wells 1-4
+Distributing 18.0 from A1 of Opentrons 15 Tube Rack with Falcon 15 mL Conical on slot 4 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2
+ Transferring 18.0 from A1 of Opentrons 15 Tube Rack with Falcon 15 mL Conical on slot 4 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2
+ Aspirating 76.0 uL from A1 of Opentrons 15 Tube Rack with Falcon 15 mL Conical on slot 4 at 92.86 uL/sec
+ Dispensing 18.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 92.86 uL/sec
+ Dispensing 18.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 92.86 uL/sec
+ Dispensing 18.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 92.86 uL/sec
+ Dispensing 18.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 92.86 uL/sec
+ Blowing out into Trash Bin on slot 12
+Dropping tip into Trash Bin on slot 12
+
+=== Step 2: Transferring bacteria and plating ===
+
+Processing construct 1: ['E_coli_DH5alpha_with_standard_GFP_transformation_lvl1', 'Competent_Cell_DH5alpha', 'standard_GFP_transformation_lvl1', 'Media_1']
+Picking up tip from A1 of Opentrons OT-2 96 Filter Tip Rack 20 µL on slot 9
+Aspirating 2.0 uL from A1 of Bio-Rad 96 Well Plate 200 µL PCR on Thermocycler Module GEN1 on slot 7 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Mixing 5 times with a volume of 19.0 ul
+ Aspirating 19.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 2.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Mixing 5 times with a volume of 19.0 ul
+ Aspirating 19.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 4.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 4.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5
+Dropping tip into Trash Bin on slot 12
+Picking up tip from B1 of Opentrons OT-2 96 Filter Tip Rack 20 µL on slot 9
+Aspirating 4.0 uL from A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 4.0 uL into A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5 at 7.56 uL/sec
+Blowing out at A7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5
+Dropping tip into Trash Bin on slot 12
+
+Processing construct 2: ['E_coli_DH5alpha_with_standard_GFP_transformation_lvl1', 'Competent_Cell_DH5alpha', 'standard_GFP_transformation_lvl1', 'Media_1']
+Picking up tip from C1 of Opentrons OT-2 96 Filter Tip Rack 20 µL on slot 9
+Aspirating 2.0 uL from B1 of Bio-Rad 96 Well Plate 200 µL PCR on Thermocycler Module GEN1 on slot 7 at 3.78 uL/sec
+Dispensing 2.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Mixing 5 times with a volume of 19.0 ul
+ Aspirating 19.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 2.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 2.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Mixing 5 times with a volume of 19.0 ul
+ Aspirating 19.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 19.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 19.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 4.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 4.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5 at 7.56 uL/sec
+Blowing out at B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5
+Dropping tip into Trash Bin on slot 12
+Picking up tip from D1 of Opentrons OT-2 96 Filter Tip Rack 20 µL on slot 9
+Aspirating 4.0 uL from B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 4.0 uL into B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5 at 7.56 uL/sec
+Blowing out at B7 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 5
+Dropping tip into Trash Bin on slot 12
+
+=== Plating protocol complete ===
+Plated 2 constructs with 1 replicates
+Created a total of 4 colonies
+Generated plating_layout.json
+Generated plating_layout.xlsx
+/Users/gonzalovidal/.opentrons/robot_settings.json not found. Loading defaults
+Deck calibration not found.
+/Users/gonzalovidal/.opentrons/deck_calibration.json not found. Loading defaults
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.py b/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.py
new file mode 100644
index 0000000..a76c2f2
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.py
@@ -0,0 +1,217 @@
+from pudu.transformation import HeatShockTransformation
+from opentrons import protocol_api
+
+
+# Protocol data
+transformation_data = [
+ {
+ 'Strain': 'http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_transformation_lvl1/1',
+ 'Chassis': 'https://sbolcanvas.org/DH5alpha/1',
+ 'Plasmids': ['http://buildcompiler.org/standard_GFP_transformation_lvl1/1']
+ }
+]
+
+# Plasmid well locations from assembly protocol output
+plasmid_locations = {
+ 'http://buildcompiler.org/standard_GFP_transformation_lvl1/1': ['A1']
+}
+
+# Protocol metadata
+metadata = {
+ 'protocolName': 'BuildCompiler Level-1 Transformation',
+ 'author': 'BuildCompiler',
+ 'description': 'PUDU heat-shock transformation generated from BuildCompiler level-1 output.',
+ 'apiLevel': '2.21'
+}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+ """Main protocol execution function"""
+
+ protocol_instance = HeatShockTransformation(
+ transformation_data=transformation_data,
+ plasmid_locations=plasmid_locations
+ )
+ protocol_instance.run(protocol)
+
+
+
+# ======================================================================
+# PARAMETER REFERENCE — HeatShockTransformation
+#
+# To customize your protocol, add any of the parameters below
+# to the HeatShockTransformation() constructor call in run() above.
+# Example: protocol_instance = HeatShockTransformation(
+# transformation_data=transformation_data,
+# replicates=3,
+# initial_tip='B1',
+# )
+# ======================================================================
+#
+# [HeatShockTransformation]
+# transformation_data Optional = None
+# plasmid_locations Optional = None
+# json_params Optional = None
+# transfer_volume_dna float = 2
+# transfer_volume_competent_cell float = 20
+# tube_volume_competent_cell float = 100
+# transfer_volume_recovery_media float = 60
+# tube_volume_recovery_media float = 1200
+# cold_incubation1 Optional = None
+# heat_shock Optional = None
+# cold_incubation2 Optional = None
+# recovery_incubation Optional = None
+#
+# [Transformation]
+# volume_dna float = 20
+# replicates int = 2
+# thermocycler_starting_well int = 0
+# thermocycler_labware str = nest_96_wellplate_100ul_pcr_full_skirt
+# temperature_module_labware str = opentrons_24_aluminumblock_nest_1.5ml_snapcap
+# temperature_module_position str = 1
+# dna_plate str = nest_96_wellplate_100ul_pcr_full_skirt
+# dna_plate_position str = 2
+# use_dna_96plate bool = False
+# tiprack_p20_labware str = opentrons_96_tiprack_20ul
+# tiprack_p20_position str = 9
+# tiprack_p200_labware str = opentrons_96_filtertiprack_200ul
+# tiprack_p200_position str = 6
+# pipette_p20 str = p20_single_gen2
+# pipette_p20_position str = left
+# pipette_p300 str = p300_single_gen2
+# pipette_p300_position str = right
+# aspiration_rate float = 0.5
+# dispense_rate float = 1
+# initial_dna_well int = 0
+# water_testing bool = False
+# initial_tip_p20 Optional = None
+# initial_tip_p300 Optional = None
+# tube_rack_labware str = opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap
+# tube_rack_position str = 3
+#
+# ----------------------------------------------------------------------
+# Full parameter descriptions:
+#
+# [HeatShockTransformation]
+# Heat shock transformation protocol for the Opentrons OT-2.
+#
+# Automates the full heat shock transformation workflow: loading DNA and competent
+# cells into a thermocycler plate, running the heat shock cycle, adding recovery
+# media, and exporting a plating map for the next protocol step.
+#
+# Inherits all base parameters from Transformation. The attributes below are
+# specific to the heat shock transformation protocol.
+#
+# Attributes
+# ----------
+# transfer_volume_dna : float
+# Volume of DNA to transfer into each thermocycler well, in microliters.
+# By default, 2 microliters. Note: this is the volume actually pipetted per
+# reaction, distinct from volume_dna (the volume loaded into the source well).
+# transfer_volume_competent_cell : float
+# Volume of competent cells to transfer into each thermocycler well, in
+# microliters. By default, 20 microliters.
+# tube_volume_competent_cell : float
+# Total usable volume of competent cells per tube, in microliters. Used to
+# calculate how many reactions each tube can supply before switching to the
+# next tube. By default, 100 microliters.
+# transfer_volume_recovery_media : float
+# Volume of recovery media to add to each well after heat shock, in
+# microliters. By default, 60 microliters.
+# tube_volume_recovery_media : float
+# Total usable volume of recovery media per tube, in microliters. Used to
+# calculate how many wells each tube can supply. By default, 1200 microliters.
+# cold_incubation1 : dict
+# First cold incubation step (on ice before heat shock). A dict with keys
+# 'temperature' (°C) and 'hold_time_minutes'.
+# By default, {'temperature': 4, 'hold_time_minutes': 30}.
+# heat_shock : dict
+# Heat shock step. A dict with keys 'temperature' (°C) and 'hold_time_minutes'.
+# By default, {'temperature': 42, 'hold_time_minutes': 1}.
+# cold_incubation2 : dict
+# Second cold incubation immediately after heat shock. A dict with keys
+# 'temperature' (°C) and 'hold_time_minutes'.
+# By default, {'temperature': 4, 'hold_time_minutes': 2}.
+# recovery_incubation : dict
+# Recovery incubation after recovery media addition. A dict with keys
+# 'temperature' (°C) and 'hold_time_minutes'.
+# By default, {'temperature': 37, 'hold_time_minutes': 60}.
+#
+# [Transformation]
+# Base class for automated transformation protocols on the Opentrons OT-2.
+#
+# Handles loading transformation data, validating parameters, and providing
+# shared utilities used by all transformation subclasses. Subclasses implement
+# the specific thermocycler workflow (e.g. heat shock).
+#
+# Attributes
+# ----------
+# volume_dna : float
+# Volume of DNA loaded into each source well, in microliters. By default,
+# 20 microliters. We suggest 2 µL for extracted plasmid and 5 µL for PCR
+# products when setting transfer_volume_dna in the subclass.
+# replicates : int
+# Number of transformation replicates per strain per assembly location.
+# By default, 2.
+# thermocycler_starting_well : int
+# Zero-indexed starting well in the thermocycler plate. By default, 0 (well A1).
+# thermocycler_labware : str
+# Labware type for the thermocycler plate.
+# By default, 'nest_96_wellplate_100ul_pcr_full_skirt'.
+# temperature_module_labware : str
+# Labware type for the aluminum block on the temperature module.
+# By default, 'opentrons_24_aluminumblock_nest_1.5ml_snapcap'.
+# temperature_module_position : str
+# Deck slot for the temperature module. By default, '1'.
+# dna_plate : str
+# Labware type for the 96-well DNA source plate (used when use_dna_96plate=True).
+# By default, 'nest_96_wellplate_100ul_pcr_full_skirt'.
+# dna_plate_position : str
+# Deck slot for the 96-well DNA source plate. By default, '2'.
+# use_dna_96plate : bool
+# If True, DNA is sourced from a 96-well plate at fixed positions given by
+# plasmid_locations. Automatically set to True when plasmid_locations is
+# provided. By default, False.
+# tiprack_p20_labware : str
+# Labware type for the p20 tip rack. By default, 'opentrons_96_tiprack_20ul'.
+# tiprack_p20_position : str
+# Deck slot for the p20 tip rack. By default, '9'.
+# tiprack_p200_labware : str
+# Labware type for the p200 tip rack.
+# By default, 'opentrons_96_filtertiprack_200ul'.
+# tiprack_p200_position : str
+# Deck slot for the p200 tip rack. By default, '6'.
+# pipette_p20 : str
+# Pipette model for the p20 single-channel. By default, 'p20_single_gen2'.
+# pipette_p20_position : str
+# Mount for the p20 pipette ('left' or 'right'). By default, 'left'.
+# pipette_p300 : str
+# Pipette model for the p300 single-channel. By default, 'p300_single_gen2'.
+# pipette_p300_position : str
+# Mount for the p300 pipette ('left' or 'right'). By default, 'right'.
+# aspiration_rate : float
+# Relative aspiration speed as a fraction of the pipette's maximum flow
+# rate, where 1.0 is full speed and 0.5 is half speed. Lower values
+# reduce bubble formation. By default, 0.5.
+# dispense_rate : float
+# Relative dispense speed as a fraction of the pipette's maximum flow
+# rate, where 1.0 is full speed. By default, 1.0.
+# initial_dna_well : int
+# Zero-indexed starting well for DNA tubes on the aluminum block (used when
+# use_dna_96plate=False). By default, 0.
+# water_testing : bool
+# If True, uses water in place of competent cells and recovery media during
+# simulation/testing runs. By default, False.
+# initial_tip_p20 : str, optional
+# Well name of the first tip to use from the p20 tip rack (e.g. 'B1').
+# If None, starts from the first available tip. By default, None.
+# initial_tip_p300 : str, optional
+# Well name of the first tip to use from the p300 tip rack (e.g. 'C3').
+# If None, starts from the first available tip. By default, None.
+# tube_rack_labware : str
+# Labware type for the tube rack that holds competent cells and recovery
+# media. Moving these off the temperature module frees the entire aluminum
+# block for DNA plasmids, maximising unique constructs per run.
+# By default, 'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap'.
+# tube_rack_position : str
+# Deck slot for the tube rack. By default, '3'.
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.simulate.log b/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.simulate.log
new file mode 100644
index 0000000..949672b
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/pudu_transformation_protocol.simulate.log
@@ -0,0 +1,78 @@
+DNA constructs in DNA plate
+{'standard_GFP_transformation_lvl1': ['A1']}
+Competent cells and media in tube rack
+{'Competent Cell DH5alpha_1': 'A1', 'Media_1': 'B1'}
+Genetically modified organisms in thermocycler
+{'A1': ['E_coli_DH5alpha_with_standard_GFP_transformation_lvl1', 'Competent_Cell_DH5alpha', 'standard_GFP_transformation_lvl1', 'Media_1'], 'B1': ['E_coli_DH5alpha_with_standard_GFP_transformation_lvl1', 'Competent_Cell_DH5alpha', 'standard_GFP_transformation_lvl1', 'Media_1']}
+Simulation detected - enabling water testing mode
+Opening Thermocycler lid
+Distributing 20.0 from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+ Transferring 20.0 from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+ Picking up tip from A1 of Opentrons OT-2 96 Filter Tip Rack 200 µL on slot 6
+ Mixing 3 times with a volume of 50.0 ul
+ Aspirating 50.0 uL from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Dispensing 50.0 uL into A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Aspirating 50.0 uL from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Dispensing 50.0 uL into A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Aspirating 50.0 uL from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Dispensing 50.0 uL into A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Aspirating 40.0 uL from A1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Dispensing 20.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 92.86 uL/sec
+ Dispensing 20.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 92.86 uL/sec
+ Dropping tip into Trash Bin on slot 12
+Picking up tip from A1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 9
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Aspirating 20.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Aspirating 20.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Picking up tip from B1 of Opentrons OT-2 96 Tip Rack 20 µL on slot 9
+Mixing 3 times with a volume of 2.0 ul
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+ Dispensing 2.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 7.56 uL/sec
+Aspirating 2.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on slot 2 at 3.78 uL/sec
+Dispensing 2.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Blowing out at B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+Aspirating 20.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Aspirating 20.0 uL from B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Dispensing 20.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 7.56 uL/sec
+Touching tip
+Dropping tip into Trash Bin on slot 12
+Closing Thermocycler lid
+Opening Thermocycler lid
+Distributing 60.0 from B1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+ Transferring 60.0 from B1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 to A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7
+ Picking up tip from B1 of Opentrons OT-2 96 Filter Tip Rack 200 µL on slot 6
+ Aspirating 120.0 uL from B1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Air gap of 10 uL
+ Aspirating 10.0 uL from B1 of Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap on slot 3 at 92.86 uL/sec
+ Dispensing 70.0 uL into A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 92.86 uL/sec
+ Air gap of 10 uL
+ Aspirating 10.0 uL from A1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 92.86 uL/sec
+ Dispensing 70.0 uL into B1 of NEST 96 Well Plate 100 µL PCR Full Skirt on Thermocycler Module GEN1 on slot 7 at 92.86 uL/sec
+ Dropping tip into Trash Bin on slot 12
+Closing Thermocycler lid
+
+======================================================================
+Generated plating_input.json for next protocol
+ Bacteria locations: 2
+======================================================================
+/Users/gonzalovidal/.opentrons/robot_settings.json not found. Loading defaults
+Deck calibration not found.
+/Users/gonzalovidal/.opentrons/deck_calibration.json not found. Loading defaults
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/transformation_input.json b/notebooks/results/buildcompiler_transformation_quickstart/transformation_input.json
new file mode 100644
index 0000000..195ee0f
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/transformation_input.json
@@ -0,0 +1,5 @@
+{
+ "http://buildcompiler.org/standard_GFP_transformation_lvl1/1": [
+ "A1"
+ ]
+}
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_products.xml b/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_products.xml
new file mode 100644
index 0000000..17363de
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_products.xml
@@ -0,0 +1,550 @@
+
+
+
+
+
+
+ Ligation_Scar_A
+ 1
+
+
+
+
+
+ Ligation_Scar_B
+
+
+ 1
+
+
+
+
+
+ 1
+
+
+
+
+
+ Ligation_Scar_C
+
+
+
+
+
+
+ Ligation_Scar_D
+
+ 1
+
+
+
+
+
+ 1
+
+ Ligation_Scar_E
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ J23100
+
+
+
+
+
+
+ B0015_3_annotation
+ 1
+
+
+ B0015_3_location
+ 1
+ 793
+ 921
+
+
+
+
+
+
+
+
+
+ Ligation_Scar_D_annotation
+
+
+
+ 1
+ 792
+ 789
+
+ Ligation_Scar_D_location
+
+
+ 1
+
+
+
+
+
+
+
+
+
+ Ligation_Scar_C_B0034
+
+
+ 1
+
+
+ standard_GFP_transformation_lvl1
+
+
+
+
+
+
+ 1
+ 1
+ 4
+
+ Ligation_Scar_A_location
+
+
+ Ligation_Scar_A_annotation
+ 1
+
+
+
+
+
+
+
+ Ligation_Scar_E
+
+ 1
+
+
+
+
+
+
+ 1
+ Ligation_Scar_A
+
+
+
+
+
+
+ 1
+
+ B0015
+
+
+
+
+
+
+
+ Ligation_Scar_E_B0015
+
+ 1
+
+
+
+
+
+
+
+
+
+ 3152
+ 926
+ 1
+
+ dvk_backbone_core_5_location
+
+
+
+ dvk_backbone_core_5_annotation
+ 1
+
+
+
+
+
+ dvk_backbone_core
+
+ 1
+
+
+
+
+
+
+
+
+ Ligation_Scar_E_annotation
+
+
+
+ 922
+ Ligation_Scar_E_location
+ 925
+ 1
+
+
+
+ 1
+
+
+
+
+ Ligation_Scar_C_annotation
+
+
+
+ 1
+
+ 68
+ Ligation_Scar_C_location
+
+ 65
+
+
+ 1
+
+
+
+
+
+ B0034_Ligation_Scar_B
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 69
+ 1
+
+ 788
+ E0040m_gfp_3_location
+
+
+
+
+ E0040m_gfp_3_annotation
+ 1
+
+
+ standard_GFP_transformation_lvl1
+
+
+
+
+ 1
+ E0040m_gfp
+
+
+
+
+
+
+
+
+
+ 1
+ 43
+
+ Ligation_Scar_B_location
+ 40
+
+
+ 1
+
+
+ Ligation_Scar_B_annotation
+
+
+
+
+
+ 1
+
+
+ Ligation_Scar_B
+
+
+
+
+ B0034
+
+ 1
+
+
+
+
+
+
+ 1
+
+
+
+
+ dvk_backbone_core_Ligation_Scar_E
+
+
+
+
+
+
+ 1
+ B0034_3_location
+ 64
+
+
+ 44
+
+
+ B0034_3_annotation
+
+
+ 1
+
+
+
+
+
+
+ J23100_Ligation_Scar_A
+ 1
+
+
+
+
+
+
+
+ J23100_3_annotation
+ 1
+
+
+
+
+ J23100_3_location
+ 5
+
+
+ 39
+ 1
+
+
+
+
+
+
+
+
+ 1
+
+
+ Ligation_Scar_D_E0040m_gfp
+
+
+ 1
+
+
+
+
+ Ligation_Scar_B_J23100
+
+ 1
+
+
+
+
+
+ 1
+
+
+ B0015_Ligation_Scar_D
+
+
+
+
+
+
+
+
+
+ 1
+ Ligation_Scar_C
+
+
+
+
+
+ Ligation_Scar_D
+ 1
+
+
+
+
+
+
+ 1
+
+ E0040m_gfp_Ligation_Scar_C
+
+
+
+
+
+
+
+
+
+ 1
+ Ligation_Scar_A_sequence
+ GGAG
+
+
+
+
+ 1
+ Ligation_Scar_B_sequence
+ TACT
+
+
+
+ 1
+
+ Ligation_Scar_C_sequence
+ AATG
+
+
+
+
+ Ligation_Scar_D_sequence
+ 1
+ AGGT
+
+
+
+
+ Ligation_Scar_E_sequence
+ 1
+ GCTT
+
+
+
+ GGAGTTGACGGCTAGCTCAGTCCTAGGTACAGTGCTAGCTACTAGAGAAAGAGGAGAAATACTAAATGATGCGTAAAGGAGAAGAACTTTTCACTGGAGTTGTCCCAATTCTTGTTGAATTAGATGGTGATGTTAATGGGCACAAATTTTCTGTCAGTGGAGAGGGTGAAGGTGATGCAACATACGGAAAACTTACCCTTAAATTTATTTGCACTACTGGAAAACTACCTGTTCCATGGCCAACACTTGTCACTACTTTCGGTTATGGTGTTCAATGCTTTGCGAGATACCCAGATCATATGAAACAGCATGACTTTTTCAAGAGTGCCATGCCCGAAGGTTATGTACAGGAAAGAACTATATTTTTCAAAGATGACGGGAACTACAAGACACGTGCTGAAGTCAAGTTTGAAGGTGATACCCTTGTTAATAGAATCGAGTTAAAAGGTATTGATTTTAAAGAAGATGGAAACATTCTTGGACACAAATTGGAATACAACTATAACTCACACAATGTATACATCATGGCAGACAAACAAAAGAATGGAATCAAAGTTAACTTCAAAATTAGACACAACATTGAAGATGGAAGCGTTCAACTAGCAGACCATTATCAACAAAATACTCCAATTGGCGATGGCCCTGTCCTTTTACCAGACAACCATTACCTGTCCACACAATCTGCCCTTTCGAAAGATCCCAACGAAAAGAGAGATCACATGGTCCTTCTTGAGTTTGTAACAGCTGCTGGGATTACACATGGCATGGATGAACTATACAAATAATAAAGGTCCAGGCATCAAATAAAACGAAAGGCTCAGTCGAAAGACTGGGCCTTTCGTTTTATCTGTTGTTTGTCGGTGAACGCTCTCTACTAGAGTCACACTGGCTCACCTTCGGGTGGGCCTTTCTGCGTTTATAGCTTatgtcttctactagtagcggccgctgcagtccggcaaaaaagggcaaggtgtcaccaccctgccctttttctttaaaaccgaaaagattacttcgcgttatgcaggcttcctcgctcactgactcgctgcgctcggtcgttcggctgcggcgagcggtatcagctcactcaaaggcggtaatacggttatccacagaatcaggggataacgcaggaaagaacatgtgagcaaaaggccagcaaaaggccaggaaccgtaaaaaggccgcgttgctggcgtttttccacaggctccgcccccctgacgagcatcacaaaaatcgacgctcaagtcagaggtggcgaaacccgacaggactataaagataccaggcgtttccccctggaagctccctcgtgcgctctcctgttccgaccctgccgcttaccggatacctgtccgcctttctcccttcgggaagcgtggcgctttctcatagctcacgctgtaggtatctcagttcggtgtaggtcgttcgctccaagctgggctgtgtgcacgaaccccccgttcagcccgaccgctgcgccttatccggtaactatcgtcttgagtccaacccggtaagacacgacttatcgccactggcagcagccactggtaacaggattagcagagcgaggtatgtaggcggtgctacagagttcttgaagtggtggcctaactacggctacactagaagaacagtatttggtatctgcgctctgctgaagccagttaccttcggaaaaagagttggtagctcttgatccggcaaacaaaccaccgctggtagcggtggtttttttgtttgcaagcagcagattacgcgcagaaaaaaaggatctcaagaagatcctttgatcttttctacggggtctgacgctcagtggaacgaaaactcacgttaagggattttggtcatgagattatcaaaaaggatcttcacctagatccttttaaattaaaaatgaagttttaaatcaatctaaagtatatatgagtaaacttggtctgacagctcgagtcccgtcaagtcagcgtaatgctctgccagtgttacaaccaattaaccaattctgattagaaaaactcatcgagcatcaaatgaaactgcaatttattcatatcaggattatcaataccatatttttgaaaaagccgtttctgtaatgaaggagaaaactcaccgaggcagttccataggatggcaagatcctggtatcggtctgcgattccgactcgtccaacatcaatacaacctattaatttcccctcgtcaaaaataaggttatcaagtgagaaatcaccatgagtgacgactgaatccggtgagaatggcaaaagcttatgcatttctttccagacttgttcaacaggccagccattacgctcgtcatcaaaatcactcgcatcaaccaaaccgttattcattcgtgattgcgcctgagcgagacgaaatacgcgatcgctgttaaaaggacaattacaaacaggaatcgaatgcaaccggcgcaggaacactgccagcgcatcaacaatattttcacctgaatcaggatattcttctaatacctggaatgctgttttcccggggatcgcagtggtgagtaaccatgcatcatcaggagtacggataaaatgcttgatggtcggaagaggcataaattccgtcagccagtttagtctgaccatctcatctgtaacatcattggcaacgctacctttgccatgtttcagaaacaactctggcgcatcgggcttcccatacaatcgatagattgtcgcacctgattgcccgacattatcgcgagcccatttatacccatataaatcagcatccatgttggaatttaatcgcggcctggagcaagacgtttcccgttgaatatggctcataacaccccttgtattactgtttatgtaagcagacagttttattgttcatgatgatatatttttatcttgtgcaatgtaacatcagagattttgagacacaacgtggctttgttgaataaatcgaacttttgctgagttgaaggatcagctcgagtgccacctgacgtctaagaaaccattattatcatgacattaacctataaaaataggcgtatcacgaggcagaatttcagataaaaaaaatccttagctttcgctaaggatgatttctggaattcgcggccgcttctagagactagtggaagacat
+
+ standard_GFP_transformation_lvl1_seq
+ 1
+
+
+ 1
+ standard_GFP_transformation_lvl1_impl
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ pB0015_DE_impl
+
+
+
+ DNA Assembly
+
+
+ pE0040_CD_impl
+
+ 1
+
+
+
+
+
+
+
+
+ T4_Ligase
+ 1
+
+
+
+
+
+
+
+ pB0034_BC_impl
+ 1
+
+
+
+
+
+
+
+
+
+ 1
+ DVK_AE_impl
+
+
+
+
+ 1
+
+
+ BsaI_enzyme
+
+
+
+
+
+
+
+
+ 1
+
+ assemble_
+
+
+ standard_GFP_transformation_lvl1_assembly
+
+
+
+ 1
+
+ pJ23100_AB_impl
+
+
+
+
+
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_pudu_input.json b/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_pudu_input.json
new file mode 100644
index 0000000..67eaa63
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/transformation_lvl1_pudu_input.json
@@ -0,0 +1,9 @@
+[
+ {
+ "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_transformation_lvl1/1",
+ "Chassis": "https://sbolcanvas.org/DH5alpha/1",
+ "Plasmids": [
+ "http://buildcompiler.org/standard_GFP_transformation_lvl1/1"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/transformation_products.xml b/notebooks/results/buildcompiler_transformation_quickstart/transformation_products.xml
new file mode 100644
index 0000000..ccb41eb
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/transformation_products.xml
@@ -0,0 +1,619 @@
+
+
+
+
+
+
+ Ligation_Scar_A
+ 1
+
+
+
+
+
+ Ligation_Scar_B
+
+
+ 1
+
+
+
+
+
+ 1
+
+
+
+
+
+ Ligation_Scar_C
+
+
+
+
+
+
+ Ligation_Scar_D
+
+ 1
+
+
+
+
+
+ 1
+
+ Ligation_Scar_E
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ J23100
+
+
+
+
+
+
+ B0015_3_annotation
+ 1
+
+
+ B0015_3_location
+ 1
+ 793
+ 921
+
+
+
+
+
+
+
+
+
+ Ligation_Scar_D_annotation
+
+
+
+ 1
+ 792
+ 789
+
+ Ligation_Scar_D_location
+
+
+ 1
+
+
+
+
+
+
+
+
+
+ Ligation_Scar_C_B0034
+
+
+ 1
+
+
+ standard_GFP_transformation_lvl1
+
+
+
+
+
+
+ 1
+ 1
+ 4
+
+ Ligation_Scar_A_location
+
+
+ Ligation_Scar_A_annotation
+ 1
+
+
+
+
+
+
+
+ Ligation_Scar_E
+
+ 1
+
+
+
+
+
+
+ 1
+ Ligation_Scar_A
+
+
+
+
+
+
+ 1
+
+ B0015
+
+
+
+
+
+
+
+ Ligation_Scar_E_B0015
+
+ 1
+
+
+
+
+
+
+
+
+
+ 3152
+ 926
+ 1
+
+ dvk_backbone_core_5_location
+
+
+
+ dvk_backbone_core_5_annotation
+ 1
+
+
+
+
+
+ dvk_backbone_core
+
+ 1
+
+
+
+
+
+
+
+
+ Ligation_Scar_E_annotation
+
+
+
+ 922
+ Ligation_Scar_E_location
+ 925
+ 1
+
+
+
+ 1
+
+
+
+
+ Ligation_Scar_C_annotation
+
+
+
+ 1
+
+ 68
+ Ligation_Scar_C_location
+
+ 65
+
+
+ 1
+
+
+
+
+
+ B0034_Ligation_Scar_B
+
+ 1
+
+
+
+
+
+
+
+
+
+
+ 69
+ 1
+
+ 788
+ E0040m_gfp_3_location
+
+
+
+
+ E0040m_gfp_3_annotation
+ 1
+
+
+ standard_GFP_transformation_lvl1
+
+
+
+
+ 1
+ E0040m_gfp
+
+
+
+
+
+
+
+
+
+ 1
+ 43
+
+ Ligation_Scar_B_location
+ 40
+
+
+ 1
+
+
+ Ligation_Scar_B_annotation
+
+
+
+
+
+ 1
+
+
+ Ligation_Scar_B
+
+
+
+
+ B0034
+
+ 1
+
+
+
+
+
+
+ 1
+
+
+
+
+ dvk_backbone_core_Ligation_Scar_E
+
+
+
+
+
+
+ 1
+ B0034_3_location
+ 64
+
+
+ 44
+
+
+ B0034_3_annotation
+
+
+ 1
+
+
+
+
+
+
+ J23100_Ligation_Scar_A
+ 1
+
+
+
+
+
+
+
+ J23100_3_annotation
+ 1
+
+
+
+
+ J23100_3_location
+ 5
+
+
+ 39
+ 1
+
+
+
+
+
+
+
+
+ 1
+
+
+ Ligation_Scar_D_E0040m_gfp
+
+
+ 1
+
+
+
+
+ Ligation_Scar_B_J23100
+
+ 1
+
+
+
+
+
+ 1
+
+
+ B0015_Ligation_Scar_D
+
+
+
+
+
+
+
+
+
+ 1
+ Ligation_Scar_C
+
+
+
+
+
+ Ligation_Scar_D
+ 1
+
+
+
+
+
+
+ 1
+
+ E0040m_gfp_Ligation_Scar_C
+
+
+
+
+
+
+
+
+
+ 1
+ Ligation_Scar_A_sequence
+ GGAG
+
+
+
+
+ 1
+ Ligation_Scar_B_sequence
+ TACT
+
+
+
+ 1
+
+ Ligation_Scar_C_sequence
+ AATG
+
+
+
+
+ Ligation_Scar_D_sequence
+ 1
+ AGGT
+
+
+
+
+ Ligation_Scar_E_sequence
+ 1
+ GCTT
+
+
+
+ GGAGTTGACGGCTAGCTCAGTCCTAGGTACAGTGCTAGCTACTAGAGAAAGAGGAGAAATACTAAATGATGCGTAAAGGAGAAGAACTTTTCACTGGAGTTGTCCCAATTCTTGTTGAATTAGATGGTGATGTTAATGGGCACAAATTTTCTGTCAGTGGAGAGGGTGAAGGTGATGCAACATACGGAAAACTTACCCTTAAATTTATTTGCACTACTGGAAAACTACCTGTTCCATGGCCAACACTTGTCACTACTTTCGGTTATGGTGTTCAATGCTTTGCGAGATACCCAGATCATATGAAACAGCATGACTTTTTCAAGAGTGCCATGCCCGAAGGTTATGTACAGGAAAGAACTATATTTTTCAAAGATGACGGGAACTACAAGACACGTGCTGAAGTCAAGTTTGAAGGTGATACCCTTGTTAATAGAATCGAGTTAAAAGGTATTGATTTTAAAGAAGATGGAAACATTCTTGGACACAAATTGGAATACAACTATAACTCACACAATGTATACATCATGGCAGACAAACAAAAGAATGGAATCAAAGTTAACTTCAAAATTAGACACAACATTGAAGATGGAAGCGTTCAACTAGCAGACCATTATCAACAAAATACTCCAATTGGCGATGGCCCTGTCCTTTTACCAGACAACCATTACCTGTCCACACAATCTGCCCTTTCGAAAGATCCCAACGAAAAGAGAGATCACATGGTCCTTCTTGAGTTTGTAACAGCTGCTGGGATTACACATGGCATGGATGAACTATACAAATAATAAAGGTCCAGGCATCAAATAAAACGAAAGGCTCAGTCGAAAGACTGGGCCTTTCGTTTTATCTGTTGTTTGTCGGTGAACGCTCTCTACTAGAGTCACACTGGCTCACCTTCGGGTGGGCCTTTCTGCGTTTATAGCTTatgtcttctactagtagcggccgctgcagtccggcaaaaaagggcaaggtgtcaccaccctgccctttttctttaaaaccgaaaagattacttcgcgttatgcaggcttcctcgctcactgactcgctgcgctcggtcgttcggctgcggcgagcggtatcagctcactcaaaggcggtaatacggttatccacagaatcaggggataacgcaggaaagaacatgtgagcaaaaggccagcaaaaggccaggaaccgtaaaaaggccgcgttgctggcgtttttccacaggctccgcccccctgacgagcatcacaaaaatcgacgctcaagtcagaggtggcgaaacccgacaggactataaagataccaggcgtttccccctggaagctccctcgtgcgctctcctgttccgaccctgccgcttaccggatacctgtccgcctttctcccttcgggaagcgtggcgctttctcatagctcacgctgtaggtatctcagttcggtgtaggtcgttcgctccaagctgggctgtgtgcacgaaccccccgttcagcccgaccgctgcgccttatccggtaactatcgtcttgagtccaacccggtaagacacgacttatcgccactggcagcagccactggtaacaggattagcagagcgaggtatgtaggcggtgctacagagttcttgaagtggtggcctaactacggctacactagaagaacagtatttggtatctgcgctctgctgaagccagttaccttcggaaaaagagttggtagctcttgatccggcaaacaaaccaccgctggtagcggtggtttttttgtttgcaagcagcagattacgcgcagaaaaaaaggatctcaagaagatcctttgatcttttctacggggtctgacgctcagtggaacgaaaactcacgttaagggattttggtcatgagattatcaaaaaggatcttcacctagatccttttaaattaaaaatgaagttttaaatcaatctaaagtatatatgagtaaacttggtctgacagctcgagtcccgtcaagtcagcgtaatgctctgccagtgttacaaccaattaaccaattctgattagaaaaactcatcgagcatcaaatgaaactgcaatttattcatatcaggattatcaataccatatttttgaaaaagccgtttctgtaatgaaggagaaaactcaccgaggcagttccataggatggcaagatcctggtatcggtctgcgattccgactcgtccaacatcaatacaacctattaatttcccctcgtcaaaaataaggttatcaagtgagaaatcaccatgagtgacgactgaatccggtgagaatggcaaaagcttatgcatttctttccagacttgttcaacaggccagccattacgctcgtcatcaaaatcactcgcatcaaccaaaccgttattcattcgtgattgcgcctgagcgagacgaaatacgcgatcgctgttaaaaggacaattacaaacaggaatcgaatgcaaccggcgcaggaacactgccagcgcatcaacaatattttcacctgaatcaggatattcttctaatacctggaatgctgttttcccggggatcgcagtggtgagtaaccatgcatcatcaggagtacggataaaatgcttgatggtcggaagaggcataaattccgtcagccagtttagtctgaccatctcatctgtaacatcattggcaacgctacctttgccatgtttcagaaacaactctggcgcatcgggcttcccatacaatcgatagattgtcgcacctgattgcccgacattatcgcgagcccatttatacccatataaatcagcatccatgttggaatttaatcgcggcctggagcaagacgtttcccgttgaatatggctcataacaccccttgtattactgtttatgtaagcagacagttttattgttcatgatgatatatttttatcttgtgcaatgtaacatcagagattttgagacacaacgtggctttgttgaataaatcgaacttttgctgagttgaaggatcagctcgagtgccacctgacgtctaagaaaccattattatcatgacattaacctataaaaataggcgtatcacgaggcagaatttcagataaaaaaaatccttagctttcgctaaggatgatttctggaattcgcggccgcttctagagactagtggaagacat
+
+ standard_GFP_transformation_lvl1_seq
+ 1
+
+
+ 1
+ standard_GFP_transformation_lvl1_impl
+
+
+
+
+
+
+ E_coli_DH5alpha_impl
+
+ 1
+
+
+
+
+ 1
+ E_coli_DH5alpha_with_standard_GFP_transformation_lvl1_impl
+
+
+
+ 1
+
+
+ 1
+
+
+ pB0015_DE_impl
+
+
+
+ DNA Assembly
+
+
+ pE0040_CD_impl
+
+ 1
+
+
+
+
+
+
+
+
+
+ T4_Ligase
+ 1
+
+
+
+
+
+
+ pB0034_BC_impl
+ 1
+
+
+
+
+
+
+
+
+
+ 1
+ DVK_AE_impl
+
+
+
+
+ 1
+
+
+ BsaI_enzyme
+
+
+
+
+
+
+
+
+ 1
+
+ assemble_
+
+
+ standard_GFP_transformation_lvl1_assembly
+
+
+
+ 1
+
+ pJ23100_AB_impl
+
+
+
+
+
+ 1
+ Transform E_coli_DH5alpha with standard_GFP_transformation_lvl1
+ transform_standard_GFP_transformation_lvl1_1
+
+
+
+ 1
+
+
+
+ transform_standard_GFP_transformation_lvl1_1_plasmid_usage
+
+
+
+
+
+
+
+ 1
+ transform_standard_GFP_transformation_lvl1_1_chassis_usage
+
+
+
+
+
+
+ E_coli_DH5alpha
+ E_coli_DH5alpha
+
+ 1
+
+
+ 1
+ E_coli_DH5alpha_with_standard_GFP_transformation_lvl1
+
+
+
+
+ 1
+
+ E_coli_DH5alpha_with_standard_GFP_transformation_lvl1_plasmid_fc
+
+
+
+
+
+ E_coli_DH5alpha_with_standard_GFP_transformation_lvl1_chassis_module
+
+ 1
+
+
+
+ E_coli_DH5alpha transformed with standard_GFP_transformation_lvl1
+
+
+
diff --git a/notebooks/results/buildcompiler_transformation_quickstart/transformation_summary.json b/notebooks/results/buildcompiler_transformation_quickstart/transformation_summary.json
new file mode 100644
index 0000000..48efd2b
--- /dev/null
+++ b/notebooks/results/buildcompiler_transformation_quickstart/transformation_summary.json
@@ -0,0 +1,49 @@
+{
+ "stage": "transformation",
+ "inputs": [
+ "standard_GFP_transformation_lvl1_A_E"
+ ],
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformation_activity": "http://buildcompiler.org/transform_standard_GFP_transformation_lvl1_1/1",
+ "transformed_strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_transformation_lvl1/1",
+ "transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_transformation_lvl1_impl/1"
+ }
+ ],
+ "json_intermediate": {
+ "protocol": "chemical_transformation",
+ "version": "0.1",
+ "steps": [
+ {
+ "step": 1,
+ "plasmid": "standard_GFP_transformation_lvl1",
+ "chassis": "E_coli_DH5alpha",
+ "mix_ul": {
+ "competent_cells": 50,
+ "assembly_product": 5
+ },
+ "heat_shock": {
+ "temperature_c": 42,
+ "duration_seconds": 45
+ },
+ "recovery": {
+ "medium": "SOC",
+ "volume_ul": 950,
+ "duration_min": 60
+ }
+ }
+ ]
+ },
+ "protocol_artifacts": {
+ "ot2_script": "TODO: adapter to protocol generator",
+ "human_instructions": [
+ "Thaw competent cells on ice.",
+ "Combine assembly product with competent cells as specified.",
+ "Run heat shock and recovery according to generated parameters."
+ ],
+ "logs": [
+ "Prepared transformation input for plasmid standard_GFP_transformation_lvl1 into chassis E_coli_DH5alpha."
+ ]
+ }
+}
\ No newline at end of file
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_input.json
new file mode 100644
index 0000000..c687f78
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_input.json
@@ -0,0 +1,13 @@
+[
+ {
+ "Product": "http://buildcompiler.org/standard_GFP_full_build/1",
+ "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1",
+ "PartsList": [
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1"
+ ],
+ "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1"
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_protocol.py
new file mode 100644
index 0000000..73b1757
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/assembly_lvl1_pudu_assembly_protocol.py
@@ -0,0 +1,26 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+assembly_data = [
+ {
+ "Product": "http://buildcompiler.org/standard_GFP_full_build/1",
+ "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1",
+ "PartsList": [
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1"
+ ],
+ "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1"
+ }
+]
+
+metadata = {
+ 'protocolName': 'BuildCompiler assembly_lvl1 Assembly',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/full_build_manifest.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/full_build_manifest.json
new file mode 100644
index 0000000..3b85fe6
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/full_build_manifest.json
@@ -0,0 +1,156 @@
+{
+ "results_dir": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success",
+ "domestication": {
+ "successful": [],
+ "failed": []
+ },
+ "assembly_lvl1": {
+ "successful": [
+ {
+ "design": "standard_GFP",
+ "products": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+ ],
+ "failed": []
+ },
+ "assembly_lvl2": {
+ "successful": [],
+ "failed": []
+ },
+ "transformation": {
+ "successful": [
+ {
+ "stage_label": "standard_GFP",
+ "products": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ],
+ "result": {
+ "stage": "transformation",
+ "inputs": [
+ "standard_GFP_full_build_A_E"
+ ],
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformation_activity": "http://buildcompiler.org/transform_standard_GFP_full_build_1/1",
+ "transformed_strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1"
+ }
+ ],
+ "json_intermediate": {
+ "protocol": "chemical_transformation",
+ "version": "0.1",
+ "steps": [
+ {
+ "step": 1,
+ "plasmid": "standard_GFP_full_build",
+ "chassis": "E_coli_DH5alpha",
+ "mix_ul": {
+ "competent_cells": 50,
+ "assembly_product": 5
+ },
+ "heat_shock": {
+ "temperature_c": 42,
+ "duration_seconds": 45
+ },
+ "recovery": {
+ "medium": "SOC",
+ "volume_ul": 950,
+ "duration_min": 60
+ }
+ }
+ ]
+ },
+ "protocol_artifacts": {
+ "ot2_script": "TODO: adapter to protocol generator",
+ "human_instructions": [
+ "Thaw competent cells on ice.",
+ "Combine assembly product with competent cells as specified.",
+ "Run heat shock and recovery according to generated parameters."
+ ],
+ "logs": [
+ "Prepared transformation input for plasmid standard_GFP_full_build into chassis E_coli_DH5alpha."
+ ]
+ }
+ }
+ }
+ ],
+ "failed": []
+ },
+ "plating": {
+ "successful": [
+ {
+ "stage_label": "standard_GFP",
+ "result": {
+ "stage": "plating",
+ "protocol_type": "manual",
+ "plate": {
+ "plate_implementation": "solid_96_well_plate",
+ "plate_map": {
+ "A1": "1_plated_A1_impl"
+ }
+ },
+ "metadata": {
+ "plate_rows": [
+ {
+ "well": "A1",
+ "source_transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1",
+ "strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "plated_strain_implementation": "1_plated_A1_impl",
+ "strain_display_name": "1_plated_A1_impl"
+ }
+ ],
+ "layout_dataframe_columns": [
+ "well",
+ "source_transformed_strain_implementation",
+ "strain_module",
+ "plated_strain_implementation",
+ "strain_display_name"
+ ]
+ },
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+ },
+ "advanced_params": {}
+ },
+ "protocol_artifacts": {
+ "plate_map_json": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.json",
+ "plate_map_csv": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.csv",
+ "plate_layout_dataframe_csv": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_layout_dataframe.csv",
+ "logs": [],
+ "pudu": {
+ "runner_script": "https://github.com/MyersResearchGroup/PUDU/blob/main/scripts/run_sbol2plating_with_params.py",
+ "mode": "manual",
+ "advanced_params": {}
+ },
+ "manual_protocol_markdown": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/manual_plating_protocol.md"
+ }
+ }
+ }
+ ],
+ "failed": []
+ },
+ "skipped": [
+ {
+ "stage": "assembly_lvl2",
+ "reason": "no level-2 design provided"
+ }
+ ],
+ "artifacts": [
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_plasmid_locations.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_transformation_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/plating_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_plating_protocol.py"
+ ],
+ "manifest_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/full_build_manifest.json",
+ "zip_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success.zip",
+ "artifact_zip": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success.zip"
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/plating_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/plating_pudu_input.json
new file mode 100644
index 0000000..9812604
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/plating_pudu_input.json
@@ -0,0 +1,5 @@
+{
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_plating_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_plating_protocol.py
new file mode 100644
index 0000000..e8c41f8
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_plating_protocol.py
@@ -0,0 +1,18 @@
+from pudu.plating import Plating
+from opentrons import protocol_api
+
+plating_data = {
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Plating',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = Plating(plating_data=plating_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_transformation_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_transformation_protocol.py
new file mode 100644
index 0000000..42392d7
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/pudu_transformation_protocol.py
@@ -0,0 +1,31 @@
+from pudu.transformation import HeatShockTransformation
+from opentrons import protocol_api
+
+transformation_data = [
+ {
+ "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+]
+
+plasmid_locations = {
+ "http://buildcompiler.org/standard_GFP_full_build/1": [
+ "A1"
+ ]
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Transformation',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = HeatShockTransformation(
+ transformation_data=transformation_data,
+ plasmid_locations=plasmid_locations,
+ )
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/manual_plating_protocol.md b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/manual_plating_protocol.md
new file mode 100644
index 0000000..bc86255
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/manual_plating_protocol.md
@@ -0,0 +1,22 @@
+# BuildCompiler Plating Protocol
+
+## Plate
+- Plate ID: `solid_96_well_plate`
+- Protocol type: `manual`
+
+## Input transformed strains
+- 1_plated_A1_impl
+
+## Parameters
+- (none)
+
+## 96-well plate map
+| Well | Source transformed strain implementation | Plated strain implementation | Strain module |
+|---|---|---|---|
+| A1 | http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1 | 1_plated_A1_impl | http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1 |
+
+## Steps
+1. Prepare one sterile solid-media 96-well plate.
+2. Label the plate with the plate ID and date.
+3. Transfer each transformed strain to the destination well shown in the map.
+4. Incubate according to lab defaults or parameters above.
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_layout_dataframe.csv b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_layout_dataframe.csv
new file mode 100644
index 0000000..0053e37
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_layout_dataframe.csv
@@ -0,0 +1,2 @@
+well,source_transformed_strain_implementation,strain_module,plated_strain_implementation,strain_display_name
+A1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1,1_plated_A1_impl,1_plated_A1_impl
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.csv b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.csv
new file mode 100644
index 0000000..0053e37
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.csv
@@ -0,0 +1,2 @@
+well,source_transformed_strain_implementation,strain_module,plated_strain_implementation,strain_display_name
+A1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1,1_plated_A1_impl,1_plated_A1_impl
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.json
new file mode 100644
index 0000000..b8c290a
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plate_map.json
@@ -0,0 +1,13 @@
+{
+ "plate_implementation": "solid_96_well_plate",
+ "protocol_type": "manual",
+ "well_map": [
+ {
+ "well": "A1",
+ "source_transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1",
+ "strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "plated_strain_implementation": "1_plated_A1_impl",
+ "strain_display_name": "1_plated_A1_impl"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plating_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plating_input.json
new file mode 100644
index 0000000..ff85937
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/standard_GFP_plating/plating_input.json
@@ -0,0 +1,5 @@
+{
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
\ No newline at end of file
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_plasmid_locations.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_plasmid_locations.json
new file mode 100644
index 0000000..e722f2c
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_plasmid_locations.json
@@ -0,0 +1,5 @@
+{
+ "http://buildcompiler.org/standard_GFP_full_build/1": [
+ "A1"
+ ]
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_pudu_input.json
new file mode 100644
index 0000000..ebff170
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success 2/transformation_pudu_input.json
@@ -0,0 +1,9 @@
+[
+ {
+ "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success.zip b/notebooks/results/full_build_workflow_examples/lvl1_success.zip
new file mode 100644
index 0000000..71614b3
Binary files /dev/null and b/notebooks/results/full_build_workflow_examples/lvl1_success.zip differ
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_input.json
new file mode 100644
index 0000000..c687f78
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_input.json
@@ -0,0 +1,13 @@
+[
+ {
+ "Product": "http://buildcompiler.org/standard_GFP_full_build/1",
+ "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1",
+ "PartsList": [
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1"
+ ],
+ "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1"
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_protocol.py
new file mode 100644
index 0000000..73b1757
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_protocol.py
@@ -0,0 +1,26 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+assembly_data = [
+ {
+ "Product": "http://buildcompiler.org/standard_GFP_full_build/1",
+ "Backbone": "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/DVK_AE/1",
+ "PartsList": [
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pJ23100_AB/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0034_BC/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pE0040_CD/1",
+ "https://synbiohub.org/user/Gon/CIDARMoCloPlasmidsKit/pB0015_DE/1"
+ ],
+ "Restriction Enzyme": "https://synbiohub.org/user/Gon/Enzyme_Implementations/BsaI/1"
+ }
+]
+
+metadata = {
+ 'protocolName': 'BuildCompiler assembly_lvl1 Assembly',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/full_build_manifest.json b/notebooks/results/full_build_workflow_examples/lvl1_success/full_build_manifest.json
new file mode 100644
index 0000000..3b85fe6
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/full_build_manifest.json
@@ -0,0 +1,156 @@
+{
+ "results_dir": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success",
+ "domestication": {
+ "successful": [],
+ "failed": []
+ },
+ "assembly_lvl1": {
+ "successful": [
+ {
+ "design": "standard_GFP",
+ "products": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+ ],
+ "failed": []
+ },
+ "assembly_lvl2": {
+ "successful": [],
+ "failed": []
+ },
+ "transformation": {
+ "successful": [
+ {
+ "stage_label": "standard_GFP",
+ "products": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ],
+ "result": {
+ "stage": "transformation",
+ "inputs": [
+ "standard_GFP_full_build_A_E"
+ ],
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformation_activity": "http://buildcompiler.org/transform_standard_GFP_full_build_1/1",
+ "transformed_strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1"
+ }
+ ],
+ "json_intermediate": {
+ "protocol": "chemical_transformation",
+ "version": "0.1",
+ "steps": [
+ {
+ "step": 1,
+ "plasmid": "standard_GFP_full_build",
+ "chassis": "E_coli_DH5alpha",
+ "mix_ul": {
+ "competent_cells": 50,
+ "assembly_product": 5
+ },
+ "heat_shock": {
+ "temperature_c": 42,
+ "duration_seconds": 45
+ },
+ "recovery": {
+ "medium": "SOC",
+ "volume_ul": 950,
+ "duration_min": 60
+ }
+ }
+ ]
+ },
+ "protocol_artifacts": {
+ "ot2_script": "TODO: adapter to protocol generator",
+ "human_instructions": [
+ "Thaw competent cells on ice.",
+ "Combine assembly product with competent cells as specified.",
+ "Run heat shock and recovery according to generated parameters."
+ ],
+ "logs": [
+ "Prepared transformation input for plasmid standard_GFP_full_build into chassis E_coli_DH5alpha."
+ ]
+ }
+ }
+ }
+ ],
+ "failed": []
+ },
+ "plating": {
+ "successful": [
+ {
+ "stage_label": "standard_GFP",
+ "result": {
+ "stage": "plating",
+ "protocol_type": "manual",
+ "plate": {
+ "plate_implementation": "solid_96_well_plate",
+ "plate_map": {
+ "A1": "1_plated_A1_impl"
+ }
+ },
+ "metadata": {
+ "plate_rows": [
+ {
+ "well": "A1",
+ "source_transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1",
+ "strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "plated_strain_implementation": "1_plated_A1_impl",
+ "strain_display_name": "1_plated_A1_impl"
+ }
+ ],
+ "layout_dataframe_columns": [
+ "well",
+ "source_transformed_strain_implementation",
+ "strain_module",
+ "plated_strain_implementation",
+ "strain_display_name"
+ ]
+ },
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+ },
+ "advanced_params": {}
+ },
+ "protocol_artifacts": {
+ "plate_map_json": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.json",
+ "plate_map_csv": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.csv",
+ "plate_layout_dataframe_csv": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_layout_dataframe.csv",
+ "logs": [],
+ "pudu": {
+ "runner_script": "https://github.com/MyersResearchGroup/PUDU/blob/main/scripts/run_sbol2plating_with_params.py",
+ "mode": "manual",
+ "advanced_params": {}
+ },
+ "manual_protocol_markdown": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/manual_plating_protocol.md"
+ }
+ }
+ }
+ ],
+ "failed": []
+ },
+ "skipped": [
+ {
+ "stage": "assembly_lvl2",
+ "reason": "no level-2 design provided"
+ }
+ ],
+ "artifacts": [
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/assembly_lvl1_pudu_assembly_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_plasmid_locations.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_transformation_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/plating_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_plating_protocol.py"
+ ],
+ "manifest_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success/full_build_manifest.json",
+ "zip_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success.zip",
+ "artifact_zip": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl1_success.zip"
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/plating_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success/plating_pudu_input.json
new file mode 100644
index 0000000..9812604
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/plating_pudu_input.json
@@ -0,0 +1,5 @@
+{
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_plating_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_plating_protocol.py
new file mode 100644
index 0000000..e8c41f8
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_plating_protocol.py
@@ -0,0 +1,18 @@
+from pudu.plating import Plating
+from opentrons import protocol_api
+
+plating_data = {
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Plating',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = Plating(plating_data=plating_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_transformation_protocol.py b/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_transformation_protocol.py
new file mode 100644
index 0000000..42392d7
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/pudu_transformation_protocol.py
@@ -0,0 +1,31 @@
+from pudu.transformation import HeatShockTransformation
+from opentrons import protocol_api
+
+transformation_data = [
+ {
+ "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+]
+
+plasmid_locations = {
+ "http://buildcompiler.org/standard_GFP_full_build/1": [
+ "A1"
+ ]
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Transformation',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = HeatShockTransformation(
+ transformation_data=transformation_data,
+ plasmid_locations=plasmid_locations,
+ )
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/manual_plating_protocol.md b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/manual_plating_protocol.md
new file mode 100644
index 0000000..bc86255
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/manual_plating_protocol.md
@@ -0,0 +1,22 @@
+# BuildCompiler Plating Protocol
+
+## Plate
+- Plate ID: `solid_96_well_plate`
+- Protocol type: `manual`
+
+## Input transformed strains
+- 1_plated_A1_impl
+
+## Parameters
+- (none)
+
+## 96-well plate map
+| Well | Source transformed strain implementation | Plated strain implementation | Strain module |
+|---|---|---|---|
+| A1 | http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1 | 1_plated_A1_impl | http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1 |
+
+## Steps
+1. Prepare one sterile solid-media 96-well plate.
+2. Label the plate with the plate ID and date.
+3. Transfer each transformed strain to the destination well shown in the map.
+4. Incubate according to lab defaults or parameters above.
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_layout_dataframe.csv b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_layout_dataframe.csv
new file mode 100644
index 0000000..0053e37
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_layout_dataframe.csv
@@ -0,0 +1,2 @@
+well,source_transformed_strain_implementation,strain_module,plated_strain_implementation,strain_display_name
+A1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1,1_plated_A1_impl,1_plated_A1_impl
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.csv b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.csv
new file mode 100644
index 0000000..0053e37
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.csv
@@ -0,0 +1,2 @@
+well,source_transformed_strain_implementation,strain_module,plated_strain_implementation,strain_display_name
+A1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1,http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1,1_plated_A1_impl,1_plated_A1_impl
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.json b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.json
new file mode 100644
index 0000000..b8c290a
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plate_map.json
@@ -0,0 +1,13 @@
+{
+ "plate_implementation": "solid_96_well_plate",
+ "protocol_type": "manual",
+ "well_map": [
+ {
+ "well": "A1",
+ "source_transformed_strain_implementation": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build_impl/1",
+ "strain_module": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "plated_strain_implementation": "1_plated_A1_impl",
+ "strain_display_name": "1_plated_A1_impl"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plating_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plating_input.json
new file mode 100644
index 0000000..ff85937
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/standard_GFP_plating/plating_input.json
@@ -0,0 +1,5 @@
+{
+ "bacterium_locations": {
+ "A1": "1_plated_A1_impl"
+ }
+}
\ No newline at end of file
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_plasmid_locations.json b/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_plasmid_locations.json
new file mode 100644
index 0000000..e722f2c
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_plasmid_locations.json
@@ -0,0 +1,5 @@
+{
+ "http://buildcompiler.org/standard_GFP_full_build/1": [
+ "A1"
+ ]
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_pudu_input.json
new file mode 100644
index 0000000..ebff170
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl1_success/transformation_pudu_input.json
@@ -0,0 +1,9 @@
+[
+ {
+ "Strain": "http://buildcompiler.org/E_coli_DH5alpha_with_standard_GFP_full_build/1",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/standard_GFP_full_build/1"
+ ]
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages.zip b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages.zip
new file mode 100644
index 0000000..828d424
Binary files /dev/null and b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages.zip differ
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_input.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_input.json
new file mode 100644
index 0000000..6a7484a
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_input.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Product": "http://buildcompiler.org/assembled_demo_tu/1",
+ "Backbone": "demo_lvl1_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ],
+ "Restriction Enzyme": "BsaI"
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_protocol.py b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_protocol.py
new file mode 100644
index 0000000..0f4ed2b
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_protocol.py
@@ -0,0 +1,23 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+assembly_data = [
+ {
+ "Product": "http://buildcompiler.org/assembled_demo_tu/1",
+ "Backbone": "demo_lvl1_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ],
+ "Restriction Enzyme": "BsaI"
+ }
+]
+
+metadata = {
+ 'protocolName': 'BuildCompiler assembly_lvl1 Assembly',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_input.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_input.json
new file mode 100644
index 0000000..789a52b
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_input.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Product": "http://buildcompiler.org/assembled_demo_lvl2/1",
+ "Backbone": "demo_lvl2_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ],
+ "Restriction Enzyme": "BbsI"
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_protocol.py b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_protocol.py
new file mode 100644
index 0000000..62f7c9f
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_protocol.py
@@ -0,0 +1,23 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+assembly_data = [
+ {
+ "Product": "http://buildcompiler.org/assembled_demo_lvl2/1",
+ "Backbone": "demo_lvl2_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ],
+ "Restriction Enzyme": "BbsI"
+ }
+]
+
+metadata = {
+ 'protocolName': 'BuildCompiler assembly_lvl2 Assembly',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_input.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_input.json
new file mode 100644
index 0000000..847b17e
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_input.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Product": "http://buildcompiler.org/domesticated_missing_promoter/1",
+ "Backbone": "demo_domestication_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/missing_promoter/1"
+ ],
+ "Restriction Enzyme": "BsaI"
+ }
+]
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_protocol.py b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_protocol.py
new file mode 100644
index 0000000..92d915e
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_protocol.py
@@ -0,0 +1,23 @@
+from pudu.assembly import SBOLLoopAssembly
+from opentrons import protocol_api
+
+assembly_data = [
+ {
+ "Product": "http://buildcompiler.org/domesticated_missing_promoter/1",
+ "Backbone": "demo_domestication_backbone",
+ "PartsList": [
+ "http://buildcompiler.org/missing_promoter/1"
+ ],
+ "Restriction Enzyme": "BsaI"
+ }
+]
+
+metadata = {
+ 'protocolName': 'BuildCompiler domestication Assembly',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/full_build_manifest.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/full_build_manifest.json
new file mode 100644
index 0000000..12015ad
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/full_build_manifest.json
@@ -0,0 +1,164 @@
+{
+ "results_dir": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages",
+ "domestication": {
+ "successful": [
+ {
+ "parts": [
+ "missing_promoter"
+ ],
+ "products": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ]
+ }
+ ],
+ "failed": []
+ },
+ "assembly_lvl1": {
+ "successful": [
+ {
+ "design": "demo_tu",
+ "products": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ]
+ }
+ ],
+ "failed": [
+ {
+ "design": "demo_tu",
+ "error": "level-1 input is missing a domesticated part"
+ }
+ ]
+ },
+ "assembly_lvl2": {
+ "successful": [
+ {
+ "design": "demo_lvl2_design",
+ "products": [
+ "http://buildcompiler.org/assembled_demo_lvl2/1"
+ ],
+ "after_recovery": true
+ }
+ ],
+ "failed": [
+ {
+ "design": "demo_lvl2_design",
+ "error": "level-2 input is missing level-1 regions",
+ "recovery": "attempting level-1 assembly and domestication"
+ }
+ ]
+ },
+ "transformation": {
+ "successful": [
+ {
+ "stage_label": "domestication",
+ "products": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ],
+ "result": {
+ "stage": "transformation",
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformed_strain_module": "http://buildcompiler.org/domesticated_missing_promoter/1_strain",
+ "transformed_strain_implementation": "http://buildcompiler.org/domesticated_missing_promoter/1_strain_impl"
+ }
+ ]
+ }
+ },
+ {
+ "stage_label": "full_build_lvl2_1_lvl1",
+ "products": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ],
+ "result": {
+ "stage": "transformation",
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformed_strain_module": "http://buildcompiler.org/assembled_demo_tu/1_strain",
+ "transformed_strain_implementation": "http://buildcompiler.org/assembled_demo_tu/1_strain_impl"
+ }
+ ]
+ }
+ },
+ {
+ "stage_label": "lvl2_1_final",
+ "products": [
+ "http://buildcompiler.org/assembled_demo_lvl2/1"
+ ],
+ "result": {
+ "stage": "transformation",
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {
+ "transformed_strain_module": "http://buildcompiler.org/assembled_demo_lvl2/1_strain",
+ "transformed_strain_implementation": "http://buildcompiler.org/assembled_demo_lvl2/1_strain_impl"
+ }
+ ]
+ }
+ }
+ ],
+ "failed": []
+ },
+ "plating": {
+ "successful": [
+ {
+ "stage_label": "domestication",
+ "result": {
+ "stage": "plating",
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ }
+ }
+ }
+ },
+ {
+ "stage_label": "full_build_lvl2_1_lvl1",
+ "result": {
+ "stage": "plating",
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ }
+ }
+ }
+ },
+ {
+ "stage_label": "lvl2_1_final",
+ "result": {
+ "stage": "plating",
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "failed": []
+ },
+ "skipped": [],
+ "artifacts": [
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl1_pudu_assembly_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/assembly_lvl2_pudu_assembly_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/domestication_pudu_assembly_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_plasmid_locations.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_transformation_protocol.py",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/plating_pudu_input.json",
+ "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_plating_protocol.py"
+ ],
+ "manifest_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/full_build_manifest.json",
+ "zip_path": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages.zip",
+ "artifact_zip": "/Users/gonzalovidal/Documents/GitHub/BuildCompiler/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages.zip"
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/plating_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/plating_pudu_input.json
new file mode 100644
index 0000000..a5114c5
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/plating_pudu_input.json
@@ -0,0 +1,19 @@
+{
+ "batches": [
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ },
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ },
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ }
+ ]
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_plating_protocol.py b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_plating_protocol.py
new file mode 100644
index 0000000..f5ed7f5
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_plating_protocol.py
@@ -0,0 +1,32 @@
+from pudu.plating import Plating
+from opentrons import protocol_api
+
+plating_data = {
+ "batches": [
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ },
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ },
+ {
+ "bacterium_locations": {
+ "A1": "demo_transformed_strain"
+ }
+ }
+ ]
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Plating',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = Plating(plating_data=plating_data)
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_transformation_protocol.py b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_transformation_protocol.py
new file mode 100644
index 0000000..6a1d04d
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/pudu_transformation_protocol.py
@@ -0,0 +1,51 @@
+from pudu.transformation import HeatShockTransformation
+from opentrons import protocol_api
+
+transformation_data = [
+ {
+ "Strain": "http://buildcompiler.org/domesticated_missing_promoter/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ]
+ },
+ {
+ "Strain": "http://buildcompiler.org/assembled_demo_tu/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ]
+ },
+ {
+ "Strain": "http://buildcompiler.org/assembled_demo_lvl2/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/assembled_demo_lvl2/1"
+ ]
+ }
+]
+
+plasmid_locations = {
+ "http://buildcompiler.org/domesticated_missing_promoter/1": [
+ "A1"
+ ],
+ "http://buildcompiler.org/assembled_demo_tu/1": [
+ "B1"
+ ],
+ "http://buildcompiler.org/assembled_demo_lvl2/1": [
+ "C1"
+ ]
+}
+
+metadata = {
+ 'protocolName': 'BuildCompiler Transformation',
+ 'author': 'BuildCompiler',
+ 'apiLevel': '2.21',
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ protocol_instance = HeatShockTransformation(
+ transformation_data=transformation_data,
+ plasmid_locations=plasmid_locations,
+ )
+ protocol_instance.run(protocol)
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_plasmid_locations.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_plasmid_locations.json
new file mode 100644
index 0000000..849779b
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_plasmid_locations.json
@@ -0,0 +1,11 @@
+{
+ "http://buildcompiler.org/domesticated_missing_promoter/1": [
+ "A1"
+ ],
+ "http://buildcompiler.org/assembled_demo_tu/1": [
+ "B1"
+ ],
+ "http://buildcompiler.org/assembled_demo_lvl2/1": [
+ "C1"
+ ]
+}
diff --git a/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_pudu_input.json b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_pudu_input.json
new file mode 100644
index 0000000..9ed14f5
--- /dev/null
+++ b/notebooks/results/full_build_workflow_examples/lvl2_triggers_all_stages/transformation_pudu_input.json
@@ -0,0 +1,23 @@
+[
+ {
+ "Strain": "http://buildcompiler.org/domesticated_missing_promoter/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/domesticated_missing_promoter/1"
+ ]
+ },
+ {
+ "Strain": "http://buildcompiler.org/assembled_demo_tu/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/assembled_demo_tu/1"
+ ]
+ },
+ {
+ "Strain": "http://buildcompiler.org/assembled_demo_lvl2/1_strain",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": [
+ "http://buildcompiler.org/assembled_demo_lvl2/1"
+ ]
+ }
+]
diff --git a/src/buildcompiler/adapters/pudu/__init__.py b/src/buildcompiler/adapters/pudu/__init__.py
index eb22839..242d8f4 100644
--- a/src/buildcompiler/adapters/pudu/__init__.py
+++ b/src/buildcompiler/adapters/pudu/__init__.py
@@ -11,6 +11,7 @@
)
from .plating_json import plating_to_pudu_json
from .transformation_json import (
+ plasmid_locations_to_pudu_json,
transformation_to_pudu_json,
transformations_to_pudu_json,
)
@@ -23,6 +24,7 @@
"legacy_assembly_route_to_pudu_json",
"legacy_assembly_routes_to_pudu_json",
"write_assembly_pudu_input_json",
+ "plasmid_locations_to_pudu_json",
"transformation_to_pudu_json",
"transformations_to_pudu_json",
"plating_to_pudu_json",
diff --git a/src/buildcompiler/adapters/pudu/plating_json.py b/src/buildcompiler/adapters/pudu/plating_json.py
index 3648e20..a6656cf 100644
--- a/src/buildcompiler/adapters/pudu/plating_json.py
+++ b/src/buildcompiler/adapters/pudu/plating_json.py
@@ -7,16 +7,21 @@
def plating_to_pudu_json(
*,
- bacterium_locations: Mapping[str, str],
+ bacterium_locations: Mapping[str, str | list[str]],
advanced_parameters: Mapping[str, object] | None = None,
) -> dict[str, object]:
- """Adapt plating records into deterministic legacy-compatible PUDU JSON keys."""
+ """Adapt plating records into PUDU's plating input JSON.
+
+ PUDU expects thermocycler source wells as keys and transformed construct
+ identifiers as values, wrapped under ``bacterium_locations``.
+ """
stable_locations = OrderedDict(
sorted(bacterium_locations.items(), key=lambda kv: kv[0])
)
payload: dict[str, Any] = {
"bacterium_locations": dict(stable_locations),
- "advanced_parameters": dict(advanced_parameters or {}),
}
+ if advanced_parameters:
+ payload.update(dict(advanced_parameters))
return payload
diff --git a/src/buildcompiler/adapters/pudu/transformation_json.py b/src/buildcompiler/adapters/pudu/transformation_json.py
index 207a561..4385da9 100644
--- a/src/buildcompiler/adapters/pudu/transformation_json.py
+++ b/src/buildcompiler/adapters/pudu/transformation_json.py
@@ -5,6 +5,11 @@
from buildcompiler.domain import IndexedPlasmid
+PUDU_96_WELL_ORDER = tuple(
+ f"{row}{column}" for column in range(1, 13) for row in "ABCDEFGH"
+)
+
+
def _stable_identifier(identity: str, display_id: str | None) -> str:
return identity or display_id or ""
@@ -51,3 +56,31 @@ def transformations_to_pudu_json(
strict=True,
)
]
+
+
+def plasmid_locations_to_pudu_json(
+ plasmids: Sequence[IndexedPlasmid | str],
+ *,
+ wells: Sequence[str] | None = None,
+) -> dict[str, list[str]]:
+ """Create PUDU's assembly-output plasmid location map.
+
+ PUDU's transformation protocol optionally consumes the
+ ``transformation_input.json`` emitted by its assembly simulation. The shape
+ is ``{"plasmid_uri": ["A1"]}``, where each value is a list because one
+ plasmid may be available in multiple source wells.
+ """
+
+ if wells is None:
+ wells = PUDU_96_WELL_ORDER[: len(plasmids)]
+ if len(plasmids) != len(wells):
+ raise ValueError("plasmids and wells must have the same length.")
+
+ locations: dict[str, list[str]] = {}
+ for plasmid, well in zip(plasmids, wells, strict=True):
+ plasmid_id = _plasmid_identifier(plasmid)
+ if not plasmid_id:
+ raise ValueError("plasmid identity cannot be empty.")
+ locations.setdefault(plasmid_id, []).append(well)
+
+ return locations
diff --git a/src/buildcompiler/buildcompiler.py b/src/buildcompiler/buildcompiler.py
index d450f4a..e49417d 100644
--- a/src/buildcompiler/buildcompiler.py
+++ b/src/buildcompiler/buildcompiler.py
@@ -4,6 +4,7 @@
import re
import shutil
import warnings
+import zipfile
from typing import Any, List, Dict, Tuple
import urllib.parse
import csv
@@ -32,7 +33,13 @@
write_plate_map_json,
write_plating_protocol_script,
)
-from .adapters.pudu import legacy_assembly_routes_to_pudu_json
+from .adapters.pudu import (
+ legacy_assembly_routes_to_pudu_json,
+ plasmid_locations_to_pudu_json,
+ plating_to_pudu_json,
+ transformations_to_pudu_json,
+ write_assembly_pudu_input_json,
+)
from .constants import (
AMP,
ENGINEERED_REGION,
@@ -80,6 +87,7 @@ def __init__(
self.restriction_enzyme_implementations = []
self.ligase_implementations = []
self.last_assembly_pudu_json = []
+ self.last_assembly_pudu_json_by_stage = {}
self.BsaI_impl = None
self.BbsI_impl = None
self.T4_ligase_impl = None
@@ -103,6 +111,7 @@ def from_local_documents(
compiler.restriction_enzyme_implementations = []
compiler.ligase_implementations = []
compiler.last_assembly_pudu_json = []
+ compiler.last_assembly_pudu_json_by_stage = {}
compiler.BsaI_impl = None
compiler.BbsI_impl = None
compiler.T4_ligase_impl = None
@@ -294,6 +303,7 @@ def _remove_internal_bsai_sites(sequence: str) -> tuple[str, int]:
dsDNAs = []
domesticated_parts = []
+ pudu_payloads = []
for part in parts:
part_role = next(
@@ -394,7 +404,21 @@ def _remove_internal_bsai_sites(sequence: str) -> tuple[str, int]:
assembly_products, assembly_doc = assembly.run()
product_definition = assembly_products[0].plasmid_definition
domesticated_parts.append(product_definition)
+ pudu_payloads.extend(
+ legacy_assembly_routes_to_pudu_json(
+ product_plasmids=assembly_products,
+ part_plasmid_routes=[
+ [insert_plasmid] for _ in range(len(assembly_products))
+ ],
+ backbones=[backbone for _ in range(len(assembly_products))],
+ restriction_enzymes=[
+ self.BsaI_impl for _ in range(len(assembly_products))
+ ],
+ )
+ )
+ self.last_assembly_pudu_json = pudu_payloads
+ self.last_assembly_pudu_json_by_stage["domestication"] = list(pudu_payloads)
return domesticated_parts
def assembly_lvl1(
@@ -429,8 +453,8 @@ def assembly_lvl1(
enumerated_part_lists = enumerate_design_variants(combinatorial_part_dict)
- for i, list in enumerate(enumerated_part_lists):
- plasmid_dict = self._construct_plasmid_dict(list, AMP)
+ for i, part_list in enumerate(enumerated_part_lists):
+ plasmid_dict = self._construct_plasmid_dict(part_list, AMP)
if isinstance(backbone, dict):
raise ValueError(
@@ -567,6 +591,7 @@ def assembly_lvl1(
assembly_dict[abstract_design.identity] = composite_plasmids
self.last_assembly_pudu_json = pudu_payloads
+ self.last_assembly_pudu_json_by_stage["assembly_lvl1"] = list(pudu_payloads)
return assembly_dict, final_doc
def assembly_lvl2(
@@ -610,6 +635,7 @@ def assembly_lvl2(
composite_plasmid_dict, final_doc = self.assembly_lvl1(
TUs, backbone=backbone_dict, product_name=f"{TU.displayId}_plas"
)
+ lvl1_pudu_payloads = list(self.last_assembly_pudu_json)
for key, composites in composite_plasmid_dict.items():
simplified_representation, new_defs = self._encapsulate_TU(composites[0])
@@ -655,12 +681,15 @@ def assembly_lvl2(
)
lvl2_plasmids, final_doc = assembly.run() # TODO upload product_doc?
- self.last_assembly_pudu_json = legacy_assembly_routes_to_pudu_json(
+ lvl2_pudu_payloads = legacy_assembly_routes_to_pudu_json(
product_plasmids=lvl2_plasmids,
part_plasmid_routes=[lvl1_plasmids for _ in range(len(lvl2_plasmids))],
backbones=[backbone for _ in range(len(lvl2_plasmids))],
restriction_enzymes=[self.BbsI_impl for _ in range(len(lvl2_plasmids))],
)
+ self.last_assembly_pudu_json = lvl2_pudu_payloads
+ self.last_assembly_pudu_json_by_stage["assembly_lvl1"] = lvl1_pudu_payloads
+ self.last_assembly_pudu_json_by_stage["assembly_lvl2"] = list(lvl2_pudu_payloads)
self.indexed_plasmids.extend(lvl2_plasmids)
return lvl2_plasmids, final_doc
@@ -1065,6 +1094,842 @@ def plating(
"protocol_artifacts": protocol_artifacts,
}
+ def full_build(
+ self,
+ designs: Any = None,
+ results_dir: str | Path = "full_build_results",
+ overwrite: bool = False,
+ chassis_name: str = "E_coli_DH5alpha",
+ plating_protocol_type: str = "manual",
+ plating_advanced_params: dict | None = None,
+ product_name: str = "full_build",
+ ) -> Dict[str, Any]:
+ """Run the legacy full build workflow and return packaged artifacts.
+
+ The workflow is deliberately file-oriented: each stage writes explicit
+ intermediates/protocol inputs under ``results_dir`` and the return value
+ includes a zip archive containing those artifacts.
+ """
+
+ results_path = Path(results_dir)
+ if results_path.exists() and overwrite:
+ shutil.rmtree(results_path)
+ results_path.mkdir(parents=True, exist_ok=True)
+ self.last_assembly_pudu_json = []
+ self.last_assembly_pudu_json_by_stage = {}
+
+ result: Dict[str, Any] = {
+ "results_dir": str(results_path),
+ "domestication": {"successful": [], "failed": []},
+ "assembly_lvl1": {"successful": [], "failed": []},
+ "assembly_lvl2": {"successful": [], "failed": []},
+ "transformation": {"successful": [], "failed": []},
+ "plating": {"successful": [], "failed": []},
+ "skipped": [],
+ "artifacts": [],
+ }
+ assembly_payloads: Dict[str, list[dict[str, object]]] = {
+ "assembly_lvl1": [],
+ "assembly_lvl2": [],
+ "domestication": [],
+ }
+
+ lvl2_docs, lvl1_designs = self._split_full_build_inputs(designs)
+
+ for index, lvl2_doc in enumerate(lvl2_docs, start=1):
+ label = f"lvl2_{index}"
+ try:
+ lvl2_products, lvl2_doc_out = self._run_full_build_lvl2(
+ lvl2_doc,
+ product_name=f"{product_name}_{label}",
+ result=result,
+ assembly_payloads=assembly_payloads,
+ chassis_name=chassis_name,
+ results_path=results_path,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+ self._run_transformation_and_plating(
+ lvl2_products,
+ stage_label=f"{label}_final",
+ result=result,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ transformation_doc=lvl2_doc_out,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+ except Exception as exc:
+ result["assembly_lvl2"]["failed"].append(
+ {"design": label, "error": str(exc)}
+ )
+
+ if lvl1_designs:
+ self._run_full_build_lvl1_designs(
+ lvl1_designs,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ chassis_name=chassis_name,
+ results_path=results_path,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ product_name=product_name,
+ )
+ elif not lvl2_docs:
+ result["skipped"].append(
+ {"stage": "assembly_lvl2", "reason": "no level-2 design provided"}
+ )
+
+ artifact_paths = self._write_full_build_artifacts(
+ result=result,
+ assembly_payloads=assembly_payloads,
+ results_path=results_path,
+ )
+ result["artifacts"].extend(str(path) for path in artifact_paths)
+
+ manifest_path = results_path / "full_build_manifest.json"
+ zip_path = self._resolve_full_build_zip_path(results_path, overwrite=overwrite)
+ result["manifest_path"] = str(manifest_path)
+ result["zip_path"] = str(zip_path)
+ result["artifact_zip"] = str(zip_path)
+ self._write_json(manifest_path, result)
+ self._archive_full_build_results(results_path, zip_path)
+
+ return result
+
+ def _split_full_build_inputs(
+ self, designs: Any
+ ) -> tuple[list[sbol2.Document], list[sbol2.ComponentDefinition]]:
+ if designs is None:
+ return [], [self._get_abstract_design()]
+ if isinstance(designs, sbol2.Document):
+ return [designs], []
+ if isinstance(designs, sbol2.CombinatorialDerivation):
+ return [], self._normalize_full_build_designs(designs)
+ if isinstance(designs, sbol2.ComponentDefinition):
+ return [], [designs]
+ if isinstance(designs, list) or isinstance(designs, tuple):
+ lvl2_docs = [item for item in designs if isinstance(item, sbol2.Document)]
+ lvl1_inputs = [
+ item for item in designs if not isinstance(item, sbol2.Document)
+ ]
+ lvl1_designs = (
+ self._normalize_full_build_designs(lvl1_inputs)
+ if lvl1_inputs
+ else []
+ )
+ return lvl2_docs, lvl1_designs
+ return [], self._normalize_full_build_designs(designs)
+
+ def _run_full_build_lvl2(
+ self,
+ lvl2_doc: sbol2.Document,
+ *,
+ product_name: str,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ chassis_name: str,
+ results_path: Path,
+ plating_protocol_type: str,
+ plating_advanced_params: dict | None,
+ overwrite: bool,
+ ) -> tuple[list[Any], sbol2.Document]:
+ try:
+ lvl2_products, lvl2_doc_out = self.assembly_lvl2(
+ lvl2_doc, product_name=product_name
+ )
+ result["assembly_lvl2"]["successful"].append(
+ {
+ "design": self._document_label(lvl2_doc),
+ "products": self._product_identities(lvl2_products),
+ }
+ )
+ self._capture_assembly_payloads(assembly_payloads)
+ return list(lvl2_products), lvl2_doc_out
+ except Exception as lvl2_exc:
+ result["assembly_lvl2"]["failed"].append(
+ {
+ "design": self._document_label(lvl2_doc),
+ "error": str(lvl2_exc),
+ "recovery": "attempting level-1 assembly and domestication",
+ }
+ )
+
+ tus = _extract_lvl2_TUs(lvl2_doc)
+ lvl1_products = self._attempt_lvl1_then_domesticate(
+ tus,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ product_name=f"{product_name}_lvl1",
+ results_path=results_path,
+ chassis_name=chassis_name,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+ if lvl1_products:
+ self._run_transformation_and_plating(
+ lvl1_products,
+ stage_label=f"{product_name}_lvl1",
+ result=result,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ transformation_doc=self.sbol_doc,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+
+ lvl2_products, lvl2_doc_out = self.assembly_lvl2(
+ lvl2_doc, product_name=product_name
+ )
+ result["assembly_lvl2"]["successful"].append(
+ {
+ "design": self._document_label(lvl2_doc),
+ "products": self._product_identities(lvl2_products),
+ "after_recovery": True,
+ }
+ )
+ self._capture_assembly_payloads(assembly_payloads)
+ return list(lvl2_products), lvl2_doc_out
+
+ def _run_full_build_lvl1_designs(
+ self,
+ designs: list[sbol2.ComponentDefinition],
+ *,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ chassis_name: str,
+ results_path: Path,
+ plating_protocol_type: str,
+ plating_advanced_params: dict | None,
+ overwrite: bool,
+ product_name: str,
+ ) -> None:
+ missing_parts = []
+ seen_missing = set()
+ for design in designs:
+ for missing in self._find_missing_parts_for_lvl1(design):
+ part = missing["part"]
+ if part.identity not in seen_missing:
+ missing_parts.append(part)
+ seen_missing.add(part.identity)
+
+ if missing_parts:
+ self._run_domestication(
+ missing_parts,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+
+ for design in designs:
+ try:
+ products, stage_doc = self._run_one_lvl1_design(
+ design,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ product_name=product_name,
+ )
+ except Exception as exc:
+ result["assembly_lvl1"]["failed"].append(
+ {"design": design.displayId or design.identity, "error": str(exc)}
+ )
+ continue
+ self._run_transformation_and_plating(
+ products,
+ stage_label=design.displayId or "lvl1",
+ result=result,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ transformation_doc=stage_doc,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+
+ result["skipped"].append(
+ {"stage": "assembly_lvl2", "reason": "no level-2 design provided"}
+ )
+
+ def _attempt_lvl1_then_domesticate(
+ self,
+ designs: list[sbol2.ComponentDefinition],
+ *,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ product_name: str,
+ results_path: Path,
+ chassis_name: str,
+ plating_protocol_type: str,
+ plating_advanced_params: dict | None,
+ overwrite: bool,
+ ) -> list[Any]:
+ products: list[Any] = []
+ failed_designs: list[sbol2.ComponentDefinition] = []
+ for design in designs:
+ try:
+ design_products, _ = self._run_one_lvl1_design(
+ design,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ product_name=product_name,
+ )
+ products.extend(design_products)
+ except Exception as exc:
+ result["assembly_lvl1"]["failed"].append(
+ {"design": design.displayId or design.identity, "error": str(exc)}
+ )
+ failed_designs.append(design)
+
+ missing_parts = []
+ seen_missing = set()
+ for design in failed_designs:
+ for missing in self._find_missing_parts_for_lvl1(design):
+ part = missing["part"]
+ if part.identity not in seen_missing:
+ missing_parts.append(part)
+ seen_missing.add(part.identity)
+
+ if missing_parts:
+ self._run_domestication(
+ missing_parts,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+
+ for design in failed_designs:
+ design_products, _ = self._run_one_lvl1_design(
+ design,
+ result=result,
+ assembly_payloads=assembly_payloads,
+ product_name=product_name,
+ )
+ products.extend(design_products)
+
+ return products
+
+ def _run_one_lvl1_design(
+ self,
+ design: sbol2.ComponentDefinition,
+ *,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ product_name: str,
+ ) -> tuple[list[Any], sbol2.Document]:
+ output = self.assembly_lvl1([design], product_name=product_name)
+ products, stage_doc = self._normalize_lvl1_output(output, design)
+ result["assembly_lvl1"]["successful"].append(
+ {
+ "design": design.displayId or design.identity,
+ "products": self._product_identities(products),
+ }
+ )
+ self._capture_assembly_payloads(assembly_payloads, default_stage="assembly_lvl1")
+ return products, stage_doc
+
+ def _run_domestication(
+ self,
+ parts: list[sbol2.ComponentDefinition],
+ *,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ results_path: Path,
+ chassis_name: str,
+ plating_protocol_type: str,
+ plating_advanced_params: dict | None,
+ overwrite: bool,
+ ) -> list[Any]:
+ try:
+ products = list(self.domestication(parts))
+ except Exception as exc:
+ result["domestication"]["failed"].append(
+ {
+ "parts": [part.displayId or part.identity for part in parts],
+ "error": str(exc),
+ }
+ )
+ return []
+
+ self._index_domestication_products(products)
+
+ result["domestication"]["successful"].append(
+ {
+ "parts": [part.displayId or part.identity for part in parts],
+ "products": self._product_identities(products),
+ }
+ )
+ self._capture_assembly_payloads(assembly_payloads, default_stage="domestication")
+ self._run_transformation_and_plating(
+ products,
+ stage_label="domestication",
+ result=result,
+ results_path=results_path,
+ chassis_name=chassis_name,
+ transformation_doc=self.sbol_doc,
+ plating_protocol_type=plating_protocol_type,
+ plating_advanced_params=plating_advanced_params,
+ overwrite=overwrite,
+ )
+ return products
+
+
+ def _index_domestication_products(self, products: list[Any]) -> None:
+ """Make domesticated plasmids available to subsequent assembly retries."""
+ for product in products:
+ if isinstance(product, Plasmid):
+ if not self._get_indexed_plasmid(
+ self.indexed_plasmids, product.plasmid_definition
+ ):
+ self.indexed_plasmids.append(product)
+ continue
+
+ if isinstance(product, sbol2.ComponentDefinition):
+ self._sort_plasmid_components(product, self.sbol_doc)
+
+ def _run_transformation_and_plating(
+ self,
+ products: list[Any],
+ *,
+ stage_label: str,
+ result: Dict[str, Any],
+ results_path: Path,
+ chassis_name: str,
+ transformation_doc: sbol2.Document,
+ plating_protocol_type: str,
+ plating_advanced_params: dict | None,
+ overwrite: bool,
+ ) -> None:
+ if not products:
+ return
+ try:
+ transformation_result = self.transformation(
+ products,
+ chassis_name=chassis_name,
+ transformation_doc=transformation_doc,
+ )
+ result["transformation"]["successful"].append(
+ {
+ "stage_label": stage_label,
+ "products": self._product_identities(products),
+ "result": transformation_result,
+ }
+ )
+ except Exception as exc:
+ result["transformation"]["failed"].append(
+ {
+ "stage_label": stage_label,
+ "products": self._product_identities(products),
+ "error": str(exc),
+ }
+ )
+ return
+
+ try:
+ plating_result = self.plating(
+ transformation_result,
+ results_dir=results_path / f"{stage_label}_plating",
+ protocol_type=plating_protocol_type,
+ advanced_params=plating_advanced_params,
+ plating_doc=transformation_doc,
+ overwrite=overwrite,
+ )
+ result["plating"]["successful"].append(
+ {"stage_label": stage_label, "result": plating_result}
+ )
+ except Exception as exc:
+ result["plating"]["failed"].append(
+ {"stage_label": stage_label, "error": str(exc)}
+ )
+
+ def _normalize_full_build_designs(self, designs: Any) -> list[sbol2.ComponentDefinition]:
+ if isinstance(designs, sbol2.ComponentDefinition):
+ return [designs]
+ if isinstance(designs, sbol2.CombinatorialDerivation):
+ return self._expand_combinatorial_derivation(designs)
+ if isinstance(designs, list) or isinstance(designs, tuple):
+ normalized: list[sbol2.ComponentDefinition] = []
+ for design in designs:
+ if isinstance(design, sbol2.CombinatorialDerivation):
+ normalized.extend(self._expand_combinatorial_derivation(design))
+ elif isinstance(design, sbol2.ComponentDefinition):
+ normalized.append(design)
+ else:
+ raise ValueError(
+ "full_build designs must be SBOL ComponentDefinitions, "
+ "CombinatorialDerivations, Documents, or lists of those."
+ )
+ return normalized
+ raise ValueError(
+ "full_build designs must be SBOL ComponentDefinitions, "
+ "CombinatorialDerivations, Documents, or lists of those."
+ )
+
+ def _expand_combinatorial_derivation(
+ self,
+ derivation: sbol2.CombinatorialDerivation,
+ product_name_prefix: str = "full_build",
+ ) -> list[sbol2.ComponentDefinition]:
+ template = get_or_pull(
+ self.sbol_doc, self.sbh, derivation.masterTemplate, self.server_mode
+ )
+ variant_lists = enumerate_design_variants(
+ extract_combinatorial_design_parts(template, derivation)
+ )
+ variants = []
+ for index, parts in enumerate(variant_lists, start=1):
+ variant = sbol2.ComponentDefinition(
+ f"{product_name_prefix}_variant_{index:03d}"
+ )
+ self.sbol_doc.add(variant)
+ created_components = []
+ for part_index, part in enumerate(parts, start=1):
+ component = variant.components.create(f"part_{part_index}")
+ component.definition = part.identity
+ created_components.append(component)
+ for constraint_index in range(len(created_components) - 1):
+ constraint = variant.sequenceConstraints.create(
+ f"constraint_{constraint_index + 1}"
+ )
+ constraint.subject = created_components[constraint_index].identity
+ constraint.object = created_components[constraint_index + 1].identity
+ constraint.restriction = sbol2.SBOL_RESTRICTION_PRECEDES
+ variants.append(variant)
+ return variants
+
+ def _find_missing_parts_for_lvl1(
+ self, design: sbol2.ComponentDefinition
+ ) -> list[dict[str, Any]]:
+ missing = []
+ parts = self._extract_design_parts(design)
+ plasmid_dict = self._construct_plasmid_dict(parts, AMP)
+ for part in parts:
+ if not plasmid_dict.get(part.displayId):
+ missing.append({"part": part, "reason": "no implemented plasmid"})
+
+ if missing:
+ return missing
+
+ backbone, compatible = self._get_backbone(
+ plasmid_dict, antibiotic_resistance=KAN
+ )
+ if backbone is None or not compatible:
+ return [
+ {
+ "part": part,
+ "reason": "no compatible level-1 route",
+ }
+ for part in parts
+ ]
+ return []
+
+ def _normalize_lvl1_output(
+ self, output: Any, design: sbol2.ComponentDefinition
+ ) -> tuple[list[Any], sbol2.Document]:
+ stage_doc = self.sbol_doc
+ payload = output
+ if isinstance(output, tuple):
+ payload = output[0]
+ if len(output) > 1 and isinstance(output[1], sbol2.Document):
+ stage_doc = output[1]
+ if isinstance(payload, dict):
+ products = list(payload.get(design.identity, []))
+ if not products:
+ products = [
+ product
+ for product_list in payload.values()
+ for product in (
+ product_list
+ if isinstance(product_list, list)
+ else [product_list]
+ )
+ ]
+ return products, stage_doc
+ if isinstance(payload, list):
+ return payload, stage_doc
+ return [payload], stage_doc
+
+ def _capture_assembly_payloads(
+ self,
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ default_stage: str = "assembly_lvl1",
+ ) -> None:
+ staged = getattr(self, "last_assembly_pudu_json_by_stage", {}) or {}
+ if staged:
+ for stage, payloads in staged.items():
+ assembly_payloads.setdefault(stage, [])
+ assembly_payloads[stage].extend(self._dedupe_payloads(payloads))
+ return
+ payloads = getattr(self, "last_assembly_pudu_json", []) or []
+ assembly_payloads.setdefault(default_stage, [])
+ assembly_payloads[default_stage].extend(self._dedupe_payloads(payloads))
+
+ def _write_full_build_artifacts(
+ self,
+ *,
+ result: Dict[str, Any],
+ assembly_payloads: Dict[str, list[dict[str, object]]],
+ results_path: Path,
+ ) -> list[Path]:
+ written: list[Path] = []
+
+ for stage, payloads in sorted(assembly_payloads.items()):
+ payloads = self._dedupe_payloads(payloads)
+ if not payloads:
+ continue
+ json_path = write_assembly_pudu_input_json(
+ payloads, results_path / f"{stage}_pudu_assembly_input.json"
+ )
+ written.append(json_path)
+ written.append(
+ self._write_pudu_assembly_protocol_script(
+ results_path / f"{stage}_pudu_assembly_protocol.py",
+ payloads,
+ protocol_name=f"BuildCompiler {stage} Assembly",
+ )
+ )
+
+ transformation_payloads = []
+ plasmid_location_inputs = []
+ for entry in result["transformation"]["successful"]:
+ products = entry.get("products", [])
+ tx_result = entry.get("result", {})
+ artifacts = tx_result.get("sbol_artifacts", []) if isinstance(tx_result, dict) else []
+ strain_ids = [
+ artifact.get("transformed_strain_module")
+ or artifact.get("transformed_strain_implementation")
+ for artifact in artifacts
+ if isinstance(artifact, dict)
+ ]
+ if not strain_ids:
+ strain_ids = [f"{entry.get('stage_label', 'transformation')}_strain"]
+ chassis = (
+ tx_result.get("chassis", "E_coli_DH5alpha")
+ if isinstance(tx_result, dict)
+ else "E_coli_DH5alpha"
+ )
+ transformation_payloads.extend(
+ transformations_to_pudu_json(
+ strain_identities=strain_ids,
+ chassis_identities=[chassis for _ in strain_ids],
+ plasmid_sets=self._plasmid_sets_for_transformed_strains(
+ products, strain_ids
+ ),
+ )
+ )
+ plasmid_location_inputs.extend(products)
+
+ if transformation_payloads:
+ tx_path = results_path / "transformation_pudu_input.json"
+ self._write_json(tx_path, transformation_payloads)
+ written.append(tx_path)
+ location_payload = plasmid_locations_to_pudu_json(plasmid_location_inputs)
+ loc_path = results_path / "transformation_plasmid_locations.json"
+ self._write_json(loc_path, location_payload)
+ written.append(loc_path)
+ written.append(
+ self._write_pudu_transformation_protocol_script(
+ results_path / "pudu_transformation_protocol.py",
+ transformation_payloads,
+ location_payload,
+ )
+ )
+
+ plating_payloads = []
+ for entry in result["plating"]["successful"]:
+ plating_result = entry.get("result", {})
+ if not isinstance(plating_result, dict):
+ continue
+ plating_data = (
+ plating_result.get("json_intermediate", {}).get("plating_data", {})
+ )
+ bacterium_locations = plating_data.get("bacterium_locations")
+ if bacterium_locations:
+ plating_payloads.append(
+ plating_to_pudu_json(bacterium_locations=bacterium_locations)
+ )
+
+ if plating_payloads:
+ plating_payload = (
+ plating_payloads[0]
+ if len(plating_payloads) == 1
+ else {"batches": plating_payloads}
+ )
+ plating_path = results_path / "plating_pudu_input.json"
+ self._write_json(plating_path, plating_payload)
+ written.append(plating_path)
+ written.append(
+ self._write_pudu_plating_protocol_script(
+ results_path / "pudu_plating_protocol.py", plating_payload
+ )
+ )
+
+ return written
+
+ def _plasmid_sets_for_transformed_strains(
+ self, products: list[Any], strain_ids: list[str]
+ ) -> list[list[str]]:
+ product_ids = self._product_identities(products)
+ if len(product_ids) == len(strain_ids):
+ return [[product_id] for product_id in product_ids]
+ return [product_ids for _ in strain_ids]
+
+ def _write_pudu_assembly_protocol_script(
+ self, path: Path, payload: list[dict[str, object]], protocol_name: str
+ ) -> Path:
+ script = (
+ "from pudu.assembly import SBOLLoopAssembly\n"
+ "from opentrons import protocol_api\n\n"
+ f"assembly_data = {json.dumps(payload, indent=4)}\n\n"
+ "metadata = {\n"
+ f" 'protocolName': {protocol_name!r},\n"
+ " 'author': 'BuildCompiler',\n"
+ " 'apiLevel': '2.21',\n"
+ "}\n\n"
+ "def run(protocol: protocol_api.ProtocolContext):\n"
+ " protocol_instance = SBOLLoopAssembly(assembly_data=assembly_data)\n"
+ " protocol_instance.run(protocol)\n"
+ )
+ path.write_text(script, encoding="utf-8")
+ return path
+
+ def _write_pudu_transformation_protocol_script(
+ self,
+ path: Path,
+ transformation_payload: list[dict[str, object]],
+ plasmid_locations: dict[str, list[str]],
+ ) -> Path:
+ script = (
+ "from pudu.transformation import HeatShockTransformation\n"
+ "from opentrons import protocol_api\n\n"
+ f"transformation_data = {json.dumps(transformation_payload, indent=4)}\n\n"
+ f"plasmid_locations = {json.dumps(plasmid_locations, indent=4)}\n\n"
+ "metadata = {\n"
+ " 'protocolName': 'BuildCompiler Transformation',\n"
+ " 'author': 'BuildCompiler',\n"
+ " 'apiLevel': '2.21',\n"
+ "}\n\n"
+ "def run(protocol: protocol_api.ProtocolContext):\n"
+ " protocol_instance = HeatShockTransformation(\n"
+ " transformation_data=transformation_data,\n"
+ " plasmid_locations=plasmid_locations,\n"
+ " )\n"
+ " protocol_instance.run(protocol)\n"
+ )
+ path.write_text(script, encoding="utf-8")
+ return path
+
+ def _write_pudu_plating_protocol_script(
+ self, path: Path, plating_payload: dict[str, object]
+ ) -> Path:
+ script = (
+ "from pudu.plating import Plating\n"
+ "from opentrons import protocol_api\n\n"
+ f"plating_data = {json.dumps(plating_payload, indent=4)}\n\n"
+ "metadata = {\n"
+ " 'protocolName': 'BuildCompiler Plating',\n"
+ " 'author': 'BuildCompiler',\n"
+ " 'apiLevel': '2.21',\n"
+ "}\n\n"
+ "def run(protocol: protocol_api.ProtocolContext):\n"
+ " protocol_instance = Plating(plating_data=plating_data)\n"
+ " protocol_instance.run(protocol)\n"
+ )
+ path.write_text(script, encoding="utf-8")
+ return path
+
+ def _resolve_full_build_zip_path(
+ self, results_path: Path, overwrite: bool
+ ) -> Path:
+ zip_path = results_path.with_suffix(".zip")
+ if zip_path.exists() and overwrite:
+ zip_path.unlink()
+ if zip_path.exists():
+ index = 1
+ while True:
+ candidate = results_path.with_name(f"{results_path.name}_{index}.zip")
+ if not candidate.exists():
+ zip_path = candidate
+ break
+ index += 1
+ return zip_path
+
+ def _archive_full_build_results(
+ self, results_path: Path, zip_path: Path
+ ) -> Path:
+ with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
+ for path in sorted(results_path.rglob("*")):
+ if path.is_file():
+ archive.write(path, path.relative_to(results_path))
+ return zip_path
+
+ def _write_json(self, path: Path, payload: Any) -> Path:
+ path.write_text(
+ json.dumps(self._json_safe(payload), indent=2) + "\n",
+ encoding="utf-8",
+ )
+ return path
+
+ def _json_safe(self, value: Any) -> Any:
+ if isinstance(value, Path):
+ return str(value)
+ if isinstance(value, dict):
+ return {str(key): self._json_safe(item) for key, item in value.items()}
+ if isinstance(value, (list, tuple, set)):
+ return [self._json_safe(item) for item in value]
+ if isinstance(value, (str, int, float, bool)) or value is None:
+ return value
+ if hasattr(value, "identity"):
+ return getattr(value, "identity")
+ if hasattr(value, "plasmid_definition"):
+ return self._plasmid_identity(value)
+ return str(value)
+
+ def _product_identities(self, products: list[Any]) -> list[str]:
+ return [self._plasmid_identity(product) for product in products]
+
+ def _plasmid_identity(self, product: Any) -> str:
+ if isinstance(product, str):
+ return product
+ definition = getattr(product, "plasmid_definition", product)
+ return str(
+ getattr(definition, "identity", None)
+ or getattr(definition, "displayId", None)
+ or product
+ )
+
+ def _document_label(self, doc: sbol2.Document) -> str:
+ try:
+ top_level = extract_toplevel_definition(doc)
+ return top_level.displayId or top_level.identity
+ except Exception:
+ return "level_2_design"
+
+ def _dedupe_payloads(
+ self, payloads: list[dict[str, object]]
+ ) -> list[dict[str, object]]:
+ deduped = []
+ seen = set()
+ for payload in payloads or []:
+ key = json.dumps(self._json_safe(payload), sort_keys=True)
+ if key in seen:
+ continue
+ seen.add(key)
+ deduped.append(payload)
+ return deduped
+
def _extract_plasmids_from_strain(
self,
strain: sbol2.ModuleDefinition,
@@ -1643,6 +2508,57 @@ def _add_if_absent(self, doc: sbol2.Document, obj: Any):
if doc.find(obj.identity) is None:
doc.add(obj)
+ def _normalize_transformation_inputs(
+ self, assembly_products: List[Any]
+ ) -> List[Dict[str, sbol2.ComponentDefinition | str]]:
+ """Normalize supported transformation inputs into plasmid definitions.
+
+ Transformation can be called directly after assembly, where inputs are
+ BuildCompiler ``Plasmid`` objects, or independently with SBOL plasmid
+ definitions/dict payloads. This keeps that adapter logic out of the SBOL
+ writer so transformation remains usable as a standalone stage.
+ """
+
+ normalized_products = []
+ for product in assembly_products:
+ source = None
+ plasmid = None
+
+ if isinstance(product, dict):
+ source = product.get("source") or product.get("name")
+ product = product.get("plasmid") or product.get("plasmid_definition")
+
+ if isinstance(product, Plasmid):
+ plasmid = product.plasmid_definition
+ source = source or product.name or plasmid.displayId
+ elif isinstance(product, sbol2.ComponentDefinition):
+ plasmid = product
+ source = source or plasmid.displayId
+ elif hasattr(product, "plasmid_definition"):
+ plasmid = product.plasmid_definition
+ source = source or getattr(product, "name", None) or plasmid.displayId
+
+ if plasmid is None:
+ raise ValueError(
+ "transformation inputs must be Plasmid objects, "
+ "sbol2.ComponentDefinition plasmids, or dictionaries with a "
+ "'plasmid' entry."
+ )
+ if not isinstance(plasmid, sbol2.ComponentDefinition):
+ raise ValueError(
+ f"transformation plasmid input must resolve to a "
+ f"ComponentDefinition, got {type(plasmid).__name__}."
+ )
+
+ normalized_products.append(
+ {
+ "source": source or plasmid.displayId or plasmid.identity,
+ "plasmid": plasmid,
+ }
+ )
+
+ return normalized_products
+
def _get_or_create_chassis(
self, doc: sbol2.Document, chassis_name: str
) -> tuple[sbol2.ModuleDefinition, sbol2.Implementation]:
diff --git a/tests/test_buildcompiler_transformation.py b/tests/test_buildcompiler_transformation.py
index 0b9a6e7..137ab88 100644
--- a/tests/test_buildcompiler_transformation.py
+++ b/tests/test_buildcompiler_transformation.py
@@ -1,11 +1,13 @@
import os
import sys
import unittest
+from pathlib import Path
import sbol2
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
+from buildcompiler.abstract_translator import extract_toplevel_definition
from buildcompiler.buildcompiler import BuildCompiler
from buildcompiler.constants import ENGINEERED_PLASMID
@@ -59,6 +61,46 @@ def test_transformation_requires_inputs(self):
with self.assertRaises(ValueError):
self.compiler.transformation([])
+ def test_transformation_accepts_lvl1_assembly_products(self):
+ test_files = Path(__file__).parent / "test_files"
+ collection_docs = []
+ for filename in (
+ "CIDARMoCloParts_collection.xml",
+ "CIDARMoCloPlasmidsKit_collection.xml",
+ "Enzyme_Implementations_collection.xml",
+ "impl_test_collection.xml",
+ ):
+ doc = sbol2.Document()
+ doc.read(str(test_files / filename))
+ collection_docs.append(doc)
+
+ design_doc = sbol2.Document()
+ design_doc.read(str(test_files / "abstract_design.xml"))
+ design = extract_toplevel_definition(design_doc)
+ compiler = BuildCompiler.from_local_documents(
+ collection_docs, design_doc=design_doc
+ )
+
+ assembly_routes, assembly_doc = compiler.assembly_lvl1(
+ [design],
+ final_doc=sbol2.Document(),
+ product_name="transformation_lvl1",
+ )
+
+ products = assembly_routes[design.identity]
+ result = compiler.transformation(
+ products,
+ chassis_name="E_coli_DH5alpha",
+ transformation_doc=assembly_doc,
+ )
+
+ self.assertEqual(result["stage"], "transformation")
+ self.assertEqual(result["inputs"], [products[0].name])
+ self.assertEqual(len(result["sbol_artifacts"]), 1)
+ self.assertIsNotNone(
+ assembly_doc.find(result["sbol_artifacts"][0]["transformation_activity"])
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_full_build.py b/tests/test_full_build.py
index eb3a532..071d9ae 100644
--- a/tests/test_full_build.py
+++ b/tests/test_full_build.py
@@ -1,3 +1,4 @@
+import json
import os
import sys
import tempfile
@@ -11,6 +12,7 @@
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
from buildcompiler.buildcompiler import BuildCompiler
+from buildcompiler.plasmid import Plasmid
from buildcompiler.constants import ENGINEERED_PLASMID
@@ -57,6 +59,16 @@ def _make_plasmid(self, display_id: str) -> sbol2.ComponentDefinition:
self.doc.add(plasmid)
return plasmid
+ def _make_lvl2_document(self) -> tuple[sbol2.Document, sbol2.ComponentDefinition]:
+ doc = sbol2.Document()
+ tu = sbol2.ComponentDefinition("example_tu")
+ lvl2 = sbol2.ComponentDefinition("example_lvl2_design")
+ doc.add(tu)
+ doc.add(lvl2)
+ comp = lvl2.components.create("tu_component")
+ comp.definition = tu.identity
+ return doc, tu
+
def test_normalize_full_build_designs_input_shapes(self):
d1 = self._make_design("design_a", ["part_a"])
d2 = self._make_design("design_b", ["part_b"])
@@ -108,6 +120,44 @@ def test_find_missing_parts_reports_missing_and_present(self):
self.assertEqual(missing[0]["part"].displayId, "part_y")
self.assertEqual(missing[0]["reason"], "no implemented plasmid")
+ def test_run_domestication_indexes_products_before_retry(self):
+ missing_part = self._make_part("missing_for_retry")
+ domesticated = self._make_plasmid("domesticated_for_retry")
+
+ with tempfile.TemporaryDirectory() as tmpdir, patch.object(
+ self.compiler, "domestication", return_value=[domesticated]
+ ), patch.object(
+ self.compiler, "_sort_plasmid_components"
+ ) as mock_sort, patch.object(
+ self.compiler, "_run_transformation_and_plating"
+ ):
+ self.compiler._run_domestication(
+ [missing_part],
+ result={
+ "domestication": {"successful": [], "failed": []},
+ "transformation": {"successful": [], "failed": []},
+ "plating": {"successful": [], "failed": []},
+ },
+ assembly_payloads={},
+ results_path=Path(tmpdir),
+ chassis_name="E_coli_DH5alpha",
+ plating_protocol_type="manual",
+ plating_advanced_params=None,
+ overwrite=True,
+ )
+
+ mock_sort.assert_called_once_with(domesticated, self.compiler.sbol_doc)
+
+ def test_index_domestication_products_adds_plasmid_routes_once(self):
+ domesticated = self._make_plasmid("domesticated_route_for_retry")
+ route = object.__new__(Plasmid)
+ route.plasmid_definition = domesticated
+
+ self.compiler._index_domestication_products([route])
+ self.compiler._index_domestication_products([route])
+
+ self.assertEqual(self.compiler.indexed_plasmids, [route])
+
def test_full_build_orchestration_and_stage_skip(self):
design_a = self._make_design("dA", ["pa"])
design_b = self._make_design("dB", ["pb"])
@@ -173,6 +223,181 @@ def test_full_build_writes_manifest_and_zip_and_return_shape(self):
names = archive.namelist()
self.assertIn("full_build_manifest.json", names)
+ def test_full_build_pudu_transformation_pairs_each_strain_with_own_product(self):
+ result = {
+ "transformation": {
+ "successful": [
+ {
+ "stage_label": "assembly_lvl1",
+ "products": ["plasmid_a", "plasmid_b"],
+ "result": {
+ "chassis": "E_coli_DH5alpha",
+ "sbol_artifacts": [
+ {"transformed_strain_module": "strain_a"},
+ {"transformed_strain_module": "strain_b"},
+ ],
+ },
+ }
+ ]
+ },
+ "plating": {"successful": []},
+ }
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ self.compiler._write_full_build_artifacts(
+ result=result,
+ assembly_payloads={},
+ results_path=Path(tmpdir),
+ )
+ payload = json.loads(
+ (Path(tmpdir) / "transformation_pudu_input.json").read_text(
+ encoding="utf-8"
+ )
+ )
+
+ self.assertEqual(
+ payload,
+ [
+ {
+ "Strain": "strain_a",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": ["plasmid_a"],
+ },
+ {
+ "Strain": "strain_b",
+ "Chassis": "E_coli_DH5alpha",
+ "Plasmids": ["plasmid_b"],
+ },
+ ],
+ )
+
+ def test_full_build_lvl2_example_packages_pudu_protocols_for_recovery_stack(self):
+ lvl2_doc, _ = self._make_lvl2_document()
+ missing_part = self._make_part("missing_promoter")
+ domesticated = self._make_plasmid("domesticated_missing_promoter")
+ lvl1_product = self._make_plasmid("assembled_example_tu")
+ lvl2_product = self._make_plasmid("assembled_example_lvl2")
+ calls = []
+
+ def fake_assembly_lvl2(*args, **kwargs):
+ calls.append("assembly_lvl2")
+ if calls.count("assembly_lvl2") == 1:
+ raise RuntimeError("level-2 input is missing level-1 regions")
+ self.compiler.last_assembly_pudu_json_by_stage = {
+ "assembly_lvl2": [
+ {
+ "Product": lvl2_product.identity,
+ "Backbone": "lvl2_backbone",
+ "PartsList": [lvl1_product.identity],
+ "Restriction Enzyme": "BbsI",
+ }
+ ]
+ }
+ return [lvl2_product], self.doc
+
+ def fake_assembly_lvl1(*args, **kwargs):
+ calls.append("assembly_lvl1")
+ if calls.count("assembly_lvl1") == 1:
+ raise RuntimeError("level-1 input is missing domesticated part")
+ self.compiler.last_assembly_pudu_json_by_stage = {
+ "assembly_lvl1": [
+ {
+ "Product": lvl1_product.identity,
+ "Backbone": "lvl1_backbone",
+ "PartsList": [domesticated.identity],
+ "Restriction Enzyme": "BsaI",
+ }
+ ]
+ }
+ return [lvl1_product], self.doc
+
+ def fake_domestication(parts):
+ calls.append("domestication")
+ self.compiler.last_assembly_pudu_json_by_stage = {
+ "domestication": [
+ {
+ "Product": domesticated.identity,
+ "Backbone": "domestication_backbone",
+ "PartsList": [missing_part.identity],
+ "Restriction Enzyme": "BsaI",
+ }
+ ]
+ }
+ return [domesticated]
+
+ def fake_transformation(
+ products, chassis_name="E_coli_DH5alpha", transformation_doc=None
+ ):
+ calls.append("transformation")
+ product_id = products[0].identity
+ return {
+ "stage": "transformation",
+ "chassis": chassis_name,
+ "sbol_artifacts": [
+ {
+ "transformed_strain_module": f"{product_id}_strain",
+ "transformed_strain_implementation": f"{product_id}_strain_impl",
+ }
+ ],
+ }
+
+ def fake_plating(*args, **kwargs):
+ calls.append("plating")
+ return {
+ "stage": "plating",
+ "json_intermediate": {
+ "plating_data": {
+ "bacterium_locations": {"A1": "example_transformed_strain"}
+ }
+ },
+ }
+
+ with tempfile.TemporaryDirectory() as tmpdir, patch.object(
+ self.compiler, "assembly_lvl2", side_effect=fake_assembly_lvl2
+ ), patch.object(
+ self.compiler, "assembly_lvl1", side_effect=fake_assembly_lvl1
+ ), patch.object(
+ self.compiler,
+ "_find_missing_parts_for_lvl1",
+ return_value=[{"part": missing_part}],
+ ), patch.object(
+ self.compiler, "domestication", side_effect=fake_domestication
+ ), patch.object(
+ self.compiler, "transformation", side_effect=fake_transformation
+ ), patch.object(
+ self.compiler, "plating", side_effect=fake_plating
+ ):
+ result = self.compiler.full_build(
+ designs=lvl2_doc,
+ results_dir=Path(tmpdir) / "lvl2_full_build",
+ overwrite=True,
+ )
+ zip_path = Path(result["zip_path"])
+ self.assertTrue(zip_path.exists())
+ with zipfile.ZipFile(zip_path, "r") as archive:
+ names = set(archive.namelist())
+
+ self.assertEqual(
+ calls[:3], ["assembly_lvl2", "assembly_lvl1", "domestication"]
+ )
+ self.assertEqual(result["artifact_zip"], result["zip_path"])
+
+ expected_artifacts = {
+ "assembly_lvl1_pudu_assembly_input.json",
+ "assembly_lvl2_pudu_assembly_input.json",
+ "domestication_pudu_assembly_input.json",
+ "assembly_lvl1_pudu_assembly_protocol.py",
+ "assembly_lvl2_pudu_assembly_protocol.py",
+ "domestication_pudu_assembly_protocol.py",
+ "transformation_pudu_input.json",
+ "transformation_plasmid_locations.json",
+ "pudu_transformation_protocol.py",
+ "plating_pudu_input.json",
+ "pudu_plating_protocol.py",
+ "full_build_manifest.json",
+ }
+ self.assertTrue(expected_artifacts.issubset(names))
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/unit/adapters/pudu/test_plating_json.py b/tests/unit/adapters/pudu/test_plating_json.py
index aa075e5..320b660 100644
--- a/tests/unit/adapters/pudu/test_plating_json.py
+++ b/tests/unit/adapters/pudu/test_plating_json.py
@@ -3,17 +3,17 @@
def test_plating_to_pudu_json_shape_and_values():
payload = plating_to_pudu_json(
- bacterium_locations={"strain_b": "B2", "strain_a": "A1"},
+ bacterium_locations={"B2": "strain_b", "A1": "strain_a"},
advanced_parameters={"replicates": 2},
)
assert payload == {
- "bacterium_locations": {"strain_a": "A1", "strain_b": "B2"},
- "advanced_parameters": {"replicates": 2},
+ "bacterium_locations": {"A1": "strain_a", "B2": "strain_b"},
+ "replicates": 2,
}
-def test_plating_to_pudu_json_defaults_advanced_parameters():
- payload = plating_to_pudu_json(bacterium_locations={"strain_a": "A1"})
+def test_plating_to_pudu_json_omits_empty_advanced_parameters():
+ payload = plating_to_pudu_json(bacterium_locations={"A1": "strain_a"})
- assert payload["advanced_parameters"] == {}
+ assert payload == {"bacterium_locations": {"A1": "strain_a"}}
diff --git a/tests/unit/adapters/pudu/test_transformation_json.py b/tests/unit/adapters/pudu/test_transformation_json.py
index d18f7ab..8ad86ce 100644
--- a/tests/unit/adapters/pudu/test_transformation_json.py
+++ b/tests/unit/adapters/pudu/test_transformation_json.py
@@ -1,4 +1,5 @@
from buildcompiler.adapters.pudu import (
+ plasmid_locations_to_pudu_json,
transformation_to_pudu_json,
transformations_to_pudu_json,
)
@@ -36,3 +37,24 @@ def test_transformations_to_pudu_json_batch_helper_is_deterministic():
{"Strain": "s1", "Chassis": "c1", "Plasmids": ["p1"]},
{"Strain": "s2", "Chassis": "c2", "Plasmids": ["p2", "p3"]},
]
+
+
+def test_plasmid_locations_to_pudu_json_uses_deterministic_wells():
+ payload = plasmid_locations_to_pudu_json(["p1", "p2", "p3"])
+
+ assert payload == {
+ "p1": ["A1"],
+ "p2": ["B1"],
+ "p3": ["C1"],
+ }
+
+
+def test_plasmid_locations_to_pudu_json_accepts_explicit_wells_and_duplicates():
+ payload = plasmid_locations_to_pudu_json(
+ ["p1", "p1", "p2"], wells=["A1", "B1", "C1"]
+ )
+
+ assert payload == {
+ "p1": ["A1", "B1"],
+ "p2": ["C1"],
+ }