Files
breakpilot-compliance/admin-compliance/app/sdk/quality/_hooks/useQuaidalData.ts
T
Benjamin Admin e536247c20 feat(quaidal): backend API + frontend tab for BSI QUAIDAL data-quality controls
Wire the 195 Clean-Room QUAIDAL controls (from breakpilot-core migration 011)
into the compliance SaaS UI.

Backend:
- GET /api/v1/quaidal/stats           - counts by kind + source provenance
- GET /api/v1/quaidal/controls        - list, optional kind= filter
- GET /api/v1/quaidal/controls/{id}   - single derived control
- GET /api/v1/quaidal/criteria        - 10 QKB criteria
- GET /api/v1/quaidal/criteria/{id}   - QKB with QB/MA/QM tree

Frontend:
- /sdk/quality: new "Trainingsdaten-Qualität (BSI QUAIDAL)" tab with
  10 QKB cards and a drill-down modal showing the full QB→MA→QM tree
  plus original BSI source link and license note.
- /sdk/ai-act: Art. 10 tile on each high-risk/unacceptable result,
  linking to /sdk/quality?category=data_quality.

Pattern matches existing IACE module DIN-reference handling:
own wording, source section + URL preserved for due diligence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:03:54 +02:00

87 lines
2.4 KiB
TypeScript

'use client'
import { useCallback, useEffect, useState } from 'react'
export interface QuaidalExternalRef {
framework: string
citation: string | null
}
export interface QuaidalSource {
framework: string
section: string
url: string | null
commit_sha: string | null
title_original: string | null
license_note: string | null
}
export interface QuaidalControl {
derived_id: string
kind: 'criterion' | 'building_block' | 'measure' | 'metric'
canonical_name: string
description: string
regulation_anchor: string | null
related_quaidal_ids: string[]
external_refs: QuaidalExternalRef[]
source: QuaidalSource
plagiarism_score: number | null
}
export interface QuaidalStats {
counts_by_kind: Record<string, number>
source_framework: string
source_commit_sha: string | null
license_note: string | null
}
export interface QuaidalCriterionTree {
criterion: QuaidalControl
building_blocks: QuaidalControl[]
measures: QuaidalControl[]
metrics: QuaidalControl[]
}
const API_BASE = '/api/sdk/v1/quaidal'
export function useQuaidalData() {
const [criteria, setCriteria] = useState<QuaidalControl[]>([])
const [stats, setStats] = useState<QuaidalStats | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const loadAll = useCallback(async () => {
setLoading(true)
setError(null)
try {
const [criteriaRes, statsRes] = await Promise.all([
fetch(`${API_BASE}/criteria`, { cache: 'no-store' }),
fetch(`${API_BASE}/stats`, { cache: 'no-store' }),
])
if (criteriaRes.ok) {
const data = (await criteriaRes.json()) as QuaidalControl[]
setCriteria(Array.isArray(data) ? data : [])
} else {
setError(`Criteria endpoint returned ${criteriaRes.status}`)
}
if (statsRes.ok) {
setStats(await statsRes.json())
}
} catch (err) {
setError(String(err))
} finally {
setLoading(false)
}
}, [])
useEffect(() => { loadAll() }, [loadAll])
return { criteria, stats, loading, error, reload: loadAll }
}
export async function fetchCriterionTree(sectionId: string): Promise<QuaidalCriterionTree | null> {
const res = await fetch(`${API_BASE}/criteria/${encodeURIComponent(sectionId)}`, { cache: 'no-store' })
if (!res.ok) return null
return (await res.json()) as QuaidalCriterionTree
}