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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user