78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
|
|
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,
|
||
|
|
}
|