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

169 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""
Verify that a scenario directory is complete and ready for Curtin ICS-SimLab.
Checks:
1. configuration.json exists
2. logic/ directory exists
3. All logic files referenced in config exist in logic/
4. (Optional) Run validate_logic checks
Usage:
python3 -m tools.verify_scenario --scenario outputs/scenario_run
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import List, Set, Tuple
def get_logic_files_from_config(config: dict) -> Tuple[Set[str], Set[str]]:
"""
Extract logic filenames referenced in configuration.
Returns: (plc_logic_files, hil_logic_files)
"""
plc_files: Set[str] = set()
hil_files: Set[str] = set()
for plc in config.get("plcs", []):
logic = plc.get("logic", "")
if logic:
plc_files.add(logic)
for hil in config.get("hils", []):
logic = hil.get("logic", "")
if logic:
hil_files.add(logic)
return plc_files, hil_files
def verify_scenario(scenario_dir: Path) -> Tuple[bool, List[str]]:
"""
Verify scenario directory is complete.
Returns: (success: bool, errors: List[str])
"""
errors: List[str] = []
# Check configuration.json exists
config_path = scenario_dir / "configuration.json"
if not config_path.exists():
errors.append(f"Missing: {config_path}")
return False, errors
# Load config
try:
config = json.loads(config_path.read_text(encoding="utf-8"))
except Exception as e:
errors.append(f"Invalid JSON in {config_path}: {e}")
return False, errors
# Check logic/ directory exists
logic_dir = scenario_dir / "logic"
if not logic_dir.exists():
errors.append(f"Missing directory: {logic_dir}")
return False, errors
# Check all referenced logic files exist
plc_files, hil_files = get_logic_files_from_config(config)
all_files = plc_files | hil_files
for fname in sorted(all_files):
fpath = logic_dir / fname
if not fpath.exists():
errors.append(f"Missing logic file: {fpath} (referenced in config)")
# Check for orphan logic files (warning only)
existing_files = {f.name for f in logic_dir.glob("*.py")}
orphans = existing_files - all_files
if orphans:
# Not an error, just informational
pass
success = len(errors) == 0
return success, errors
def main() -> None:
parser = argparse.ArgumentParser(
description="Verify scenario directory is complete for ICS-SimLab"
)
parser.add_argument(
"--scenario",
required=True,
help="Path to scenario directory (e.g., outputs/scenario_run)",
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Show detailed information",
)
args = parser.parse_args()
scenario_dir = Path(args.scenario)
if not scenario_dir.exists():
raise SystemExit(f"ERROR: Scenario directory not found: {scenario_dir}")
print(f"Verifying scenario: {scenario_dir}")
print()
success, errors = verify_scenario(scenario_dir)
if args.verbose or success:
# Show contents
config_path = scenario_dir / "configuration.json"
logic_dir = scenario_dir / "logic"
if config_path.exists():
config = json.loads(config_path.read_text(encoding="utf-8"))
plc_files, hil_files = get_logic_files_from_config(config)
print("Configuration:")
print(f" PLCs: {len(config.get('plcs', []))}")
print(f" HILs: {len(config.get('hils', []))}")
print(f" Sensors: {len(config.get('sensors', []))}")
print(f" Actuators: {len(config.get('actuators', []))}")
print()
print("Logic files referenced:")
for f in sorted(plc_files):
status = "OK" if (logic_dir / f).exists() else "MISSING"
print(f" [PLC] {f}: {status}")
for f in sorted(hil_files):
status = "OK" if (logic_dir / f).exists() else "MISSING"
print(f" [HIL] {f}: {status}")
print()
# Show orphans
if logic_dir.exists():
existing = {f.name for f in logic_dir.glob("*.py")}
orphans = existing - (plc_files | hil_files)
if orphans:
print("Orphan files (not referenced in config):")
for f in sorted(orphans):
print(f" {f}")
print()
if errors:
print(f"VERIFICATION FAILED: {len(errors)} error(s)")
for err in errors:
print(f" - {err}")
raise SystemExit(1)
else:
print("VERIFICATION PASSED: Scenario is complete")
print()
print("To run with ICS-SimLab:")
print(f" cd ~/projects/ICS-SimLab-main/curtin-ics-simlab")
print(f" sudo ./start.sh {scenario_dir.absolute()}")
if __name__ == "__main__":
main()