feat(use-case-controls): Adressat-Achse — out-of-scope advisory + additiver GOV-Tag
2-Pass-Haiku-Klassifikation (konservativ + Re-Confirm jeder Nicht-unternehmen- Einstufung) der Review-Tier-Atome: wer muss die Pflicht erfuellen? - Migration 155: atom_classification.addressee (unternehmen/oeffentliche_stelle/ aufsichtsbefugnis/staat_eu/dritter/meta), additiv, kein CHECK. [migration-approved] - Service: addressee + applicable + is_gov pro Control; include_out_of_scope-Param (Default false -> out-of-scope advisory ausgeblendet, NIE geloescht); out_of_scope_count. Pure Helper addressee_applicable/addressee_is_gov (+ Tests). - Route: optionaler include_out_of_scope-Query (contract-safe, additiv). - Frontend: GOV-Chip (additiv) + "kein Kunden-Pruefaspekt"-Chip + 1-Klick-Toggle zum Einblenden der out-of-scope-Atome. Daten: 40.859 Adressat-Tags auf macmini geladen (81% applicable, 19% advisory, 3.146 GOV). Konservativ: NULL/Unklar = applicable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
provenanceBadgeClass,
|
||||
severityBadgeClass,
|
||||
splitByTier,
|
||||
addresseeLabel,
|
||||
} from '../_helpers'
|
||||
|
||||
const BACKEND_URL =
|
||||
@@ -13,12 +14,15 @@ const BACKEND_URL =
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
async function getControls(useCase: string): Promise<ControlsResponse | null> {
|
||||
async function getControls(
|
||||
useCase: string,
|
||||
oos: boolean,
|
||||
): Promise<ControlsResponse | null> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${BACKEND_URL}/api/compliance/v1/controls/use-cases/${encodeURIComponent(
|
||||
useCase,
|
||||
)}/controls?tier=all&limit=200`,
|
||||
)}/controls?tier=all&limit=200&include_out_of_scope=${oos}`,
|
||||
{ cache: 'no-store' },
|
||||
)
|
||||
return res.ok ? ((await res.json()) as ControlsResponse) : null
|
||||
@@ -42,7 +46,23 @@ function ControlsTable({ rows }: { rows: ControlItem[] }) {
|
||||
<tbody className="divide-y divide-gray-100 bg-white">
|
||||
{rows.map((c) => (
|
||||
<tr key={c.id}>
|
||||
<td className="px-4 py-2 text-gray-900">{c.title}</td>
|
||||
<td className="px-4 py-2 text-gray-900">
|
||||
<div>{c.title}</div>
|
||||
{c.is_gov || !c.applicable ? (
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{c.is_gov ? (
|
||||
<span className="rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-medium text-emerald-800">
|
||||
GOV · Öffentliche Stelle
|
||||
</span>
|
||||
) : null}
|
||||
{!c.applicable ? (
|
||||
<span className="rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-800">
|
||||
kein Kunden-Prüfaspekt · {addresseeLabel(c.addressee)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-xs text-gray-500">
|
||||
{c.sub_topic || '—'}
|
||||
</td>
|
||||
@@ -77,11 +97,15 @@ function ControlsTable({ rows }: { rows: ControlItem[] }) {
|
||||
|
||||
export default async function UseCaseControlsPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ useCase: string }>
|
||||
searchParams: Promise<{ [k: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const { useCase } = await params
|
||||
const data = await getControls(useCase)
|
||||
const sp = await searchParams
|
||||
const oos = sp.oos === '1'
|
||||
const data = await getControls(useCase, oos)
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
@@ -123,6 +147,25 @@ export default async function UseCaseControlsPage({
|
||||
</span>{' '}
|
||||
zur fachlichen Prüfung
|
||||
</p>
|
||||
{data.out_of_scope_count > 0 ? (
|
||||
<p className="text-xs">
|
||||
{data.include_out_of_scope ? (
|
||||
<Link
|
||||
href={`/sdk/coverage/${useCase}`}
|
||||
className="text-purple-600 hover:underline"
|
||||
>
|
||||
← Nur Kunden-Prüfaspekte ({data.out_of_scope_count.toLocaleString('de-DE')} Behörde/Mitgliedstaat/Dritter ausblenden)
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
href={`/sdk/coverage/${useCase}?oos=1`}
|
||||
className="text-amber-700 hover:underline"
|
||||
>
|
||||
+ {data.out_of_scope_count.toLocaleString('de-DE')} ausgeblendet (kein Kunden-Prüfaspekt: Behörde/Mitgliedstaat/Dritter) — einblenden
|
||||
</Link>
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<section className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user