feat(iace): FMEA source-document register + Anthropic extraction (Haiku)

Quote-verifiable failure extraction via Claude (Haiku 4.5): PDF sent
directly, tool-schema forces verbatim source quotes + applicable flag +
confidence — replaces the unreliable local llama run. Only applicable=true
tuples ingest into bp_iace_failure_kb; every processed doc lands in the
source manifest.

Frontend: FMEA tab now shows a "Quelldokumente" register (every document we
use, with source + licence + link + what was extracted) served from the
embedded manifest via GET /iace/failure-knowledge/sources. Manifest is
placeholder until the 100-doc Haiku run is folded in.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-13 13:34:41 +02:00
parent f1ac45dacf
commit 445079cfb2
9 changed files with 395 additions and 0 deletions
@@ -0,0 +1,77 @@
'use client'
import { useState } from 'react'
import { useFailureSources } from '../_hooks/useFailureSources'
/**
* FMEA source-document register: shows EVERY document the failure-knowledge
* corpus is built from, with source + licence + link. Collapsible; "used" =
* a concrete failure was extracted and ingested, "geprüft" = checked but not a
* failure. Full provenance, always visible.
*/
export function SourceDocuments() {
const { data } = useFailureSources()
const [open, setOpen] = useState(false)
if (!data) return null
const used = data.documents.filter((d) => d.used)
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">
Quelldokumente <span className="text-gray-400 font-normal">({used.length} verwendet / {data.count} geprüft · {data.model || 'extrahiert'})</span>
</span>
<span className="text-gray-400 text-xs">{open ? '▲' : '▼'}</span>
</button>
{open && (
<div className="px-4 pb-4">
{data.count === 0 ? (
<p className="text-xs text-gray-500">Noch keine Quelldokumente extrahiert.</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">Dokument</th>
<th className="py-1.5 pr-3">Quelle / Lizenz</th>
<th className="py-1.5 pr-3">Extrahiert</th>
<th className="py-1.5">Status</th>
</tr>
</thead>
<tbody>
{data.documents.map((d) => (
<tr key={d.id} className="border-b border-gray-100 dark:border-gray-700/50 align-top">
<td className="py-1.5 pr-3 max-w-xs">
<a href={d.url} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:underline">
{d.title || d.source}
</a>
</td>
<td className="py-1.5 pr-3 text-gray-500">
{d.source}<br /><span className="text-[10px] text-gray-400">{d.license}</span>
</td>
<td className="py-1.5 pr-3 text-gray-600 dark:text-gray-300">
{d.used ? `${d.component}${d.failure_mode}` : '—'}
{d.used && d.confidence && <span className="text-[10px] text-gray-400"> ({d.confidence})</span>}
</td>
<td className="py-1.5">
<span className={`inline-block rounded px-1.5 py-0.5 text-[10px] font-medium ${
d.used ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
: 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'}`}>
{d.used ? 'verwendet' : 'geprüft'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<p className="text-[10px] text-gray-400 mt-2">
Alle Quellen sind kommerziell nutzbar (lizenzgeprüft) und über den Link einsehbar. Extraktion
Claude-basiert mit Quell-Zitat je Wert (verified=false bis stichprobengeprüft).
</p>
</div>
)}
</div>
)
}
@@ -0,0 +1,46 @@
'use client'
import { useEffect, useState } from 'react'
export interface FailureSourceDoc {
id: number
title: string
source: string
license: string
url: string
used: boolean
component: string
failure_mode: string
confidence: string
}
export interface FailureSources {
generated: string
model: string
count: number
documents: FailureSourceDoc[]
}
/**
* Loads the FMEA source-document register (every document we use, with source +
* licence). Global, not per project — the auditable provenance of the
* failure-knowledge corpus.
*/
export function useFailureSources() {
const [data, setData] = useState<FailureSources | null>(null)
useEffect(() => {
let cancelled = false
fetch('/api/sdk/v1/iace/failure-knowledge/sources')
.then((r) => (r.ok ? r.json() : null))
.then((j) => {
if (!cancelled) setData(j)
})
.catch((err) => console.error('Failed to load failure sources:', err))
return () => {
cancelled = true
}
}, [])
return { data }
}
@@ -3,6 +3,7 @@
import { useEffect, useState } from 'react'
import { useParams } from 'next/navigation'
import { useFMEA, type FMEARow } from './_hooks/useFMEA'
import { SourceDocuments } from './_components/SourceDocuments'
const COMP_TYPE_LABELS: Record<string, string> = {
mechanical: 'Mechanisch', electrical: 'Elektrisch', sensor: 'Sensor',
@@ -60,6 +61,9 @@ export default function FMEAPage() {
{/* Info Box */}
<FMEAInfoBox />
{/* Source-document register (provenance, always visible) */}
<SourceDocuments />
{/* KI-Vorschlag + Export */}
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">