cursus.step_catalog.naming

Single source of truth for step-name <-> file-name conversion in the step catalog.

Historically each discovery module (script_discovery, builder_discovery, contract_discovery) and the steps.interfaces loader carried its own PascalCase<->snake_case table of compound acronyms (XGBoost, PyTorch, LightGBM, …). Those tables drifted (e.g. LightGBM was in one but not another; contract_discovery had none and mangled PyTorchTraining -> py_torch_training), silently breaking name resolution on whichever path used the stale table — the root cause of the TSA/SOPA filename races and the “8 of 40 discovered” class of bug.

This module centralizes that logic:

  • COMPOUND_ACRONYMS — the one canonical list of multi-word tokens that must not be split on internal capitals.

  • canonical_to_snake() — PascalCase canonical name -> snake_case file stem (PyTorchTraining -> pytorch_training).

  • parts_to_pascal() — snake_case parts -> PascalCase canonical name (the inverse).

  • canonical_key() — case/separator-insensitive key for robust fallback matching.

New frameworks are added in ONE place here. Better still, canonical_key() enables a normalized directory scan so most new acronyms resolve with no table edit at all.

is_job_type_variant(step_name)[source]

Return True if step_name ends in a known job-type suffix (foo_training).

Matches on a trailing _<suffix> so it only fires on snake_case variant names and never on a base step whose name merely contains a job word.

canonical_to_snake(canonical_name)[source]

Convert a PascalCase canonical step name to its snake_case file stem.

Compound acronyms are protected (XGBoostTraining -> xgboost_training, not x_g_boost_training); the remaining PascalCase is split on capital boundaries, including runs of capitals followed by a word (MyABCStep -> my_abc_step).

parts_to_pascal(parts)[source]

Convert snake_case parts to a PascalCase canonical name (inverse of split).

["xgboost", "training"] -> "XGBoostTraining". Parts matching a known compound acronym keep its canonical casing; others are simply capitalized.

canonical_key(name)[source]

Collapse a name/stem to a case- and separator-insensitive key.

"XGBoostTraining", "xgboost_training" and "XGBoost_Training" all map to "xgboosttraining". Used as a robust fallback when the exact convention name misses, so a new acronym step still resolves without editing COMPOUND_ACRONYMS.

resolve_base_step_name(node_name, known_step_names)[source]

Resolve a DAG node name to its base registry step name — ROBUSTLY, without a hardcoded suffix list.

A node is <StepName>[_<suffix>...] where <suffix> is an arbitrary label (job_type, data-source tag, split name — e.g. _training, _munged, _sampling, _tagging, _baseline, _embedding). Since job_type is now open (any lowercase-alnum, deep dive Tranche 3), the suffix is NOT drawn from a fixed set, so matching a trailing token against JOB_TYPE_SUFFIXES misses real nodes. Instead: strip trailing _segment groups one at a time and return the first prefix that is an actual known step name (matched via canonical_key(), so compound-acronym casing like XgboostMt vs XGBoostMT also resolves). The step registry — not a suffix allowlist — is the authority for what a base name is.

Returns the canonical known step name, or None if no prefix matches.

split_job_type_suffix(node_name, known_step_names)[source]

Split a node into (base_step_name, suffix_or_None) using the registry (robust).

('TabularPreprocessing_training', [...]) -> ('TabularPreprocessing', 'training'); ('CradleDataLoading_munged', [...]) -> ('CradleDataLoading', 'munged'); ('CradleDataLoading', [...]) -> ('CradleDataLoading', None). Unlike a JOB_TYPE_SUFFIXES check, this handles ANY suffix because the base is validated against the actual step registry via resolve_base_step_name(). Returns (node_name, None) if the node doesn’t resolve to a known base.