This commit is contained in:
gsinghpal
2026-04-28 19:39:37 -04:00
parent 2d42b33d68
commit 13e300d90e
103 changed files with 4959 additions and 331 deletions

View File

@@ -27,7 +27,32 @@ def post_init_hook(env):
_seed_default_timezone(env)
_backfill_node_input_kind(env)
_seed_step_library_if_empty(env)
_backfill_contract_review_template(env)
_seed_rack_tags_if_empty(env)
_migrate_legacy_uom_columns(env)
def _backfill_contract_review_template(env):
"""Idempotent — ensure the Contract Review library template exists.
`_seed_step_library_if_empty` only fires on a fresh DB; existing DBs
upgraded from pre-Policy-B versions still have a populated library
minus the Contract Review entry. This function fills that hole.
Re-running it is a no-op once the template exists.
"""
Tpl = env['fp.step.template']
if Tpl.search([('default_kind', '=', 'contract_review')], limit=1):
return # already there
tpl = Tpl.create({
'name': 'Contract Review',
'default_kind': 'contract_review',
})
tpl.action_seed_default_inputs()
_logger.info(
"Fusion Plating: backfilled Contract Review library template "
"(id=%s, %s default inputs).",
tpl.id, len(tpl.input_template_ids),
)
def _seed_default_timezone(env):
@@ -60,6 +85,12 @@ def _backfill_node_input_kind(env):
# Mapping of recipe-step name → default_kind. Drives sane-default
# input seeding on the starter library entries.
_STARTER_KIND_BY_NAME = {
# Policy B (2026-04-28) — recipe-side Contract Review step.
# When an author drops this template into a recipe, fp.job.step.button_*
# hooks in fusion_plating_jobs detect the kind=='contract_review' and
# auto-open / gate the QA-005 audit form (fp.contract.review).
'contract review': 'contract_review',
'qa-005': 'contract_review',
'soak clean': 'cleaning',
'electroclean': 'cleaning',
'solvent clean': 'cleaning',
@@ -73,23 +104,85 @@ _STARTER_KIND_BY_NAME = {
'zincate': 'etch',
'strip zincate': 'etch',
'acid dip': 'etch',
'hcl activation': 'etch',
'water break test': 'wbf_test',
'water break free test': 'wbf_test',
'issue panels': 'mask',
'masking': 'mask',
'mask': 'mask',
'racking': 'racking',
'rack': 'racking',
'e-nickel plate': 'plate',
'e-nickel plating': 'plate',
'electroless nickel plate': 'plate',
'electroless nickel plating': 'plate',
'enp': 'plate',
'plate': 'plate',
'plating': 'plate',
'drying': 'dry',
'dry': 'dry',
'bake': 'bake',
'oven baking': 'bake',
'oven bake': 'bake',
'baking': 'bake',
'hydrogen embrittlement bake': 'bake',
'he bake': 'bake',
'de-rack': 'derack',
'de-racking': 'derack',
'deracking': 'derack',
'derack': 'derack',
'demask': 'demask',
'de-mask': 'demask',
'de-masking': 'demask',
'demasking': 'demask',
'inspection': 'inspect',
'incoming inspection': 'inspect',
'post-plate inspection': 'inspect',
'post plate inspection': 'inspect',
'visual inspection': 'inspect',
'porosity test': 'inspect',
'adhesion test': 'inspect',
'final inspection': 'final_inspect',
'final inspection / packaging': 'final_inspect',
'shipping': 'ship',
'pack': 'ship',
'packaging': 'ship',
# Gating steps (Steelhead-style "Ready for X" intermediate states).
'ready for incoming inspection': 'gating',
'ready for plating': 'gating',
'ready for racking': 'gating',
'ready for de-masking': 'gating',
'ready for demasking': 'gating',
'ready for masking': 'gating',
'ready for bake': 'gating',
'ready for deracking': 'gating',
'ready for de-racking': 'gating',
'ready for post plate inspection': 'gating',
'ready for post-plate inspection': 'gating',
'ready for final inspection': 'gating',
'ready for shipping': 'gating',
}
def fp_resolve_step_kind(name):
"""Resolve a step name to a default_kind, tolerant of whitespace and
case. Used by both the seeder and the migration backfill so we don't
have two slightly-different lookup paths.
Returns the kind str or None when no match.
"""
if not name:
return None
key = name.strip().lower()
if key in _STARTER_KIND_BY_NAME:
return _STARTER_KIND_BY_NAME[key]
# Gating "Ready for / Ready For" prefix — anything starting with that
# is a gating node regardless of the destination step name.
if key.startswith('ready for ') or key.startswith('ready '):
return 'gating'
return None
def _seed_step_library_if_empty(env):
"""Sub 12a — seed fp.step.template starter library.
@@ -135,7 +228,7 @@ def _create_template_from_node(env, node, seen):
return
seen.add(node.name.lower())
kind = _STARTER_KIND_BY_NAME.get(node.name.lower())
kind = fp_resolve_step_kind(node.name)
vals = {
'name': node.name,
'description': node.description or False,
@@ -164,6 +257,7 @@ def _seed_minimal_library(env):
"""Hard-coded minimal seed when ENP-ALUM-BASIC isn't on the target DB."""
Tpl = env['fp.step.template']
minimal = [
('Contract Review', 'contract_review'),
('Soak Clean', 'cleaning'),
('Electroclean', 'cleaning'),
('Rinse', 'rinse'),
@@ -189,6 +283,38 @@ def _seed_minimal_library(env):
)
def _migrate_legacy_uom_columns(env):
"""Translate every free-text UoM column in the plating suite into the
new curated Selection keys.
Runs unconditionally on every fusion_plating upgrade so the day a
downstream module's migration converts a Char to Selection, the data
follows. Each call is a no-op when:
* the column already holds selection keys (identity mapping)
* the table doesn't exist (module not installed on this DB)
"""
from .models._fp_uom_selection import fp_migrate_uom_column
targets = [
# core
('fusion_plating_bath_parameter', 'uom', 'bath parameter'),
('fusion_plating_process_node_input', 'uom', 'process node input'),
('fusion_plating_process_node_input', 'target_unit', 'process node target'),
('fp_step_template_input', 'target_unit', 'step template input target'),
# compliance
('fusion_plating_discharge_limit', 'uom', 'discharge limit'),
('fusion_plating_discharge_sample_line', 'uom', 'discharge sample line'),
('fusion_plating_waste_manifest', 'uom', 'waste manifest'),
('fusion_plating_waste_stream', 'generation_uom', 'waste stream'),
('fusion_plating_spill_register', 'uom', 'spill register'),
# safety
('fusion_plating_chemical', 'container_uom', 'chemical container'),
('fusion_plating_exposure_monitoring', 'uom', 'exposure monitoring'),
]
for table, column, label in targets:
fp_migrate_uom_column(env, table, column, label)
def _seed_rack_tags_if_empty(env):
"""Sub 12b — seed 4 starter rack tags."""
Tag = env['fp.rack.tag']