#!/usr/bin/env python3 """ Export Parser Code Generates standalone Python scripts that can be handed off to data engineers or run in Jupyter notebooks. The exported code is self-contained and production-ready. Usage: python export_parser.py --vendor VI_CELL_BLU --output vicell_parser.py python export_parser.py --vendor NANODROP_EIGHT --format notebook --output nanodrop_parser.ipynb """ import sys from pathlib import Path from datetime import datetime from typing import Optional # Template for standalone Python script SCRIPT_TEMPLATE = '''#!/usr/bin/env python3 """ {instrument_name} to Allotrope Simple Model (ASM) Parser Auto-generated by Claude instrument-data-to-allotrope skill Generated: {timestamp} Vendor: {vendor} This script converts {instrument_name} output files to Allotrope Simple Model (ASM) JSON format for LIMS import, data lakes, or downstream analysis. Requirements: pip install allotropy pandas openpyxl Usage: python {script_name} input_file.csv --output output_asm.json python {script_name} input_file.csv --flatten # Also generate CSV Input file format: {file_format_description} """ import json import argparse from pathlib import Path from typing import Dict, Any, Optional try: from allotropy.parser_factory import Vendor from allotropy.to_allotrope import allotrope_from_file ALLOTROPY_AVAILABLE = True except ImportError: ALLOTROPY_AVAILABLE = False print("Warning: allotropy not installed. Install with: pip install allotropy") try: import pandas as pd PANDAS_AVAILABLE = True except ImportError: PANDAS_AVAILABLE = False def convert_to_asm(filepath: str) -> Optional[Dict[str, Any]]: """ Convert {instrument_name} file to ASM format. Args: filepath: Path to input file Returns: ASM dictionary or None if conversion fails """ if not ALLOTROPY_AVAILABLE: raise ImportError("allotropy library required. Install with: pip install allotropy") try: asm = allotrope_from_file(filepath, Vendor.{vendor}) return asm except Exception as e: print(f"Conversion error: {{e}}") return None def flatten_asm(asm: Dict[str, Any]) -> list: """ Flatten ASM to list of row dictionaries for CSV export. Args: asm: ASM dictionary Returns: List of flattened row dictionaries """ technique = "{technique}" rows = [] agg_key = f"{{technique}}-aggregate-document" agg_doc = asm.get(agg_key, {{}}) # Extract device info device = agg_doc.get("device-system-document", {{}}) device_info = {{ "instrument_serial_number": device.get("device-identifier"), "instrument_model": device.get("model-number"), }} doc_key = f"{{technique}}-document" for doc in agg_doc.get(doc_key, []): meas_agg = doc.get("measurement-aggregate-document", {{}}) common = {{ "analyst": meas_agg.get("analyst"), "measurement_time": meas_agg.get("measurement-time"), **device_info }} for meas in meas_agg.get("measurement-document", []): row = {{**common}} for key, value in meas.items(): clean_key = key.replace("-", "_") if isinstance(value, dict) and "value" in value: row[clean_key] = value["value"] if "unit" in value: row[f"{{clean_key}}_unit"] = value["unit"] else: row[clean_key] = value rows.append(row) return rows def main(): parser = argparse.ArgumentParser(description="Convert {instrument_name} to ASM") parser.add_argument("input", help="Input file path") parser.add_argument("--output", "-o", help="Output JSON path") parser.add_argument("--flatten", action="store_true", help="Also generate CSV") args = parser.parse_args() input_path = Path(args.input) if not input_path.exists(): print(f"Error: File not found: {{args.input}}") return 1 # Convert to ASM print(f"Converting {{args.input}}...") asm = convert_to_asm(str(input_path)) if asm is None: print("Conversion failed") return 1 # Write ASM JSON output_path = args.output or str(input_path.with_suffix('.asm.json')) with open(output_path, 'w') as f: json.dump(asm, f, indent=2, default=str) print(f"ASM written to: {{output_path}}") # Optionally flatten if args.flatten and PANDAS_AVAILABLE: rows = flatten_asm(asm) df = pd.DataFrame(rows) flat_path = str(input_path.with_suffix('.flat.csv')) df.to_csv(flat_path, index=False) print(f"CSV written to: {{flat_path}}") return 0 if __name__ == "__main__": sys.exit(main()) ''' # Template for Jupyter notebook NOTEBOOK_TEMPLATE = """{{ "cells": [ {{ "cell_type": "markdown", "metadata": {{}}, "source": [ "# {instrument_name} to Allotrope Simple Model (ASM) Parser\\n", "\\n", "Auto-generated by Claude instrument-data-to-allotrope skill\\n", "Generated: {timestamp}\\n", "Vendor: {vendor}\\n", "\\n", "This notebook converts {instrument_name} output files to Allotrope Simple Model (ASM) JSON format." ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "# Install requirements (uncomment if needed)\\n", "# !pip install allotropy pandas openpyxl" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "import json\\n", "from pathlib import Path\\n", "import pandas as pd\\n", "\\n", "from allotropy.parser_factory import Vendor\\n", "from allotropy.to_allotrope import allotrope_from_file" ] }}, {{ "cell_type": "markdown", "metadata": {{}}, "source": [ "## Configuration\\n", "\\n", "Set your input file path here:" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "# Configure input/output paths\\n", "INPUT_FILE = \\"your_data_file.csv\\" # <-- Change this\\n", "OUTPUT_ASM = \\"output_asm.json\\"\\n", "OUTPUT_CSV = \\"output_flat.csv\\"" ] }}, {{ "cell_type": "markdown", "metadata": {{}}, "source": [ "## Convert to ASM" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "# Convert file to ASM\\n", "asm = allotrope_from_file(INPUT_FILE, Vendor.{vendor})\\n", "\\n", "# Save ASM JSON\\n", "with open(OUTPUT_ASM, 'w') as f:\\n", " json.dump(asm, f, indent=2, default=str)\\n", "\\n", "print(f\\"ASM saved to: {{OUTPUT_ASM}}\\")" ] }}, {{ "cell_type": "markdown", "metadata": {{}}, "source": [ "## Preview ASM Structure" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "# Show ASM structure\\n", "print(json.dumps(asm, indent=2, default=str)[:2000])" ] }}, {{ "cell_type": "markdown", "metadata": {{}}, "source": [ "## Flatten to CSV" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "def flatten_asm(asm, technique=\\"{technique}\\"):\\n", " rows = []\\n", " agg_key = f\\"{{technique}}-aggregate-document\\"\\n", " agg_doc = asm.get(agg_key, {{}})\\n", " \\n", " device = agg_doc.get(\\"device-system-document\\", {{}})\\n", " device_info = {{\\n", " \\"instrument_serial_number\\": device.get(\\"device-identifier\\"),\\n", " \\"instrument_model\\": device.get(\\"model-number\\"),\\n", " }}\\n", " \\n", " doc_key = f\\"{{technique}}-document\\"\\n", " for doc in agg_doc.get(doc_key, []):\\n", " meas_agg = doc.get(\\"measurement-aggregate-document\\", {{}})\\n", " common = {{\\n", " \\"analyst\\": meas_agg.get(\\"analyst\\"),\\n", " \\"measurement_time\\": meas_agg.get(\\"measurement-time\\"),\\n", " **device_info\\n", " }}\\n", " \\n", " for meas in meas_agg.get(\\"measurement-document\\", []):\\n", " row = {{**common}}\\n", " for key, value in meas.items():\\n", " clean_key = key.replace(\\"-\\", \\"_\\")\\n", " if isinstance(value, dict) and \\"value\\" in value:\\n", " row[clean_key] = value[\\"value\\"]\\n", " if \\"unit\\" in value:\\n", " row[f\\"{{clean_key}}_unit\\"] = value[\\"unit\\"]\\n", " else:\\n", " row[clean_key] = value\\n", " rows.append(row)\\n", " return rows\\n", "\\n", "# Flatten and save\\n", "rows = flatten_asm(asm)\\n", "df = pd.DataFrame(rows)\\n", "df.to_csv(OUTPUT_CSV, index=False)\\n", "print(f\\"CSV saved to: {{OUTPUT_CSV}}\\")" ] }}, {{ "cell_type": "code", "execution_count": null, "metadata": {{}}, "source": [ "# Preview flattened data\\n", "df.head()" ] }} ], "metadata": {{ "kernelspec": {{ "display_name": "Python 3", "language": "python", "name": "python3" }}, "language_info": {{ "name": "python", "version": "3.10.0" }} }}, "nbformat": 4, "nbformat_minor": 4 }}""" # Instrument metadata for templates INSTRUMENT_INFO = { "BECKMAN_VI_CELL_BLU": { "name": "Beckman Coulter Vi-CELL BLU", "technique": "cell-counting", "file_format": "CSV export from Vi-CELL BLU software with columns: Sample ID, Viable cells, Viability, Total cells, etc.", }, "BECKMAN_VI_CELL_XR": { "name": "Beckman Coulter Vi-CELL XR", "technique": "cell-counting", "file_format": "TXT or XLS/XLSX export from Vi-CELL XR with sample and measurement data", }, "THERMO_FISHER_NANODROP_EIGHT": { "name": "Thermo Fisher NanoDrop Eight", "technique": "spectrophotometry", "file_format": "TSV or TXT export with Sample Name, Nucleic Acid Conc., A260, A280, 260/280 ratio", }, "THERMO_FISHER_NANODROP_ONE": { "name": "Thermo Fisher NanoDrop One", "technique": "spectrophotometry", "file_format": "CSV or XLSX export with spectrophotometry measurements", }, "MOLDEV_SOFTMAX_PRO": { "name": "Molecular Devices SoftMax Pro", "technique": "plate-reader", "file_format": "TXT export from SoftMax Pro with plate reader data", }, "BMG_MARS": { "name": "BMG MARS (CLARIOstar)", "technique": "plate-reader", "file_format": "CSV or TXT export from BMG MARS with Well, Content, Conc., Mean, SD, CV columns", }, "AGILENT_GEN5": { "name": "Agilent Gen5 (BioTek)", "technique": "plate-reader", "file_format": "XLSX export from Gen5 software", }, "APPBIO_QUANTSTUDIO": { "name": "Applied Biosystems QuantStudio", "technique": "pcr", "file_format": "XLSX export with qPCR data including Well, Sample Name, Target Name, CT values", }, } def generate_script(vendor: str, output_path: str) -> None: """Generate standalone Python script for given vendor.""" info = INSTRUMENT_INFO.get( vendor, { "name": vendor.replace("_", " ").title(), "technique": "generic", "file_format": "Instrument output file", }, ) script = SCRIPT_TEMPLATE.format( instrument_name=info["name"], timestamp=datetime.now().isoformat(), vendor=vendor, script_name=Path(output_path).name, file_format_description=info["file_format"], technique=info["technique"], ) with open(output_path, "w") as f: f.write(script) def generate_notebook(vendor: str, output_path: str) -> None: """Generate Jupyter notebook for given vendor.""" info = INSTRUMENT_INFO.get( vendor, { "name": vendor.replace("_", " ").title(), "technique": "generic", "file_format": "Instrument output file", }, ) notebook = NOTEBOOK_TEMPLATE.format( instrument_name=info["name"], timestamp=datetime.now().isoformat(), vendor=vendor, technique=info["technique"], ) with open(output_path, "w") as f: f.write(notebook) def main(): import argparse parser = argparse.ArgumentParser( description="Export parser code for data engineers" ) parser.add_argument("--vendor", help="Vendor enum name (e.g., VI_CELL_BLU)") parser.add_argument("--output", "-o", help="Output file path") parser.add_argument( "--format", choices=["script", "notebook"], default="script", help="Output format (default: script)", ) parser.add_argument( "--list-vendors", action="store_true", help="List supported vendors" ) args = parser.parse_args() if args.list_vendors: print("Supported vendors:") for vendor in INSTRUMENT_INFO.keys(): print(f" {vendor}") return 0 if not args.vendor or not args.output: parser.error("--vendor and --output are required when not using --list-vendors") vendor = args.vendor.upper() if args.format == "notebook": generate_notebook(vendor, args.output) else: generate_script(vendor, args.output) print(f"Parser code exported to: {args.output}") return 0 if __name__ == "__main__": sys.exit(main())