from __future__ import annotations from typing import Any from uuid import UUID from app.domain.validation import DomainValidationError, canonical_json, sha256_text def parse_project_criteria(definition: dict[str, Any]) -> dict[str, Any]: items = definition.get("items") if not isinstance(items, list) or not items: raise DomainValidationError("criteria.items must be a non-empty list") seen: set[str] = set() canonical_items: list[dict[str, Any]] = [] for raw in items: criterion_id = raw.get("criterion_definition_id") key = raw.get("criterion") item_id = str(raw.get("id") or criterion_id or key or "") if not item_id: raise DomainValidationError("each criterion needs id, criterion_definition_id or criterion") if item_id in seen: raise DomainValidationError("duplicate criterion id", {"id": item_id}) seen.add(item_id) if not criterion_id and not key: raise DomainValidationError("criterion_definition_id or criterion is required", {"id": item_id}) if not raw.get("operator"): raise DomainValidationError("operator is required", {"id": item_id}) enabled = raw.get("enabled_for_discovery") if enabled is None: enabled = raw.get("mandatory", True) canonical_items.append( { "id": item_id, "criterion_definition_id": None if criterion_id is None else UUID(str(criterion_id)), "criterion": None if key is None else str(key), "operator": str(raw["operator"]), "value": raw.get("value"), "enabled_for_discovery": bool(enabled), "mandatory": bool(enabled), "value_type": raw.get("value_type"), "unit": raw.get("unit"), "semantic": raw.get("semantic"), "category": raw.get("category"), } ) canonical = { "items": [ { "id": item["id"], "criterion_definition_id": None if item["criterion_definition_id"] is None else str(item["criterion_definition_id"]), "criterion": item["criterion"], "operator": item["operator"], "value": item["value"], "enabled_for_discovery": item["enabled_for_discovery"], } for item in canonical_items ] } return { "items": canonical_items, "definition": canonical, "definition_hash": sha256_text(canonical_json(canonical)), } def criterion_payload(row) -> dict[str, Any]: return { "id": row.item_id, "criterion_definition_id": row.criterion_definition_id, "criterion": row.criterion_key, "operator": row.operator, "value": row.value, "enabled_for_discovery": row.enabled_for_discovery, "mandatory": row.enabled_for_discovery, }