ics-simlab-config-gen-claude/tools/generate_logic.py

126 lines
4.9 KiB
Python

import argparse
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from models.ics_simlab_config import Config
from templates.tank import (
TankParams,
render_hil_stub,
render_hil_tank,
render_plc_stub,
render_plc_threshold,
)
def pick_by_keywords(ids: List[str], keywords: List[str]) -> Tuple[Optional[str], bool]:
low_ids = [(s, s.lower()) for s in ids]
for kw in keywords:
kwl = kw.lower()
for original, lowered in low_ids:
if kwl in lowered:
return original, True
return None, False
def tank_mapping_plc(inputs: List[str], outputs: List[str]) -> Tuple[Optional[str], Optional[str], Optional[str], bool]:
level, level_hit = pick_by_keywords(inputs, ["water_tank_level", "tank_level", "level"])
inlet, inlet_hit = pick_by_keywords(outputs, ["tank_input_valve", "input_valve", "inlet"])
remaining = [o for o in outputs if o != inlet]
outlet, outlet_hit = pick_by_keywords(remaining, ["tank_output_valve", "output_valve", "outlet"])
ok = bool(level and inlet and outlet and level_hit and inlet_hit and outlet_hit and inlet != outlet)
return level, inlet, outlet, ok
def tank_mapping_hil(inputs: List[str], outputs: List[str]) -> Tuple[Optional[str], Optional[str], Optional[str], bool]:
level_out, level_hit = pick_by_keywords(outputs, ["water_tank_level_output", "tank_level_output", "tank_level_value", "level"])
inlet_in, inlet_hit = pick_by_keywords(inputs, ["tank_input_valve_input", "input_valve_input", "inlet"])
remaining = [i for i in inputs if i != inlet_in]
outlet_in, outlet_hit = pick_by_keywords(remaining, ["tank_output_valve_input", "output_valve_input", "outlet"])
ok = bool(level_out and inlet_in and outlet_in and level_hit and inlet_hit and outlet_hit and inlet_in != outlet_in)
return level_out, inlet_in, outlet_in, ok
def write_text(path: Path, content: str, overwrite: bool) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
if path.exists() and not overwrite:
raise SystemExit(f"Refusing to overwrite existing file: {path} (use --overwrite)")
path.write_text(content, encoding="utf-8")
def main() -> None:
ap = argparse.ArgumentParser(description="Generate logic/*.py deterministically from configuration.json")
ap.add_argument("--config", required=True, help="Path to configuration.json")
ap.add_argument("--out-dir", required=True, help="Directory where .py files will be written")
ap.add_argument("--model", default="tank", choices=["tank"], help="Deterministic model template to use")
ap.add_argument("--overwrite", action="store_true", help="Overwrite existing files")
args = ap.parse_args()
cfg_text = Path(args.config).read_text(encoding="utf-8")
cfg = Config.model_validate_json(cfg_text)
out_dir = Path(args.out_dir)
# duplicate logic filename guard
seen: Dict[str, str] = {}
for plc in cfg.plcs:
lf = (plc.logic or "").strip()
if lf:
key = f"plc:{plc.label}"
if lf in seen:
raise SystemExit(f"Duplicate logic filename '{lf}' used by: {seen[lf]} and {key}")
seen[lf] = key
for hil in cfg.hils:
lf = (hil.logic or "").strip()
if lf:
key = f"hil:{hil.label}"
if lf in seen:
raise SystemExit(f"Duplicate logic filename '{lf}' used by: {seen[lf]} and {key}")
seen[lf] = key
# PLCs
for plc in cfg.plcs:
logic_name = (plc.logic or "").strip()
if not logic_name:
continue
inputs, outputs = plc.io_ids()
level, inlet, outlet, ok = tank_mapping_plc(inputs, outputs)
if args.model == "tank" and ok:
content = render_plc_threshold(plc.label, level, inlet, outlet, low=0.2, high=0.8)
else:
content = render_plc_stub(plc.label)
write_text(out_dir / logic_name, content, overwrite=bool(args.overwrite))
print(f"Wrote PLC logic: {out_dir / logic_name}")
# HILs
for hil in cfg.hils:
logic_name = (hil.logic or "").strip()
if not logic_name:
continue
inputs, outputs = hil.pv_io()
required_outputs = list(outputs)
level_out, inlet_in, outlet_in, ok = tank_mapping_hil(inputs, outputs)
if args.model == "tank" and ok:
content = render_hil_tank(
hil.label,
level_out_id=level_out,
inlet_cmd_in_id=inlet_in,
outlet_cmd_in_id=outlet_in,
required_output_ids=required_outputs,
params=TankParams(),
initial_level=None,
)
else:
content = render_hil_stub(hil.label, required_output_ids=required_outputs)
write_text(out_dir / logic_name, content, overwrite=bool(args.overwrite))
print(f"Wrote HIL logic: {out_dir / logic_name}")
if __name__ == "__main__":
main()