PrERT-CNM-Demo / src /prert /cli /main.py
JayNightmare's picture
Upload folder using huggingface_hub
64c8e6c verified
Raw
History Blame Contribute Delete
12.5 kB
"""Top-level PrERT command runner for command discovery and execution."""
from __future__ import annotations
import argparse
import importlib
from pathlib import Path
import sys
from typing import Callable, Dict, List, Tuple
from prert.config import load_dotenv_if_available
ENTRYPOINTS: Dict[str, str] = {
"extract": "prert.cli.extract",
"migrate": "prert.cli.migrate",
"phase2": "prert.cli.phase2",
"opp115": "prert.cli.opp115",
"app350": "prert.cli.app350",
"phase3": "prert.cli.phase3",
"phase3-freeze": "prert.cli.phase3_freeze",
"phase4": "prert.cli.phase4",
"phase4-web": "prert.cli.phase4_web",
"phase4-synth": "prert.cli.phase4_synthetic",
}
ALIASES: Dict[str, str] = {
"phase1-extract": "extract",
"phase1-migrate": "migrate",
}
REQUIRED_DOCX_INPUTS = (
"docs/Standards/Regulations/GDPR-2016_679.docx",
"docs/Standards/Regulations/NIST-1.1.docx",
)
REQUIRED_CHROMA_ENV_VARS = (
"CHROMA_HOST",
"CHROMA_API_KEY",
"CHROMA_TENANT",
"CHROMA_DATABASE",
)
OPTIONAL_ENV_VARS = (
"CHROMA_COLLECTION_NAME",
"HF_TOKEN",
)
def main() -> None:
raise SystemExit(run())
def run(argv: List[str] | None = None) -> int:
args = list(sys.argv[1:] if argv is None else argv)
load_dotenv_if_available(None)
if not args:
_print_help()
return 0
command = args[0]
command_args = args[1:]
if command in {"-h", "--help", "help"}:
_print_help()
return 0
if command in {"doctor", "guide", "interactive"}:
if command == "doctor":
return _run_doctor(command_args)
if command == "interactive":
return _run_interactive(command_args)
return _run_guide(command_args)
canonical = ALIASES.get(command, command)
if canonical not in ENTRYPOINTS:
print(f"Unknown command: {command}")
print("Use 'prert help' to list supported commands.")
return 2
return _dispatch_to_entrypoint(canonical, command_args)
def _dispatch_to_entrypoint(command: str, command_args: List[str]) -> int:
module_name = ENTRYPOINTS[command]
module = importlib.import_module(module_name)
entrypoint: Callable[[], None] = getattr(module, "main")
previous_argv = list(sys.argv)
try:
sys.argv = [f"prert {command}", *command_args]
entrypoint()
except SystemExit as exc:
code = exc.code
if isinstance(code, int):
return code
return 0
finally:
sys.argv = previous_argv
return 0
def _run_doctor(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description="Validate PrERT setup prerequisites")
parser.add_argument(
"--root",
type=Path,
default=Path.cwd(),
help="Project root to validate. Defaults to current working directory.",
)
args = parser.parse_args(argv)
root = args.root.resolve()
failures = 0
print(f"PrERT Doctor: {root}")
failures += _check_python_version()
failures += _check_docx_inputs(root)
failures += _check_iso_inputs(root)
failures += _check_env_file(root)
if failures:
print("\nResult: FAIL")
print("Fix the items above and rerun: prert doctor")
return 1
print("\nResult: PASS")
return 0
def _run_guide(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description="Show guided PrERT command sequences")
parser.add_argument(
"--goal",
choices=("full", "phase1", "phase2", "phase3", "phase4", "validation"),
default="full",
help="Workflow goal to print recommended next commands.",
)
args = parser.parse_args(argv)
print("PrERT Guided Commands")
print(f"Goal: {args.goal}\n")
if args.goal in {"full", "phase1"}:
print("Phase 1")
print("1. prert extract --chunk --output-dir artifacts/phase-1")
print("2. prert migrate --input-dir artifacts/phase-1")
if args.goal == "phase1":
return 0
print("")
if args.goal in {"full", "phase2"}:
print("Phase 2")
print("1. prert phase2")
print("2. Optional public mapping: prert opp115")
print("3. Optional enrichment: prert phase2 --public-input data/processed/opp115_public_mapping.csv")
if args.goal == "phase2":
return 0
print("")
if args.goal in {"full", "phase3"}:
print("Phase 3")
print("1. prert phase3")
print("2. Optional acceptance freeze: prert phase3-freeze --strict")
if args.goal == "phase3":
return 0
print("")
if args.goal in {"full", "phase4", "validation"}:
print("Phase 4")
print("1. Validate artifacts: prert phase4 --baseline-dir artifacts/phase-3-freeze")
print("2. Launch web app: prert phase4-web --public")
print("3. Optional synthetic fixtures: prert phase4-synth --output-dir artifacts/phase-4/synthetic-compliance")
return 0
def _run_interactive(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description="Interactive PrERT command picker")
parser.add_argument(
"--goal",
choices=("full", "phase1", "phase2", "phase3", "phase4", "validation"),
default="full",
help="Workflow goal to filter interactive options.",
)
parser.add_argument(
"--select",
type=int,
default=None,
help="Option number to select without prompt.",
)
parser.add_argument(
"--execute",
action="store_true",
help="Execute the selected option immediately.",
)
args = parser.parse_args(argv)
options = _interactive_options(args.goal)
print("PrERT Interactive Runner")
print("")
for index, (title, command_args) in enumerate(options, start=1):
print(f"{index}. {title}")
print(f" prert {' '.join(command_args)}")
selected_index = args.select
if selected_index is None:
raw = input("\nSelect an option number (Enter to exit): ").strip()
if not raw:
print("No selection made.")
return 0
if not raw.isdigit():
print("Invalid selection. Enter a numeric option.")
return 2
selected_index = int(raw)
if selected_index < 1 or selected_index > len(options):
print(f"Invalid selection: {selected_index}. Choose 1 to {len(options)}.")
return 2
title, selected_command = options[selected_index - 1]
print("")
print(f"Selected: {title}")
print(f"Command: prert {' '.join(selected_command)}")
should_execute = args.execute
if not should_execute and args.select is None:
confirm = input("Execute now? [y/N]: ").strip().lower()
should_execute = confirm in {"y", "yes"}
if not should_execute:
print("Use --execute to run selected commands directly.")
return 0
return run(selected_command)
def _interactive_options(goal: str) -> List[Tuple[str, List[str]]]:
options: List[Tuple[str, List[str]]] = []
if goal in {"full", "phase1"}:
options.extend(
[
("Phase 1: Extract controls and chunks", ["extract", "--chunk", "--output-dir", "artifacts/phase-1"]),
("Phase 1: Migrate chunks to Chroma", ["migrate", "--input-dir", "artifacts/phase-1"]),
]
)
if goal in {"full", "phase2"}:
options.extend(
[
("Phase 2: Run baseline metrics pipeline", ["phase2"]),
("Phase 2: Build OPP-115 mapping export", ["opp115"]),
]
)
if goal in {"full", "phase3"}:
options.extend(
[
("Phase 3: Run baseline training", ["phase3"]),
("Phase 3: Run acceptance freeze", ["phase3-freeze", "--strict"]),
]
)
if goal in {"full", "phase4", "validation"}:
options.extend(
[
("Phase 4: Validate baseline artifacts", ["phase4", "--baseline-dir", "artifacts/phase-3-freeze"]),
("Phase 4: Launch compliance web app", ["phase4-web", "--port", "7860"]),
(
"Phase 4: Generate synthetic compliance fixtures",
["phase4-synth", "--output-dir", "artifacts/phase-4/synthetic-compliance"],
),
]
)
return options
def _check_python_version() -> int:
major, minor = sys.version_info.major, sys.version_info.minor
if (major, minor) >= (3, 11):
print(f"[PASS] Python version {major}.{minor}")
return 0
print(f"[FAIL] Python version {major}.{minor} (requires >= 3.11)")
return 1
def _check_docx_inputs(root: Path) -> int:
failures = 0
for relative_path in REQUIRED_DOCX_INPUTS:
candidate = root / relative_path
if candidate.exists():
print(f"[PASS] Found {relative_path}")
else:
print(f"[FAIL] Missing required input {relative_path}")
failures += 1
return failures
def _check_iso_inputs(root: Path) -> int:
regulations_dir = root / "docs/Standards/Regulations"
if not regulations_dir.exists():
print("[FAIL] Missing docs/Standards/Regulations directory")
return 1
iso_files = sorted(
path
for path in regulations_dir.glob("*.docx")
if path.name.startswith("ISO") or path.name.startswith("BS EN ISO") or path.name.startswith("BS ISO")
)
if iso_files:
print(f"[PASS] ISO DOCX inputs discovered: {len(iso_files)}")
return 0
print("[FAIL] No ISO DOCX inputs found in docs/Standards/Regulations")
return 1
def _check_env_file(root: Path) -> int:
env_path = root / ".env"
if not env_path.exists():
print("[FAIL] Missing .env file")
return 1
values = _load_env_values(env_path)
failures = 0
for key in REQUIRED_CHROMA_ENV_VARS:
if values.get(key, "").strip():
print(f"[PASS] {key} is set")
else:
print(f"[FAIL] {key} is missing or empty")
failures += 1
for key in OPTIONAL_ENV_VARS:
if values.get(key, "").strip():
print(f"[PASS] {key} is set")
else:
print(f"[WARN] {key} is not set (optional)")
return failures
def _load_env_values(path: Path) -> Dict[str, str]:
values: Dict[str, str] = {}
for line in path.read_text(encoding="utf-8").splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#") or "=" not in stripped:
continue
key, raw = stripped.split("=", 1)
values[key.strip()] = raw.strip().strip('"').strip("'")
return values
def _print_help() -> None:
print("PrERT Command Runner")
print("")
print("Usage:")
print(" prert <command> [args]")
print("")
print("Core commands:")
print(" extract Run Phase 1 extraction")
print(" migrate Run Phase 1 Chroma migration")
print(" phase2 Run Phase 2 metrics pipeline")
print(" opp115 Preprocess OPP-115 public mapping inputs")
print(" phase3 Run Phase 3 baseline training")
print(" phase3-freeze Run Phase 3 acceptance freeze")
print(" phase4 Run Phase 4 validation benchmark")
print(" phase4-web Launch Phase 4 Gradio web app")
print(" phase4-synth Generate synthetic policy/schema fixtures")
print("")
print("Helper commands:")
print(" doctor Validate environment, inputs, and credentials")
print(" guide Print recommended command order")
print(" interactive Pick commands from an interactive menu")
print("")
print("Examples:")
print(" prert guide --goal full")
print(" prert interactive")
print(" prert interactive --goal phase1 --select 1 --execute")
print(" prert extract --chunk --output-dir artifacts/phase-1")
print(" prert phase4-web --public")
if __name__ == "__main__":
main()