Files
breakpilot-compliance/admin-compliance/app/sdk/licenses/page.tsx
T
Benjamin Admin dfac940272 feat(licenses): attribution renderer — Stufe 1 (overview) + Stufe 3 (SourceBadge)
Backend
- backend-compliance/compliance/api/licenses_routes.py: three endpoints
  built on the now-complete license_rule classification
  - GET  /api/compliance/licenses/overview
       global aggregation by rule + per-source breakdown (Stufe 1)
  - POST /api/compliance/licenses/aggregate
       per-control-set aggregation for PDF footer (Stufe 2) and
       tech-file appendix (Stufe 4) — consumed later
  - GET  /api/compliance/licenses/source-info/{control_uuid}
       single-control lookup for the inline source badge (Stufe 3)
- registered in api/__init__.py via the existing safe-import loader

Frontend
- app/sdk/licenses/page.tsx (Stufe 1): the /sdk/licenses overview page.
  Renders rule legend cards + per-rule source tables. Drives the
  /licenses footer link and gives auditors a one-page view of what
  licence classes the platform is operating under.
- components/sdk/SourceBadge.tsx (Stufe 3): reusable React component.
  Small R1/R2/R3 pill with click-expand tooltip showing source
  regulation + attribution string + render-full-text policy. Will be
  embedded into IACE hazards/mitigations, VVT items, DSFA controls in
  follow-up commits.

Two stages of the four-stage renderer are now ready. Stufe 2 (PDF
auto-footer) + Stufe 4 (tech-file appendix) follow once the existing
PDF generators are extended to call /licenses/aggregate.
2026-05-21 21:00:10 +02:00

161 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
// Stufe 1 of the Attribution Renderer (Task #23): the global
// "Quellen & Lizenzen" overview. Aggregates all 314k canonical_controls
// by their license_rule and shows the source regulations behind each
// bucket. Drives the footer link and gives auditors a one-page view of
// what licence classes the platform is operating under.
type SourceCount = {
regulation_id: string
regulation_name_de: string | null
license_rule: number
license_type: string | null
attribution: string | null
jurisdiction: string | null
source_type: string | null
n_controls: number
}
type RuleBucket = {
rule: number
label_de: string
label_en: string
attribution_required: boolean
render_full_text: boolean
total_controls: number
distinct_sources: number
sources: SourceCount[]
}
type Overview = {
total_controls: number
buckets: RuleBucket[]
}
const RULE_COLOR: Record<number, string> = {
1: 'border-emerald-200 bg-emerald-50',
2: 'border-amber-200 bg-amber-50',
3: 'border-slate-200 bg-slate-50',
}
const RULE_BADGE: Record<number, string> = {
1: 'bg-emerald-600 text-white',
2: 'bg-amber-600 text-white',
3: 'bg-slate-600 text-white',
}
export default function LicensesPage() {
const [data, setData] = useState<Overview | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch('/api/sdk/v1/compliance/licenses/overview')
.then((r) => (r.ok ? r.json() : Promise.reject(`HTTP ${r.status}`)))
.then(setData)
.catch((e) => setError(String(e)))
}, [])
if (error) {
return (
<div className="p-6">
<h1 className="text-xl font-semibold mb-2">Quellen &amp; Lizenzen</h1>
<p className="text-red-600">Fehler beim Laden: {error}</p>
</div>
)
}
if (!data) {
return (
<div className="p-6">
<h1 className="text-xl font-semibold">Quellen &amp; Lizenzen</h1>
<p className="text-slate-500 mt-2">Lade </p>
</div>
)
}
return (
<div className="p-6 max-w-7xl">
<header className="mb-6">
<h1 className="text-2xl font-semibold">Quellen &amp; Lizenzen</h1>
<p className="text-sm text-slate-600 mt-1">
Diese Plattform stützt sich auf {data.total_controls.toLocaleString('de-DE')}{' '}
klassifizierte Compliance-Controls aus den unten genannten Quellen.
Jeder Control trägt eine deterministische Lizenzregel (R1R3), die das
Render-Verhalten in Berichten und im Frontend steuert.
</p>
</header>
<section className="mb-8">
<h2 className="text-lg font-medium mb-3">Klassifizierungs-Schema</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
{data.buckets.map((b) => (
<div key={b.rule} className={`rounded border ${RULE_COLOR[b.rule] ?? 'border-slate-200'} p-3`}>
<div className="flex items-center gap-2 mb-2">
<span className={`inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold ${RULE_BADGE[b.rule] ?? 'bg-slate-600 text-white'}`}>
R{b.rule}
</span>
<span className="font-medium">{b.label_de}</span>
</div>
<ul className="text-xs text-slate-700 space-y-1">
<li>{b.total_controls.toLocaleString('de-DE')} Controls</li>
<li>{b.distinct_sources} Quellen</li>
<li>{b.render_full_text ? 'Volltext-Anzeige erlaubt' : 'Nur Identifier-Verweis'}</li>
<li>{b.attribution_required ? 'Attribution-Pflicht in Output' : 'keine Attribution-Pflicht'}</li>
</ul>
</div>
))}
</div>
</section>
{data.buckets.map((b) => (
<section key={b.rule} className="mb-8">
<h2 className="text-lg font-medium mb-3 flex items-center gap-2">
<span className={`inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold ${RULE_BADGE[b.rule] ?? 'bg-slate-600 text-white'}`}>
R{b.rule}
</span>
{b.label_de}{' '}
<span className="text-sm text-slate-500 font-normal">
({b.total_controls.toLocaleString('de-DE')} Controls aus {b.distinct_sources} Quellen)
</span>
</h2>
<div className="overflow-x-auto border rounded">
<table className="w-full text-sm">
<thead className="bg-slate-100 text-slate-700">
<tr>
<th className="text-left p-2">Quelle</th>
<th className="text-left p-2">Lizenztyp</th>
<th className="text-left p-2">Rechtsraum</th>
<th className="text-left p-2">Attribution</th>
<th className="text-right p-2">Controls</th>
</tr>
</thead>
<tbody>
{b.sources.map((s) => (
<tr key={`${b.rule}-${s.regulation_id}`} className="border-t">
<td className="p-2">{s.regulation_name_de ?? s.regulation_id}</td>
<td className="p-2 text-slate-600">{s.license_type ?? '—'}</td>
<td className="p-2 text-slate-600">{s.jurisdiction ?? '—'}</td>
<td className="p-2 text-slate-600">{s.attribution ?? '—'}</td>
<td className="p-2 text-right tabular-nums">{s.n_controls.toLocaleString('de-DE')}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
))}
<footer className="text-xs text-slate-500 border-t pt-4 mt-8">
Klassifizierung: deterministisch über parent_control_uuid-Vererbung,
control_parent_links regulation_registry, source_citation,
canonical_processed_chunks (Pipeline-Ground-Truth) und LLM-Aggregat-
Identifikation für eigene Werke. Audit-Skripte unter
breakpilot-core/control-pipeline/scripts/.
</footer>
</div>
)
}