uofa-demo / src /uofa_cli /explain.py
cloudronin's picture
push build context (uofa source + packs + space app)
a28ec65 verified
Raw
History Blame Contribute Delete
1.84 kB
"""Generic divergence explanations for weakener patterns.
Reads the ``description`` field from WeakenerAnnotation objects.
Falls back to a generic message when no description is present.
No pattern-specific logic — the rule engine is the authority on *why*
a weakener fires.
"""
from __future__ import annotations
def _cou_name(doc: dict) -> str:
"""Extract a short COU display name from a UofA document."""
cou = doc.get("hasContextOfUse", {})
if isinstance(cou, dict):
return cou.get("name", doc.get("name", "unknown"))
return doc.get("name", "unknown")
def _short_iri(iri: str) -> str:
"""Extract the local name from an IRI."""
if isinstance(iri, str):
return iri.rsplit("/", 1)[-1]
return str(iri)
def explain_divergence(pattern_id: str, doc_with: dict, doc_without: dict,
weakener: dict) -> list[str]:
"""Generate explanation lines for a divergent weakener.
Args:
pattern_id: The weakener pattern ID (e.g. "W-AL-01").
doc_with: The UofA doc where the weakener IS present.
doc_without: The UofA doc where the weakener is NOT present.
weakener: The weakener annotation dict.
Returns:
List of 1-2 explanation strings.
"""
name_with = _cou_name(doc_with)
name_without = _cou_name(doc_without)
description = weakener.get("description", "")
affected = weakener.get("affectedNode", "")
short = _short_iri(affected) if affected else ""
lines = []
if description:
lines.append(f"{name_with}: {description}")
elif short:
lines.append(f"{name_with}: pattern {pattern_id} fires — affects {short}.")
else:
lines.append(f"{name_with}: pattern {pattern_id} fires.")
lines.append(f"{name_without}: pattern does not fire.")
return lines