Built-in stages#
The five funnel stages. Each is a thin class whose science lives in a self-contained runner script.
The compose stage: one element-combination seed -> N candidate compositions (3.1).
The thin Stage half of the pair; the science lives entirely in
compose_runner.py (self-contained: stdlib + smact, zero matmelt
imports), which this class ships to the backend as an input artifact of every
job. This module itself imports no science – it builds job descriptions
and judges results, nothing more, so it loads on any machine.
The seed needs no input file: its id is the data. Seed ids come from
core/seeds.py ("Cl-Na", "Cl-Na-O" – sorted element symbols joined
by -), and build_jobspec decodes the id back into --elements
arguments. A malformed id is refused clearly by validate_inputs.
Fan-out contract: the runner writes children.json (declared in
produces) plus one children/<formula>/composition.json per
charge-balanced composition, all in the job’s cwd; the orchestrator adopts
the children and retires the seed as EXPANDED. Zero children is a valid
result – some combinations simply have no charge-balanced compound.
- matmelt.core.stages.compose.RUNNER_NAME = 'compose_runner.py'#
The self-contained script shipped with every compose job. Lives next to this module inside the installed package; staged into the job’s cwd by the backend, so the command references it by bare name.
- class matmelt.core.stages.compose.ComposeStage(python='python3', threshold=2, max_unary_atoms=None)[source]#
Bases:
StageTurn one element-combination seed into candidate-composition children.
- Parameters:
python (
str, default:'python3') – Interpreter to invoke on the backend this stage is wired to (the user’s own environment holds the science stack – never bundled). Plainpython3by default; a config option, never a hardcoded compute detail.threshold (
int, default:2) – Maximum stoichiometric coefficient SMACT explores. Higher finds more exotic stoichiometries and costs combinatorially more.max_unary_atoms (
int|None, default:None) – Cap for single-element seeds: the runner emits the elemental compositionsX1..Xn(allotrope stoichiometries – SMACT’s charge balance cannot apply to one element).None(the default) followsthreshold; set it separately to reach e.g.C60without exploding SMACT’s multi-element search.
- Raises:
StageError – For any invalid option. Every value is checked at construction, so a config mistake surfaces when the run is wired rather than as N failed jobs later.
-
fan_out:
bool= True# Whether this stage expands its item into children (see class docstring) instead of advancing it. Class attribute – override per subclass.
-
runtime_requirements:
tuple[Requirement,...] = (Requirement(module='smact', pip_name='smact', purpose='charge-balance filtering of element combinations (compose runner)'),)# The science packages this stage’s runner imports, checked by the preflight before any work starts. Class attribute – a science stage overrides it; the default (nothing beyond stdlib) is itself a contract, e.g. extract’s.
- property name: str#
Stable stage identifier – also the per-item idempotency key in state.
- Returns:
The canonical name (
"compose","screen", …). It is what a config’sstages:block names, what the stage registry maps, and what the state store’s completion ledger records per item, so completing a stage twice is a no-op.- Return type:
- property requires: StageInputSpec#
What must be true of an item’s directory before this stage runs.
- Returns:
The declared input contract, enforced by
validate_inputs.- Return type:
StageInputSpec
- property produces: StageOutputSpec#
What this stage guarantees an item’s directory contains on success.
- Returns:
The declared output contract, enforced by
validate_outputs.- Return type:
StageOutputSpec
- validate_inputs(item)[source]#
Refuse clearly when the seed id does not decode into element symbols.
Extends the base file check, which is vacuous here: this stage declares no input files, because the seed id is the data.
- Parameters:
item (
WorkItem) – The seed about to be composed.- Raises:
StageInputError – When the id is not element symbols joined by
-.- Return type:
- static elements_of(item)[source]#
Decode a seed id into its element symbols.
- Parameters:
item (
WorkItem) – The seed, whoseidlooks like"Cl-Na-O".- Returns:
The symbols, order preserved as-is – seed ids from
core/seeds.pyare already sorted.- Return type:
- Raises:
StageInputError – Naming exactly what is wrong: an empty or non-symbol part (symbols are
[A-Z][a-z]{0,2}), or a duplicated element.
- build_jobspec(item)[source]#
Build the portable job description for one item.
The only place in a stage where a command is constructed. The spec names no host, queue, or transport; paths point into
item.dir; any stage options (model path, device, thresholds) were injected at stage construction from config – never hardcoded here.- Parameters:
item (
WorkItem) – The item to build a job for. Its inputs have already passedvalidate_inputs.- Returns:
A portable job description any backend can execute.
- Return type:
JobSpec- Raises:
StageError – If the stage is misconfigured such that no job can be built at all – a whole-stage fault, never a per-item verdict.
- interpret_result(item, result)[source]#
Judge a finished compose job.
- Parameters:
- Returns:
okon exit 0, which includes a fan-out into zero children. Anything else is a fault carrying the runner’s stderr tail, and never terminal: a compose failure is an environment problem – missing smact, a broken interpreter – that a retry can genuinely resolve once the user fixes the venv. The runner reserves science judgments for exit 0.- Return type:
StageVerdict
The structure stage: one composition -> M candidate crystal structures (3.2).
The thin Stage half of the pair; the science lives entirely in
structure_runner.py (self-contained: stdlib + pyxtal, zero
matmelt imports), which this class ships to the backend as an input
artifact of every job. This module itself imports no science – it builds
job descriptions and judges results, nothing more, so it loads on any machine.
The input is the composition.json a compose child carries (formula,
elements, stoichiometry). validate_inputs goes beyond file presence and
checks the file actually decodes into that shape – someone starting the
pipeline mid-way with a hand-made composition is refused clearly, before a
job is built, never with silent garbage or a confusing runner crash.
Fan-out contract: the runner tries every group of each requested
dimensionality (--dims; 3D=230 space, 2D=80 layer, 1D=75 rod groups) and
writes children.json (declared in produces) plus one
children/<Formula>_<DIM>-<SG>/ directory per valid candidate (holding
structure.cif + structure.json), all in the job’s cwd; the
orchestrator adopts the children and retires the composition as EXPANDED.
Zero children is a valid result – some compositions satisfy no symmetry
constraints at all.
- matmelt.core.stages.structure.RUNNER_NAME = 'structure_runner.py'#
The self-contained script shipped with every structure job. Lives next to this module inside the installed package; staged into the job’s cwd by the backend, so the command references it by bare name.
- matmelt.core.stages.structure.COMPOSITION_NAME = 'composition.json'#
The input file a compose child carries – declared as an input artifact so the Phase-4 staging transport knows to push it (it already sits in the item dir locally; backends stage in-place inputs as a no-op).
- class matmelt.core.stages.structure.StructureStage(python='python3', dimensions=('3D',))[source]#
Bases:
StagePyXtal-generate symmetry-constrained candidates for one composition.
- Parameters:
python (
str, default:'python3') – Interpreter to invoke on the backend this stage is wired to (the user’s own environment holds the science stack – never bundled). Plainpython3by default; a config option, never a hardcoded compute detail.dimensions (
Any, default:('3D',)) – Which dimensionalities the runner generates – any of3D(230 space groups),2D(80 layer groups),1D(75 rod groups). A single tag string or a list; defaults to("3D",)(the pre-existing behaviour). A config option, forwarded to the runner as--dims.
- Raises:
StageError – For any invalid option, so a config mistake surfaces when the run is wired rather than as N failed jobs later.
-
fan_out:
bool= True# Whether this stage expands its item into children (see class docstring) instead of advancing it. Class attribute – override per subclass.
-
runtime_requirements:
tuple[Requirement,...] = (Requirement(module='pyxtal', pip_name='pyxtal', purpose='symmetry-constrained random crystal generation (structure runner)'),)# The science packages this stage’s runner imports, checked by the preflight before any work starts. Class attribute – a science stage overrides it; the default (nothing beyond stdlib) is itself a contract, e.g. extract’s.
- property name: str#
Stable stage identifier – also the per-item idempotency key in state.
- Returns:
The canonical name (
"compose","screen", …). It is what a config’sstages:block names, what the stage registry maps, and what the state store’s completion ledger records per item, so completing a stage twice is a no-op.- Return type:
- property requires: StageInputSpec#
What must be true of an item’s directory before this stage runs.
- Returns:
The declared input contract, enforced by
validate_inputs.- Return type:
StageInputSpec
- property produces: StageOutputSpec#
What this stage guarantees an item’s directory contains on success.
- Returns:
The declared output contract, enforced by
validate_outputs.- Return type:
StageOutputSpec
- validate_inputs(item)[source]#
Refuse clearly when
composition.jsondoes not decode into a composition.Extends the base file-presence check with a schema check, so a mid-pipeline start on a hand-made composition is refused before any job.
- Parameters:
item (
WorkItem) – The composition about to be turned into structures.- Raises:
StageInputError – When the file is missing, empty, or does not satisfy the schema.
- Return type:
- static composition_of(item)[source]#
Read and check the item’s
composition.json.The same rules are enforced by the runner, which must stay debuggable standalone; checking here refuses a bad item before any job is built or submitted.
- Parameters:
item (
WorkItem) – The item whose directory holds the file.- Returns:
The decoded composition.
- Return type:
- Raises:
StageInputError – Naming exactly what is wrong: unreadable or malformed JSON, a missing required key, a bad formula (it names child directories, so it must be alphanumeric), or elements and stoichiometry that do not line up.
- build_jobspec(item)[source]#
Build the portable job description for one item.
The only place in a stage where a command is constructed. The spec names no host, queue, or transport; paths point into
item.dir; any stage options (model path, device, thresholds) were injected at stage construction from config – never hardcoded here.- Parameters:
item (
WorkItem) – The item to build a job for. Its inputs have already passedvalidate_inputs.- Returns:
A portable job description any backend can execute.
- Return type:
JobSpec- Raises:
StageError – If the stage is misconfigured such that no job can be built at all – a whole-stage fault, never a per-item verdict.
- interpret_result(item, result)[source]#
Judge a finished structure job.
- Parameters:
- Returns:
okon exit 0, which includes a fan-out into zero children. Anything else is a fault carrying the runner’s stderr tail, and never terminal: a structure failure is an environment problem – missing pyxtal, a broken interpreter – that a retry can genuinely resolve. Per-space-group science rejections are absorbed inside the runner’s exit 0 and counted in its report.- Return type:
StageVerdict
The screen stage: one candidate structure -> keep (RELAXED) or drop (3.3).
The thin Stage half of the pair; the science lives entirely in
screen_runner.py (self-contained: stdlib + mace/ase, zero
matmelt imports), which this class ships to the backend as an input
artifact of every job. This module itself imports no science – it builds
job descriptions and judges results, nothing more, so it loads on any machine.
The runner MACE vc-relaxes the item’s structure.cif and writes a verdict
screen_result.json (category RELAXED or FAILED + reasons +
metrics); on RELAXED it also writes relaxed.cif. 1:1 – this stage is
NOT fan-out: the item either advances or is screened out.
This is the funnel’s first terminal science verdict. A parsed FAILED
verdict is a deterministic rejection – re-running the same relaxation with
the same model and settings reproduces it – so interpret_result returns
terminal=True: the item fails on the spot and spends no retry budget
(G-2 verdict-vs-fault). Everything else (non-zero exit, missing/corrupt
verdict) stays a retryable fault: missing mace is an environment problem,
and a crashed relaxation could be an OOM – when in doubt, fault.
Model path, device, dtype and the relaxation knobs come from config via the
constructor – never hardcoded (the 3.6 preflight/auto-download offer hooks
into the same model_path option).
- matmelt.core.stages.screen.RUNNER_NAME = 'screen_runner.py'#
The self-contained script shipped with every screen job. Lives next to this module inside the installed package; staged into the job’s cwd by the backend, so the command references it by bare name.
- matmelt.core.stages.screen.STRUCTURE_NAME = 'structure.cif'#
The input file a structure child carries – declared as an input artifact so the Phase-4 staging transport knows to push it (it already sits in the item dir locally; backends stage in-place inputs as a no-op).
- matmelt.core.stages.screen.VERDICT_NAME = 'screen_result.json'#
The verdict file the runner always writes on a completed screening.
- matmelt.core.stages.screen.RELAXED_NAME = 'relaxed.cif'#
The relaxed structure the runner writes on a RELAXED verdict.
- class matmelt.core.stages.screen.ScreenStage(python='python3', model_path='', model_size='medium', device='cpu', dtype='float64', fmax=0.05, max_steps=200)[source]#
Bases:
StageMACE vc-relax one candidate structure and keep or screen it out.
- Parameters:
python (
str, default:'python3') – Interpreter to invoke on the backend this stage is wired to (the user’s own environment holds the science stack – never bundled).model_path (
str, default:'') – Local MACE model file. Empty (default) = the MACE-MP foundation model, which mace downloads to its cache on first use – the runner discloses that before loading (3.6 adds the up-front consent offer).model_size (
str, default:'medium') – Foundation model size whenmodel_pathis empty.device (
str, default:'cpu') – Compute device passed to MACE (“cpu” | “cuda” | “mps” | “auto”).dtype (
str, default:'float64') – Torch dtype; float64 (default) is required for a reliable stress tensor in vc-relax.fmax (
float, default:0.05) – Force convergence target for the relaxation (eV/A).max_steps (
int, default:200) – Maximum optimizer steps for the relaxation.
- Raises:
StageError – For any invalid option, so a config mistake surfaces when the run is wired rather than as N failed jobs later.
Notes
The physical-sanity bounds that decide a verdict – bond lengths, stress, volume change, the energy window – are deliberately not options. They live as constants in the runner so verdicts stay comparable across a whole database.
-
runtime_requirements:
tuple[Requirement,...] = (Requirement(module='mace', pip_name='mace-torch', purpose='MACE force-field vc-relaxation (screen runner; brings ase and torch)'),)# The science packages this stage’s runner imports, checked by the preflight before any work starts. Class attribute – a science stage overrides it; the default (nothing beyond stdlib) is itself a contract, e.g. extract’s.
- property name: str#
Stable stage identifier – also the per-item idempotency key in state.
- Returns:
The canonical name (
"compose","screen", …). It is what a config’sstages:block names, what the stage registry maps, and what the state store’s completion ledger records per item, so completing a stage twice is a no-op.- Return type:
- property requires: StageInputSpec#
What must be true of an item’s directory before this stage runs.
- Returns:
The declared input contract, enforced by
validate_inputs.- Return type:
StageInputSpec
- property produces: StageOutputSpec#
What this stage guarantees an item’s directory contains on success.
- Returns:
The declared output contract, enforced by
validate_outputs.- Return type:
StageOutputSpec
- build_jobspec(item)[source]#
Build the portable job description for one item.
The only place in a stage where a command is constructed. The spec names no host, queue, or transport; paths point into
item.dir; any stage options (model path, device, thresholds) were injected at stage construction from config – never hardcoded here.- Parameters:
item (
WorkItem) – The item to build a job for. Its inputs have already passedvalidate_inputs.- Returns:
A portable job description any backend can execute.
- Return type:
JobSpec- Raises:
StageError – If the stage is misconfigured such that no job can be built at all – a whole-stage fault, never a per-item verdict.
- interpret_result(item, result)[source]#
Judge the finished screening from its exit code and verdict file.
The funnel’s first terminal science verdict.
- Parameters:
- Returns:
One of four outcomes:
A non-zero or unknown exit is a retryable fault – missing mace, an out-of-memory kill, a broken interpreter.
Exit 0 with a
RELAXEDverdict advances the item.Exit 0 with a
FAILEDverdict is a terminal science rejection: the same relaxation reproduces it, so no retry budget is spent and the recorded reason carries the failed checks.Exit 0 with a missing, corrupt, or unknown verdict means the runner broke its contract. That is a fault, not a rejection – on unknown ground, let the budget apply.
- Return type:
StageVerdict
The calculate stage: one screened-in structure -> DFTB+ result via the user’s AiiDA (6.5).
The thin Stage half of the pair; everything AiiDA lives entirely in
calculate_runner.py (self-contained: stdlib + aiida/aiida_mydftbp/ase,
zero matmelt imports), which this class ships to the backend as an
input artifact of every job. This module itself imports no science and no
AiiDA – it builds job descriptions and judges results, nothing more, so it
loads on any machine.
The runner submits one mydftbp.single WorkChain per material through the
user’s own AiiDA installation (aiida-core + the aiida-mydftbp plugin,
whose WorkChain does the self-healing retries itself – exit codes 300-370,
SCC/geometry/eigenvalue healing ladders, warm starts). Our package is the
launcher/extractor around it and never ships or imports AiiDA at package
level (the clean-room amendment: these are declared user-owned runtime
requirements, exactly the smact/pyxtal/mace model). 1:1, optional, and
skippable – configs without a calculate: entry behave exactly as before.
The runner writes calc_result.json (converged flag, WorkChain exit
status, every healing attempt with SKF-stripped parameters). Verdicts follow
the G-2 doctrine: a WorkChain that terminated with a healing-exhausted or
configuration-fatal exit status (310, 320, 330, 340, 350, 360) is a
terminal science verdict – the plugin already retried everything it
knows, so re-running reproduces it. Transient statuses (300, 370), unknown
statuses, and a broken/absent result file all stay retryable faults – when
in doubt, fault.
validate_inputs refuses clearly when screen_result.json does not say
RELAXED (extract’s precedent): calculate serves only screened-in
materials, and a mid-pipeline start on a screened-out item is told exactly
why before any job runs.
Every option comes from config via the constructor – code label, AiiDA profile, SKF locations, k-mesh, walltime, queue – never hardcoded.
- matmelt.core.stages.calculate.RUNNER_NAME = 'calculate_runner.py'#
The self-contained script shipped with every calculate job. Lives next to this module inside the installed package; staged into the job’s cwd by the backend, so the command references it by bare name.
- matmelt.core.stages.calculate.RELAXED_NAME = 'relaxed.cif'#
Inputs from the screen stage – declared as input artifacts so the staging transport knows to push them (in-place locally; staged as a no-op).
- matmelt.core.stages.calculate.RESULT_NAME = 'calc_result.json'#
The result document this stage produces (attempt history included).
- class matmelt.core.stages.calculate.CalculateStage(code_label='', profile=None, skf_label='ptbp_complete_set', skf_prefix='', skf_local_path=None, kpoints=None, max_wallclock_seconds=3600, queue_name=None, poll_interval_s=30.0, python='python3')[source]#
Bases:
StageRun one screened-in material through DFTB+ via the user’s own AiiDA.
- Parameters:
code_label (
str, default:'') – Required. Label of the user’s configured AiiDA code for DFTB+ (e.g."dftbplus@cluster") –verdi code listshows them.profile (
str|None, default:None) – AiiDA profile to load;None(default) uses the user’s default profile.skf_label (
str, default:'ptbp_complete_set') – Label of the stored Slater-KosterFolderDatanode; ensured automatically by the runner (find-or-create, loud failure).skf_prefix (
str, default:'') – Required. Absolute directory of the.skfparameter files on the machine where DFTB+ actually executes – baked into the HSD input the WorkChain runs.skf_local_path (
str|None, default:None) – Local directory of.skffiles used to create the labelled FolderData node when no node withskf_labelexists yet.None(default) means find-only: a missing node is a loud, actionable failure, never a silent stall.kpoints (
list[int] |None, default:None) – SupercellFolding k-mesh diagonal, exactly three integers >= 1.max_wallclock_seconds (
int, default:3600) – Walltime request per WorkChain attempt, in seconds.queue_name (
str|None, default:None) – Scheduler queue/partition for the WorkChain’s jobs;None(default) uses the code’s computer default.poll_interval_s (
float, default:30.0) – Seconds between WorkChain status polls by the runner.python (
str, default:'python3') – Interpreter to invoke on the backend this stage is wired to – the user’s AiiDA environment (their venv holds aiida-core, aiida-mydftbp and ase; never bundled).
- Raises:
StageError – For any invalid option. The two required ones,
code_labelandskf_prefix, get messages namingverdi code listand the remote SKF directory respectively, so the fix is actionable at wiring time rather than after N failed WorkChains.
-
runtime_requirements:
tuple[Requirement,...] = (Requirement(module='aiida', pip_name='aiida-core', purpose='AiiDA engine the calculate runner submits WorkChains through'), Requirement(module='aiida_mydftbp', pip_name='aiida-mydftbp', purpose='DFTB+ plugin providing the self-healing mydftbp.single WorkChain'), Requirement(module='ase', pip_name='ase', purpose='CIF -> DFTB+ .gen geometry conversion in the calculate runner'))# The science packages this stage’s runner imports, checked by the preflight before any work starts. Class attribute – a science stage overrides it; the default (nothing beyond stdlib) is itself a contract, e.g. extract’s.
- property name: str#
Stable stage identifier – also the per-item idempotency key in state.
- Returns:
The canonical name (
"compose","screen", …). It is what a config’sstages:block names, what the stage registry maps, and what the state store’s completion ledger records per item, so completing a stage twice is a no-op.- Return type:
- property requires: StageInputSpec#
What must be true of an item’s directory before this stage runs.
- Returns:
The declared input contract, enforced by
validate_inputs.- Return type:
StageInputSpec
- property produces: StageOutputSpec#
What this stage guarantees an item’s directory contains on success.
- Returns:
The declared output contract, enforced by
validate_outputs.- Return type:
StageOutputSpec
- validate_inputs(item)[source]#
Refuse clearly unless the screening verdict actually says RELAXED.
Extends the base file-presence check by reading the verdict: DFTB+ time is expensive, and calculate serves only screened-in materials.
- Parameters:
item (
WorkItem) – The material about to be calculated.- Raises:
StageInputError – When a required file is missing or empty, when the screening verdict is unreadable or not valid JSON, or when it names any category other than
RELAXED.- Return type:
- build_jobspec(item)[source]#
Build the portable job description for one item.
The only place in a stage where a command is constructed. The spec names no host, queue, or transport; paths point into
item.dir; any stage options (model path, device, thresholds) were injected at stage construction from config – never hardcoded here.- Parameters:
item (
WorkItem) – The item to build a job for. Its inputs have already passedvalidate_inputs.- Returns:
A portable job description any backend can execute.
- Return type:
JobSpec- Raises:
StageError – If the stage is misconfigured such that no job can be built at all – a whole-stage fault, never a per-item verdict.
- interpret_result(item, result)[source]#
Judge the finished calculation from its exit code and result file.
- Parameters:
- Returns:
One of four outcomes:
A non-zero or unknown runner exit is a retryable fault – missing aiida, a stopped daemon, an unloadable profile.
Exit 0 with
converged: trueadvances the item.Exit 0 with a terminal WorkChain exit status (310, 320, 330, 340, 350, 360) is a terminal science verdict: the plugin’s own self-healing already retried everything it knows, or the failure is configuration-fatal, so re-running reproduces it.
Exit 0 with a transient status (300, 370), an unknown status, or a missing or corrupt result document is a fault – on unknown ground, let the budget apply.
- Return type:
StageVerdict
The extract stage: one screened-in structure -> OPTIMADE-shaped record (3.4).
The thin Stage half of the pair; the logic lives entirely in
extract_runner.py – which, unlike the other runners, is stdlib only:
extraction is bookkeeping, not science, so it runs in any python anywhere
with nothing to install (there is no exit-3 path).
1:1 and the funnel’s terminal stage: the runner reads the item’s
relaxed.cif (parsed by its own minimal CIF reader – geometry is
best-effort, a parse failure degrades to a geometry-less record, never a
crash), screen_result.json (must say RELAXED: extract serves only
screened-in materials), the optional structure.json provenance from
the structure stage and the optional calc_result.json from the
calculate stage (folded under _mcli.calculate – healing-attempt
history included), and writes record.json – an OPTIMADE
structures-shaped document ready for a database or an OPTIMADE-serving
layer, with the non-standard extras namespaced under _mcli.
validate_inputs refuses clearly when screen_result.json does not
parse to a RELAXED verdict – someone starting mid-pipeline on a
screened-OUT (or hand-made) item is told exactly why, before any job runs.
- matmelt.core.stages.extract.RUNNER_NAME = 'extract_runner.py'#
The self-contained (stdlib-only) script shipped with every extract job.
- matmelt.core.stages.extract.RELAXED_NAME = 'relaxed.cif'#
Inputs from the screen stage – declared as input artifacts so the Phase-4 staging transport knows to push them (in-place locally; staged as a no-op).
- matmelt.core.stages.extract.META_NAME = 'structure.json'#
Optional provenance from the structure stage (space group, lattice type); folded into the record when present, never required.
- matmelt.core.stages.extract.CALC_NAME = 'calc_result.json'#
Optional result from the calculate stage (DFTB+ WorkChain outcome with the full healing-attempt history); folded under
_mcli.calculatewhen present, never required – extract stays startable without calculate.
- matmelt.core.stages.extract.RECORD_NAME = 'record.json'#
The OPTIMADE-shaped document this stage produces.
- class matmelt.core.stages.extract.ExtractStage(python='python3')[source]#
Bases:
StageAssemble one screened-in material’s OPTIMADE-shaped
record.json.- Parameters:
python (
str, default:'python3') – Interpreter to invoke on the backend this stage is wired to. The runner needs nothing beyond the standard library, so any python works – the option exists for backend-environment symmetry with the other stages, never because a dependency demands it.- Raises:
StageError – When
pythonis not a non-empty string.
- property name: str#
Stable stage identifier – also the per-item idempotency key in state.
- Returns:
The canonical name (
"compose","screen", …). It is what a config’sstages:block names, what the stage registry maps, and what the state store’s completion ledger records per item, so completing a stage twice is a no-op.- Return type:
- property requires: StageInputSpec#
What must be true of an item’s directory before this stage runs.
- Returns:
The declared input contract, enforced by
validate_inputs.- Return type:
StageInputSpec
- property produces: StageOutputSpec#
What this stage guarantees an item’s directory contains on success.
- Returns:
The declared output contract, enforced by
validate_outputs.- Return type:
StageOutputSpec
- validate_inputs(item)[source]#
Refuse clearly unless the screening verdict actually says RELAXED.
Extends the base file-presence check by reading the verdict, which is the mid-pipeline-start guard: extract serves only screened-in materials.
- Parameters:
item (
WorkItem) – The material about to be extracted.- Raises:
StageInputError – When a required file is missing or empty, when the screening verdict is unreadable or not valid JSON, or when it names any category other than
RELAXED.- Return type:
- build_jobspec(item)[source]#
Build the portable job description for one item.
The only place in a stage where a command is constructed. The spec names no host, queue, or transport; paths point into
item.dir; any stage options (model path, device, thresholds) were injected at stage construction from config – never hardcoded here.- Parameters:
item (
WorkItem) – The item to build a job for. Its inputs have already passedvalidate_inputs.- Returns:
A portable job description any backend can execute.
- Return type:
JobSpec- Raises:
StageError – If the stage is misconfigured such that no job can be built at all – a whole-stage fault, never a per-item verdict.
- interpret_result(item, result)[source]#
Judge a finished extract job.
- Parameters:
- Returns:
okon exit 0, meaning the record was written. Anything else is a fault carrying the runner’s stderr tail, and never terminal: with a stdlib-only runner a failure is a broken input file or an environment accident, and the item already carries a RELAXED verdict – science never rejects here.- Return type:
StageVerdict