setExpanded(expanded === row.id ? null : row.id)}>
|
- {row.passed ? (
- ✓
- ) : row.skipped ? (
- —
- ) : (
- ✗
- )}
+ {(() => {
+ const st = rowReviewStatus(row)
+ if (st === 'pass') return ✓
+ if (st === 'na') return —
+ if (st === 'review') return ?
+ return ✗
+ })()}
|
{row.doc_type} |
{row.regulation || '—'} |
{row.label} |
- {row.severity || '—'}
+ {(() => {
+ const prio = regulationToPriority(row.regulation)
+ return (
+
+ {prio}
+
+ )
+ })()}
|
{expanded === row.id && (
diff --git a/backend-compliance/compliance/services/mc_scorecard.py b/backend-compliance/compliance/services/mc_scorecard.py
index 20a2d456..ecb09e34 100644
--- a/backend-compliance/compliance/services/mc_scorecard.py
+++ b/backend-compliance/compliance/services/mc_scorecard.py
@@ -121,11 +121,37 @@ def _dedup_key(label: str) -> str:
return label
+_CONDITIONAL_MARKERS = ("falls ", "sofern ", "wenn ", "soweit ",
+ "bei bedarf", "ggf.", "gegebenenfalls")
+
+
+def _is_hard_finding(r: dict) -> bool:
+ """Echtes Finding = wir haben einen positiven Treffer im Text der den
+ Verstoss belegt. Stille im Text reicht NICHT — das wandert ins MC-Audit
+ als "selbst pruefen", nicht ins Email als HIGH-Drohung.
+
+ Heuristik:
+ - matched_text nicht leer = textuelle Evidenz vorhanden → hart
+ - konditionales Label ("falls / sofern / wenn") UND matched_text leer
+ → weich (Pre-Condition nicht belegt) → raus aus Top-Fails
+ - sonst: hart (klassische Pflichtangaben-Lücke wie "DSB fehlt")
+ """
+ mt = (r.get("matched_text") or "").strip()
+ if mt:
+ return True
+ label_low = (r.get("label") or "").lower()
+ if any(m in label_low for m in _CONDITIONAL_MARKERS):
+ return False
+ return True
+
+
def top_fails(check_results: list[dict], n: int = 10) -> list[dict]:
"""Return top-N failing MCs sorted by severity then label.
Skipped + passed MCs are excluded. INFO severity is excluded by
- default since those are guidance, not findings.
+ default since those are guidance, not findings. Konditionale MCs
+ ohne Negativ-Beleg (P8) werden ebenfalls ausgesteuert — sie
+ erscheinen nur noch im MC-Audit als "selbst pruefen".
Near-duplicates (multiple MCs that all complain about "einfache
Sprache" / "Einwilligungsaufforderung" / ...) are collapsed to ONE
@@ -136,6 +162,7 @@ def top_fails(check_results: list[dict], n: int = 10) -> list[dict]:
r for r in (check_results or [])
if not r.get("passed") and not r.get("skipped")
and (r.get("severity") or "").upper() != "INFO"
+ and _is_hard_finding(r)
]
fails.sort(key=lambda r: (
_SEV_RANK.get((r.get("severity") or "MEDIUM").upper(), 5),