feat(cra): Datenblatt-Analyse-Frontend (Grenzen-Extraktion + Rückfragen)
DatasheetExtract auf /sdk/cra: Datenblatt einfügen (oder Beispiel OWIS/Zwick) → POST /extract-datasheet → gefuellte ISO-12100-Grenzen mit Quellen-Zitat + deterministisch erkannte Schnittstellen/Einheiten + gezielte Rückfragen fuer leere Pflichtfelder (foreseeable_misuses, person_groups, …). Vorstufe fuer 'Projekt anlegen' → IACE-Grenzen-Prefill. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { DATASHEET_EXAMPLES } from './readiness-presets'
|
||||||
|
|
||||||
|
interface Followup { key: string; label: string; question: string }
|
||||||
|
interface ExtractResult {
|
||||||
|
limits: Record<string, string>
|
||||||
|
provenance: Record<string, string>
|
||||||
|
detected: { interfaces: string[]; units: string[] }
|
||||||
|
filled: string[]
|
||||||
|
missing: string[]
|
||||||
|
followup: Followup[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIELD_LABEL: Record<string, string> = {
|
||||||
|
machine_designation: 'Maschinenbezeichnung', machine_type: 'Maschinentyp', manufacturer: 'Hersteller',
|
||||||
|
year_of_construction: 'Baujahr', general_description: 'Allgemeine Beschreibung',
|
||||||
|
intended_purpose: 'Verwendungszweck', area_of_use: 'Einsatzbereich', operating_modes: 'Betriebsarten',
|
||||||
|
variants: 'Varianten', foreseeable_misuses: 'Vorhersehbare Fehlanwendungen',
|
||||||
|
spatial_limits: 'Räumliche Grenzen', temporal_limits: 'Zeitliche Grenzen',
|
||||||
|
operating_conditions: 'Betriebsbedingungen', energy_supply: 'Energieversorgung',
|
||||||
|
mechanical_interfaces: 'Mechanische Schnittstellen', electrical_interfaces: 'Elektrische Schnittstellen',
|
||||||
|
software_interfaces: 'Software-Schnittstellen', pneumatic_hydraulic_interfaces: 'Pneumatik/Hydraulik',
|
||||||
|
person_groups: 'Personengruppen', qualification_requirements: 'Qualifikationsanforderungen',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DatasheetExtract() {
|
||||||
|
const [text, setText] = useState('')
|
||||||
|
const [res, setRes] = useState<ExtractResult | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [answers, setAnswers] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/v1/cra/extract-datasheet', {
|
||||||
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ text }),
|
||||||
|
})
|
||||||
|
setRes(r.ok ? await r.json() : null)
|
||||||
|
setAnswers({})
|
||||||
|
} finally { setLoading(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border border-indigo-200 dark:border-indigo-800 bg-indigo-50/50 dark:bg-indigo-900/20 p-5 mb-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Datenblatt-Analyse → Maschinengrenzen</h2>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1">
|
||||||
|
Datenblatt einfügen — wir füllen die ISO-12100-Grenzen automatisch (lokales KI-Modell, Datenhoheit)
|
||||||
|
und fragen gezielt nach, was nicht im Datenblatt steht. Jeder übernommene Wert trägt seine Quelle.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-2 mt-3">
|
||||||
|
<span className="text-xs text-gray-500">Beispiel laden:</span>
|
||||||
|
{DATASHEET_EXAMPLES.map((d) => (
|
||||||
|
<button key={d.id} onClick={() => { setText(d.text); setRes(null) }}
|
||||||
|
className="rounded border border-indigo-300 dark:border-indigo-700 bg-white dark:bg-gray-800 text-indigo-700 dark:text-indigo-300 text-xs px-2 py-1 hover:bg-indigo-100">
|
||||||
|
{d.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
value={text} onChange={(e) => setText(e.target.value)}
|
||||||
|
placeholder="Datenblatt-Text hier einfügen …"
|
||||||
|
className="w-full text-sm rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 p-2 mt-3 mb-3" rows={5}
|
||||||
|
/>
|
||||||
|
<button onClick={run} disabled={loading || text.length < 50}
|
||||||
|
className="rounded bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white text-sm px-4 py-2">
|
||||||
|
{loading ? 'Analysiere …' : 'Grenzen extrahieren'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{res && (
|
||||||
|
<div className="mt-5 space-y-4">
|
||||||
|
{(res.detected.interfaces.length > 0 || res.detected.units.length > 0) && (
|
||||||
|
<div className="text-xs text-gray-600 dark:text-gray-300">
|
||||||
|
<span className="font-medium">Deterministisch erkannt:</span>{' '}
|
||||||
|
{[...res.detected.interfaces, ...res.detected.units].map((d) => (
|
||||||
|
<span key={d} className="inline-block rounded bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 text-[11px] mr-1 mb-1">{d}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Aus dem Datenblatt übernommen */}
|
||||||
|
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-800 dark:text-gray-200">
|
||||||
|
Aus dem Datenblatt übernommen <span className="text-gray-400 font-normal">({res.filled.length})</span>
|
||||||
|
</h3>
|
||||||
|
<ul className="mt-2 space-y-1.5">
|
||||||
|
{res.filled.map((k) => (
|
||||||
|
<li key={k} className="text-xs text-gray-700 dark:text-gray-200">
|
||||||
|
<span className="font-medium">{FIELD_LABEL[k] || k}:</span> {res.limits[k]}
|
||||||
|
{res.provenance[k] && (
|
||||||
|
<span className="block text-[10px] text-gray-400 italic">Quelle: „{res.provenance[k]}"</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{res.filled.length === 0 && <li className="text-xs text-gray-400">Nichts eindeutig erkannt — bitte unten ergänzen.</li>}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rückfragen — nicht im Datenblatt */}
|
||||||
|
{res.followup.length > 0 && (
|
||||||
|
<div className="rounded-lg border border-amber-200 dark:border-amber-800 bg-amber-50/60 dark:bg-amber-900/20 p-3">
|
||||||
|
<h3 className="text-sm font-semibold text-amber-900 dark:text-amber-200">
|
||||||
|
Rückfragen — steht nicht im Datenblatt ({res.followup.length})
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-amber-800/80 dark:text-amber-300/80 mb-2">
|
||||||
|
Diese Angaben braucht die CE-Risikobeurteilung, ein Datenblatt liefert sie typischerweise nicht.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{res.followup.map((f) => (
|
||||||
|
<div key={f.key}>
|
||||||
|
<label className="block text-xs text-gray-700 dark:text-gray-200">{f.question}</label>
|
||||||
|
<input value={answers[f.key] || ''} onChange={(e) => setAnswers((a) => ({ ...a, [f.key]: e.target.value }))}
|
||||||
|
className="w-full text-sm rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 p-1.5 mt-0.5"
|
||||||
|
placeholder={f.label} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500 italic">
|
||||||
|
Nächster Schritt: „Projekt anlegen" überträgt diese Grenzen + Antworten in das IACE-Modul; daraus
|
||||||
|
werden Gefährdungen und Maßnahmen abgeleitet (Entwurf — Bestätigung mit Sicherheitsingenieur).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -58,3 +58,30 @@ export const READINESS_PRESETS: ReadinessPreset[] = [
|
|||||||
hazard_types: ['movement_crush'],
|
hazard_types: ['movement_crush'],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Example datasheet texts for the extraction demo (paste-equivalent). Sourced
|
||||||
|
// from public datasheets (owis.eu PS 90+, zwickroell.com roboTest / testXpert).
|
||||||
|
export const DATASHEET_EXAMPLES: { id: string; label: string; text: string }[] = [
|
||||||
|
{
|
||||||
|
id: 'owis',
|
||||||
|
label: 'OWIS PS 90+',
|
||||||
|
text:
|
||||||
|
'OWIS PS 90+ — Universelle Positioniersteuerung. Bis zu 9 Achsen für Schritt-, DC-, ' +
|
||||||
|
'BLDC- und Linearmotoren. Schnittstellen: USB, RS232, Ethernet; optionales Anybus-Modul ' +
|
||||||
|
'(Modbus/TCP) für industrielle Netzwerke. 8 TTL-, 8 Analog- und 8 SPS-Ein-/Ausgänge. ' +
|
||||||
|
'4 konfigurierbare Endschalter-Eingänge pro Achse. Ereignisbasierte Triggersignale. ' +
|
||||||
|
'Betriebssoftware OWISoft 3.0. SDK für C, C++, C#, LabView (32/64 Bit). Versorgung 24 V. ' +
|
||||||
|
'Optionale Motor-Haltebremsen für bis zu 4 Achsen. Anschluss für externen Not-Halt-Taster. ' +
|
||||||
|
'PC-Betrieb unter Windows 10/11.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zwick',
|
||||||
|
label: 'ZwickRoell roboTest',
|
||||||
|
text:
|
||||||
|
'ZwickRoell roboTest — automatisiertes Prüfsystem. Roboter-Probenhandling für 24/7-Betrieb. ' +
|
||||||
|
'Software autoEdition (Datenmanagement, Steuerung, Prozessvisualisierung) und Prüfsoftware ' +
|
||||||
|
'testXpert (Windows 10/11). Volle Prozesssteuerung per Browser; Systemstatus auf Tablet-PCs. ' +
|
||||||
|
'Direkte Datenanbindung an Host-Systeme (ERP/LIMS/QA). Umfangreiche Benutzer- und ' +
|
||||||
|
'Rechteverwaltung. 2D-Barcode/QR-Scanner. Automatische Querschnittsmessung mit Tastern.',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React, { useState, useEffect, useCallback } from 'react'
|
|||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { ClassificationBadge } from './_components/ClassificationBadge'
|
import { ClassificationBadge } from './_components/ClassificationBadge'
|
||||||
import { ReadinessCheck } from './_components/ReadinessCheck'
|
import { ReadinessCheck } from './_components/ReadinessCheck'
|
||||||
|
import { DatasheetExtract } from './_components/DatasheetExtract'
|
||||||
|
|
||||||
interface CRAProject {
|
interface CRAProject {
|
||||||
id: string
|
id: string
|
||||||
@@ -102,6 +103,8 @@ export default function CRAProjectsPage() {
|
|||||||
|
|
||||||
<ReadinessCheck onCreateProject={() => setShowModal(true)} />
|
<ReadinessCheck onCreateProject={() => setShowModal(true)} />
|
||||||
|
|
||||||
|
<DatasheetExtract />
|
||||||
|
|
||||||
{/* Bridge: vom Readiness-Check in die kombinierte CE × Cyber-Analyse */}
|
{/* Bridge: vom Readiness-Check in die kombinierte CE × Cyber-Analyse */}
|
||||||
<a
|
<a
|
||||||
href="/sdk/iace/e79921be-c78a-47ab-8dfa-5fa48ba8a34a/cra"
|
href="/sdk/iace/e79921be-c78a-47ab-8dfa-5fa48ba8a34a/cra"
|
||||||
|
|||||||
Reference in New Issue
Block a user