feat(iace): show ESAW evidence panel in risk view (B1)
The Risikobewertung page only mentioned the data sources as static prose. Add a collapsible "Datenquellen & Evidenz" panel sourced from /iace/risk-data-sources: the real Eurostat ESAW 2023 contact-mode shares per mode, with license + ready-to-print attribution, and the note that tiers anchor the ordering while values stay GT-calibrated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { RiskDataSources as RiskDataSourcesData } from '../_hooks/useRiskDataSources'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapsible evidence panel: the real public-statistics figures (Eurostat ESAW
|
||||||
|
* 2023) that anchor the W/S tiers, with license + ready-to-print attribution.
|
||||||
|
* Confidence-aware tonality — informs the source, does not alarm.
|
||||||
|
*/
|
||||||
|
export function RiskDataSources({ data }: { data: RiskDataSourcesData }) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
if (!data.evidence?.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className="w-full flex items-center justify-between px-4 py-3 text-left"
|
||||||
|
>
|
||||||
|
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||||
|
Datenquellen & Evidenz <span className="text-gray-400 font-normal">({data.evidence.length} belegte Kontaktmodi)</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-400 text-xs">{open ? '▲' : '▼'}</span>
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="px-4 pb-4 space-y-3">
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">{data.note}</p>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-gray-500 border-b border-gray-200 dark:border-gray-700 text-left">
|
||||||
|
<th className="py-1.5 pr-3">Kontaktmodus</th>
|
||||||
|
<th className="py-1.5 pr-3">Belegte Quote</th>
|
||||||
|
<th className="py-1.5 pr-3">Quelle</th>
|
||||||
|
<th className="py-1.5">Lizenz</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.evidence.map((e) => (
|
||||||
|
<tr key={e.mode} className="border-b border-gray-100 dark:border-gray-700/50 align-top">
|
||||||
|
<td className="py-1.5 pr-3 text-gray-700 dark:text-gray-300 font-medium">{e.label}</td>
|
||||||
|
<td className="py-1.5 pr-3 text-gray-600 dark:text-gray-300 tabular-nums">{e.stat}</td>
|
||||||
|
<td className="py-1.5 pr-3 text-gray-500">{e.source}</td>
|
||||||
|
<td className="py-1.5 text-gray-500">{e.license}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-gray-400">
|
||||||
|
{data.evidence[0]?.attribution} · Tiers verankern die Quoten-<em>Ordnung</em>; die Werte sind an
|
||||||
|
BreakPilot-Ground-Truth kalibriert (keine Norm-Tabelle reproduziert).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export interface RiskEvidence {
|
||||||
|
mode: string
|
||||||
|
label: string
|
||||||
|
stat: string
|
||||||
|
source: string
|
||||||
|
license: string
|
||||||
|
attribution: string
|
||||||
|
retrieved: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RiskDataSources {
|
||||||
|
note: string
|
||||||
|
evidence: RiskEvidence[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the license-tagged public-statistics evidence behind the risk-frequency
|
||||||
|
* anchors (Eurostat ESAW hsw_ph3_08, 2023). Global, not per project — so an
|
||||||
|
* auditor can see WHERE the W/S tiers come from and the source is cited.
|
||||||
|
*/
|
||||||
|
export function useRiskDataSources() {
|
||||||
|
const [data, setData] = useState<RiskDataSources | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/sdk/v1/iace/risk-data-sources')
|
||||||
|
if (!res.ok) return
|
||||||
|
const json = (await res.json()) as RiskDataSources
|
||||||
|
if (!cancelled) setData(json)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load risk data sources:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return { data }
|
||||||
|
}
|
||||||
@@ -3,14 +3,17 @@
|
|||||||
import { useParams } from 'next/navigation'
|
import { useParams } from 'next/navigation'
|
||||||
import { useRiskAssessment } from './_hooks/useRiskAssessment'
|
import { useRiskAssessment } from './_hooks/useRiskAssessment'
|
||||||
import { useRiskMatrix } from './_hooks/useRiskMatrix'
|
import { useRiskMatrix } from './_hooks/useRiskMatrix'
|
||||||
|
import { useRiskDataSources } from './_hooks/useRiskDataSources'
|
||||||
import { RiskModelCard } from './_components/RiskModelCard'
|
import { RiskModelCard } from './_components/RiskModelCard'
|
||||||
import { RiskMatrix } from './_components/RiskMatrix'
|
import { RiskMatrix } from './_components/RiskMatrix'
|
||||||
|
import { RiskDataSources } from './_components/RiskDataSources'
|
||||||
|
|
||||||
export default function RisikobewertungPage() {
|
export default function RisikobewertungPage() {
|
||||||
const params = useParams<{ projectId: string }>()
|
const params = useParams<{ projectId: string }>()
|
||||||
const projectId = params.projectId
|
const projectId = params.projectId
|
||||||
const { hazards, suggestions, loading } = useRiskAssessment(projectId)
|
const { hazards, suggestions, loading } = useRiskAssessment(projectId)
|
||||||
const { data: matrix } = useRiskMatrix(projectId)
|
const { data: matrix } = useRiskMatrix(projectId)
|
||||||
|
const { data: dataSources } = useRiskDataSources()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -26,6 +29,8 @@ export default function RisikobewertungPage() {
|
|||||||
|
|
||||||
{matrix && matrix.total > 0 && <RiskMatrix data={matrix} />}
|
{matrix && matrix.total > 0 && <RiskMatrix data={matrix} />}
|
||||||
|
|
||||||
|
{dataSources && <RiskDataSources data={dataSources} />}
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">Lade Gefaehrdungen…</div>
|
<div className="text-sm text-gray-500 dark:text-gray-400">Lade Gefaehrdungen…</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user