56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
from __future__ import annotations
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import yaml
|
|
from pydantic import ValidationError
|
|
from app.models.interview import (
|
|
LikertInstrument, QSortInstrument,
|
|
)
|
|
|
|
class InstrumentValidationError(ValueError):
|
|
pass
|
|
|
|
def _parse_yaml(path: Path) -> dict:
|
|
if not path.exists():
|
|
raise InstrumentValidationError(f"instrument file not found: {path}")
|
|
try:
|
|
with path.open("r", encoding="utf-8") as f:
|
|
data = yaml.safe_load(f)
|
|
except yaml.YAMLError as e:
|
|
raise InstrumentValidationError(f"YAML parse error in {path}: {e}") from e
|
|
if not isinstance(data, dict):
|
|
raise InstrumentValidationError(f"top-level YAML must be a mapping in {path}")
|
|
return data
|
|
|
|
def load_likert_instrument(path: Path) -> LikertInstrument:
|
|
data = _parse_yaml(Path(path))
|
|
try:
|
|
return LikertInstrument(**data)
|
|
except ValidationError as e:
|
|
raise InstrumentValidationError(str(e)) from e
|
|
|
|
def load_qsort_instrument(path: Path) -> QSortInstrument:
|
|
data = _parse_yaml(Path(path))
|
|
try:
|
|
return QSortInstrument(**data)
|
|
except ValidationError as e:
|
|
raise InstrumentValidationError(str(e)) from e
|
|
|
|
def instrument_hash(path: Path) -> str:
|
|
data = Path(path).read_bytes()
|
|
return hashlib.sha256(data).hexdigest()[:16]
|
|
|
|
def freeze_snapshot(instruments: dict[str, Path], out_path: Path) -> dict:
|
|
snapshot = {
|
|
name: {
|
|
"path": str(p),
|
|
"hash": instrument_hash(p),
|
|
"content": _parse_yaml(p),
|
|
}
|
|
for name, p in instruments.items()
|
|
}
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
out_path.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
return snapshot
|