Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .claude/skills/parser-generator/scripts/create_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class {class_name}(VendorParser[Data, Model]):
DISPLAY_NAME = "{display_name}"
RELEASE_STATE = ReleaseState.WORKING_DRAFT
SUPPORTED_EXTENSIONS = "{extension}"
SUPPORTED_DETECTION_MODES = None # TODO: Set detection modes (e.g. "Absorbance, Fluorescence")
SCHEMA_MAPPER = Mapper

def create_data(self, named_file_contents: NamedFileContents) -> Data:
Expand Down Expand Up @@ -367,10 +368,11 @@ def main():
print("\n📝 Next steps:")
print(f"1. Edit {parser_name}_reader.py to implement file parsing")
print(f"2. Edit {parser_name}_structure.py to map data to schema")
print(f"3. Add test data to {testdata_dir}")
print(f"4. Run tests: hatch run test:pytest tests/parsers/{parser_name}/")
print("5. Register parser in src/allotropy/parser_factory.py")
print("6. Update README.md with parser information")
print(f"3. Set SUPPORTED_DETECTION_MODES in {parser_name}_parser.py")
print(f"4. Add test data to {testdata_dir}")
print(f"5. Run tests: hatch run test:pytest tests/parsers/{parser_name}/")
print("6. Register parser in src/allotropy/parser_factory.py")
print("7. Run: hatch run scripts:update-instrument-table")


if __name__ == "__main__":
Expand Down
8 changes: 6 additions & 2 deletions .claude/skills/parser-generator/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ The script dynamically scans your local allotropy repository for all available s
Use the `create_parser.py` script to generate the complete parser:

```bash
python scripts/create_parser.py <parser_name> <vendor_name> --example <example_file>
python scripts/create_parser.py <parser_name> <schema_regex> --display_name "Vendor Instrument" --detection_modes "Absorbance, Fluorescence"
```

The `--detection_modes` flag sets the `SUPPORTED_DETECTION_MODES` class attribute, which populates the instruments table. Use comma-separated values for multiple modes (e.g. `"Absorbance, Fluorescence, Luminescence"`). Omit for instruments without detection (liquid handlers).

### Parser Structure Created

```
Expand Down Expand Up @@ -122,6 +124,7 @@ Generate `VendorParser` subclass with:
- `DISPLAY_NAME` - User-friendly instrument name
- `RELEASE_STATE` - Start with `ReleaseState.WORKING_DRAFT`
- `SUPPORTED_EXTENSIONS` - File extensions (from analysis)
- `SUPPORTED_DETECTION_MODES` - Detection modes the parser supports (e.g. `"Absorbance, Fluorescence"`) or `None` for instruments without detection (e.g. liquid handlers). This populates the "Supported Detection Modes" column in the supported instruments table.
- `SCHEMA_MAPPER` - Reference to schema mapper
- `create_data()` - Orchestrate reader + structure → Data

Expand Down Expand Up @@ -196,10 +199,11 @@ _VENDOR_TO_PARSER: dict[Vendor, type[VendorParser]] = {
- [ ] Confirm or select schema with `list_schemas.py`
- [ ] Generate parser using `create_parser.py`
- [ ] Review and adjust generated code
- [ ] Set `SUPPORTED_DETECTION_MODES` to the correct value for the instrument
- [ ] Add example test data to `testdata/`
- [ ] Run tests and validate output
- [ ] Register parser in `parser_factory.py`
- [ ] Update README with parser info
- [ ] Run `hatch run scripts:update-instrument-table` to regenerate the supported instruments table
- [ ] Update `RELEASE_STATE` when stable

## Key Design Principles
Expand Down
19 changes: 16 additions & 3 deletions scripts/create_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ def add_to_parser_factory(parser_name: str, enum_name: str, class_name: str) ->


def create_parser(
name: str, schema_path: Path, display_name: str | None = None
name: str,
schema_path: Path,
display_name: str | None = None,
detection_modes: str | None = None,
) -> None:
name = name.replace(" ", "_").replace("-", "_").lower()
enum_name = name.upper()
Expand All @@ -84,6 +87,8 @@ def create_parser(
Path(tests_dir, "__init__.py").touch()
Path(tests_dir, "testdata").mkdir()

detection_modes_value = f'"{detection_modes}"' if detection_modes else "None"

template_replacements = {
"PARSER_NAME": name,
"CLASS_NAME_PREFIX": class_name_prefix,
Expand All @@ -92,6 +97,7 @@ def create_parser(
"MODEL_IMPORT_PATH": import_path,
"MAPPER_IMPORT_PATH": import_path.replace("models", "schema_mappers"),
"MANIFEST": manifest,
"SUPPORTED_DETECTION_MODES": detection_modes_value,
}
write_template_files(
parser_dir,
Expand Down Expand Up @@ -119,8 +125,15 @@ def create_parser(
@click.option(
"--display_name", help="Display name for parser, defaults to title case of NAME"
)
@click.option(
"--detection_modes",
help="Comma-separated detection modes (e.g. 'Absorbance, Fluorescence')",
)
def _create_parser(
name: str, schema_regex: str, display_name: str | None = None
name: str,
schema_regex: str,
display_name: str | None = None,
detection_modes: str | None = None,
) -> None:
"""Create parser with name NAME for schema found with SCHEMA_REGEX."""
if "parser" in name.lower():
Expand All @@ -135,7 +148,7 @@ def _create_parser(
msg = f"Found more than one schema with schema regex, please narrow down: {[str(p) for p in schema_paths]}"
raise ValueError(msg)

create_parser(name, schema_paths[0], display_name)
create_parser(name, schema_paths[0], display_name, detection_modes)

from allotropy.parser_factory import update_supported_instruments

Expand Down
1 change: 1 addition & 0 deletions scripts/templates/parser_template
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class $CLASS_NAME_PREFIX$Parser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.WORKING_DRAFT
SUPPORTED_EXTENSIONS = $CLASS_NAME_PREFIX$Reader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = $SUPPORTED_DETECTION_MODES$
SCHEMA_MAPPER = Mapper

def create_data(self, named_file_contents: NamedFileContents) -> Data:
Expand Down
16 changes: 10 additions & 6 deletions src/allotropy/parser_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ def supported_extensions(self) -> list[str]:
for ext in self.get_parser().SUPPORTED_EXTENSIONS.split(",")
]

@property
def supported_detection_modes(self) -> str | None:
return self.get_parser().SUPPORTED_DETECTION_MODES

@property
def manifests(self) -> list[str]:
# NOTE: this is a list because eventually parsers will support multiple schemas as they are upgraded.
Expand Down Expand Up @@ -355,16 +359,16 @@ def get_table_contents() -> str:
continue
table_data[vendor.technique].append(vendor)

contents += '[cols="4*^.^"]\n'
contents += '[cols="5*^.^"]\n'
contents += "|===\n"
contents += (
"|Instrument Category|Instrument Software|Release Status|Exported ASM Schema\n"
)
contents += "|Instrument Category|Instrument Software|Release Status|Exported ASM Schema|Supported Detection Modes\n"
for technique in sorted(table_data):
vendors = table_data[technique]
contents += f".{len(vendors)}+|{technique}|{vendors[0].display_name}|{vendors[0].release_state}|{vendors[0].asm_versions[0]}\n"
modes = vendors[0].supported_detection_modes or "N/A"
contents += f".{len(vendors)}+|{technique}|{vendors[0].display_name}|{vendors[0].release_state}|{vendors[0].asm_versions[0]}|{modes}\n"
for vendor in vendors[1:]:
contents += f"|{vendor.display_name}|{vendor.release_state}|{vendor.asm_versions[0]}\n"
modes = vendor.supported_detection_modes or "N/A"
contents += f"|{vendor.display_name}|{vendor.release_state}|{vendor.asm_versions[0]}|{modes}\n"
contents += "|==="

return contents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AgilentGen5Parser(VendorParser[Data, Model]):
DISPLAY_NAME = "Agilent Gen5"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = AgilentGen5Reader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Absorbance, Fluorescence, Luminescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class AgilentGen5ImageParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Agilent Gen5 Image"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = AgilentGen5Reader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Optical Imaging"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AgilentOpenLabCDSParser(VendorParser[Data, Model]):
DISPLAY_NAME = constants.DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "rslt"
SUPPORTED_DETECTION_MODES = "Absorbance, Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AgilentTapestationAnalysisParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Agilent TapeStation Analysis"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "xml"
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class AppbioAbsoluteQParser(VendorParser[Data, Model]):
DISPLAY_NAME = "AppBio AbsoluteQ"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = AppbioAbsoluteQReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AppBioQuantStudioParser(VendorParser[Data, Model]):
DISPLAY_NAME = "AppBio QuantStudio RT-PCR"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = AppBioQuantStudioReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class AppBioQuantStudioDesignandanalysisParser(VendorParser[Data, Model]):
DISPLAY_NAME = "AppBio QuantStudio Design & Analysis"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = DesignQuantstudioReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class BDFACSDivaParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "xml"
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class BeckmanCoulterBiomekParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BeckmanCoulterBiomekReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = None
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class BeckmanEchoCherryPickParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BeckmanEchoCherryPickReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = None
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class BeckmanEchoPlateReformatParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BeckmanEchoPlateReformatReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = None
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class PharmSpecParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Beckman Coulter PharmSpec"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BeckmanPharmspecReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Light Obscuration"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ViCellBluParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Beckman Coulter Vi-Cell BLU"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = ViCellBluReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Brightfield"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ViCellXRParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Beckman Coulter Vi-Cell XR"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "txt,xls,xlsx"
SUPPORTED_DETECTION_MODES = "Brightfield"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BenchlingChromeleonParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BenchlingChromeleonReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Absorbance, Fluorescence, Conductivity"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BenchlingEmpowerParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BenchlingEmpowerReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Absorbance"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BioradBioplexParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Bio-Rad Bio-Plex Manager"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "xml"
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BmgLabtechSmartControlParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = BmgLabtechSmartControlReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
1 change: 1 addition & 0 deletions src/allotropy/parsers/bmg_mars/bmg_mars_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BmgMarsParser(VendorParser[Data, Model]):
DISPLAY_NAME = "BMG Labtech MARS"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "csv"
SUPPORTED_DETECTION_MODES = "Absorbance, Fluorescence, Luminescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
1 change: 1 addition & 0 deletions src/allotropy/parsers/cfxmaestro/cfxmaestro_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CfxmaestroParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = CFXMaestroReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ChemometecNcViewParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = ChemometecNcViewReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ChemometecNucleoviewParser(VendorParser[Data, Model]):
DISPLAY_NAME = "ChemoMetec Nucleoview"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = NucleoviewReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Dark Field"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class CtlImmunospotParser(VendorParser[Data, Model]):
DISPLAY_NAME = "CTL ImmunoSpot"
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = CtlImmunospotReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Optical Imaging"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class CytivaBiacoreInsightParser(VendorParser[MapperData, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = CytivaBiacoreInsightReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Surface Plasmon Resonance"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CytivaBiacoreT200ControlParser(VendorParser[MapperData, Model]):
DISPLAY_NAME = constants.DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "blr"
SUPPORTED_DETECTION_MODES = "Surface Plasmon Resonance"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class CytivaBiacoreT200EvaluationParser(VendorParser[Data, Model]):
DISPLAY_NAME = constants.DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "bme"
SUPPORTED_DETECTION_MODES = "Surface Plasmon Resonance"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CytivaUnicornParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "zip"
SUPPORTED_DETECTION_MODES = "Absorbance"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ExampleWeylandYutaniParser(VendorParser[Data, Model]):
DISPLAY_NAME = "Example Weyland Yutani"
RELEASE_STATE = ReleaseState.WORKING_DRAFT
SUPPORTED_EXTENSIONS = ExampleWeylandYutaniReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
1 change: 1 addition & 0 deletions src/allotropy/parsers/flowjo/flowjo_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class FlowjoParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = "wsp"
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class LuminexIntelliflexParser(VendorParser[Data, Model]):
DISPLAY_NAME = DISPLAY_NAME
RELEASE_STATE = ReleaseState.RECOMMENDED
SUPPORTED_EXTENSIONS = LuminexXponentReader.SUPPORTED_EXTENSIONS
SUPPORTED_DETECTION_MODES = "Fluorescence"
SCHEMA_MAPPER = Mapper

@classmethod
Expand Down
Loading
Loading