All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 36s
CI/CD / test-python-backend-compliance (push) Successful in 33s
CI/CD / test-python-document-crawler (push) Successful in 24s
CI/CD / test-python-dsms-gateway (push) Successful in 21s
CI/CD / validate-canonical-controls (push) Successful in 13s
CI/CD / Deploy (push) Successful in 2s
Phase 1: Fix completeness gates G23 (require verified/rejected mitigations) and G09 (audit trail check) Phase 2: LLM-based tech-file section generation with 19 German prompts and RAG enrichment Phase 3: Multi-format document export (PDF/Excel/DOCX/Markdown/JSON) Phase 4: Company profile → IACE data flow with auto component/classification creation Phase 5: TipTap WYSIWYG editor replacing textarea for tech-file sections Phase 6: User journey tests, developer portal API reference, updated documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
680 lines
30 KiB
Go
680 lines
30 KiB
Go
package iace
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ============================================================================
|
|
// TechFileGenerator — LLM-based generation of technical file sections
|
|
// ============================================================================
|
|
|
|
// TechFileGenerator generates technical file section content using LLM and RAG.
|
|
type TechFileGenerator struct {
|
|
llmRegistry *llm.ProviderRegistry
|
|
ragClient *ucca.LegalRAGClient
|
|
store *Store
|
|
}
|
|
|
|
// NewTechFileGenerator creates a new TechFileGenerator.
|
|
func NewTechFileGenerator(registry *llm.ProviderRegistry, ragClient *ucca.LegalRAGClient, store *Store) *TechFileGenerator {
|
|
return &TechFileGenerator{
|
|
llmRegistry: registry,
|
|
ragClient: ragClient,
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
// SectionGenerationContext holds all project data needed for LLM section generation.
|
|
type SectionGenerationContext struct {
|
|
Project *Project
|
|
Components []Component
|
|
Hazards []Hazard
|
|
Assessments map[uuid.UUID][]RiskAssessment // keyed by hazardID
|
|
Mitigations map[uuid.UUID][]Mitigation // keyed by hazardID
|
|
Classifications []RegulatoryClassification
|
|
Evidence []Evidence
|
|
RAGContext string // aggregated text from RAG search
|
|
}
|
|
|
|
// ============================================================================
|
|
// Section type constants
|
|
// ============================================================================
|
|
|
|
const (
|
|
SectionRiskAssessmentReport = "risk_assessment_report"
|
|
SectionHazardLogCombined = "hazard_log_combined"
|
|
SectionGeneralDescription = "general_description"
|
|
SectionEssentialRequirements = "essential_requirements"
|
|
SectionDesignSpecifications = "design_specifications"
|
|
SectionTestReports = "test_reports"
|
|
SectionStandardsApplied = "standards_applied"
|
|
SectionDeclarationConformity = "declaration_of_conformity"
|
|
SectionAIIntendedPurpose = "ai_intended_purpose"
|
|
SectionAIModelDescription = "ai_model_description"
|
|
SectionAIRiskManagement = "ai_risk_management"
|
|
SectionAIHumanOversight = "ai_human_oversight"
|
|
SectionComponentList = "component_list"
|
|
SectionClassificationReport = "classification_report"
|
|
SectionMitigationReport = "mitigation_report"
|
|
SectionVerificationReport = "verification_report"
|
|
SectionEvidenceIndex = "evidence_index"
|
|
SectionInstructionsForUse = "instructions_for_use"
|
|
SectionMonitoringPlan = "monitoring_plan"
|
|
)
|
|
|
|
// ============================================================================
|
|
// System prompts (German — CE compliance context)
|
|
// ============================================================================
|
|
|
|
var sectionSystemPrompts = map[string]string{
|
|
SectionRiskAssessmentReport: `Du bist CE-Experte fuer Maschinen- und KI-Sicherheit. Erstelle eine strukturierte Zusammenfassung der Risikobeurteilung gemaess ISO 12100 und EN ISO 13849. Gliederung: 1) Methodik, 2) Risikoueberblick (Anzahl Gefaehrdungen nach Risikostufe), 3) Kritische Risiken, 4) Akzeptanzbewertung, 5) Empfehlungen. Verwende Fachterminologie und beziehe dich auf die konkreten Projektdaten.`,
|
|
|
|
SectionHazardLogCombined: `Erstelle ein tabellarisches Gefaehrdungsprotokoll (Hazard Log) fuer die technische Dokumentation. Jede Gefaehrdung soll enthalten: ID, Bezeichnung, Kategorie, Lebenszyklusphase, Szenario, Schwere, Eintrittswahrscheinlichkeit, Risikolevel, Massnahmen und Status. Formatiere als strukturierte Tabelle in Markdown.`,
|
|
|
|
SectionGeneralDescription: `Erstelle eine allgemeine Maschinenbeschreibung fuer die technische Dokumentation gemaess EU-Maschinenverordnung 2023/1230 Anhang IV. Beschreibe: 1) Bestimmungsgemaesse Verwendung, 2) Aufbau und Funktion, 3) Systemkomponenten, 4) Betriebsbedingungen, 5) Schnittstellen. Verwende die bereitgestellten Projektdaten.`,
|
|
|
|
SectionEssentialRequirements: `Beschreibe die anwendbaren grundlegenden Anforderungen (Essential Health and Safety Requirements — EHSR) gemaess EU-Maschinenverordnung 2023/1230 Anhang III. Ordne jede Anforderung den relevanten Gefaehrdungen und Massnahmen zu. Beruecksichtige auch AI Act und CRA Anforderungen falls KI-Komponenten vorhanden sind.`,
|
|
|
|
SectionDesignSpecifications: `Erstelle eine Uebersicht der Konstruktionsdaten und Spezifikationen fuer die technische Dokumentation. Enthalten sein sollen: 1) Systemarchitektur, 2) Komponentenliste mit Sicherheitsrelevanz, 3) Software-/Firmware-Versionen, 4) Schnittstellenbeschreibungen, 5) Sicherheitsfunktionen. Beziehe dich auf die konkreten Komponenten.`,
|
|
|
|
SectionTestReports: `Erstelle eine Zusammenfassung der Pruefberichte und Verifikationsergebnisse. Gliederung: 1) Durchgefuehrte Pruefungen, 2) Pruefmethoden (Test, Analyse, Inspektion), 3) Ergebnisse pro Massnahme, 4) Offene Punkte, 5) Gesamtbewertung. Referenziere die konkreten Mitigationsmassnahmen und deren Verifikationsstatus.`,
|
|
|
|
SectionStandardsApplied: `Liste die angewandten harmonisierten Normen und technischen Spezifikationen auf. Ordne jede Norm den relevanten Anforderungen und Gefaehrdungskategorien zu. Beruecksichtige: ISO 12100, ISO 13849, IEC 62443, ISO/IEC 27001, sowie branchenspezifische Normen. Erklaere die Vermutungswirkung (Presumption of Conformity).`,
|
|
|
|
SectionDeclarationConformity: `Erstelle eine EU-Konformitaetserklaerung nach EU-Maschinenverordnung 2023/1230 Anhang IV. Enthalten sein muessen: 1) Hersteller-Angaben, 2) Produktidentifikation, 3) Angewandte Richtlinien und Verordnungen, 4) Angewandte Normen, 5) Bevollmaechtigter, 6) Ort, Datum, Unterschrift. Formales Dokument-Layout.`,
|
|
|
|
SectionAIIntendedPurpose: `Beschreibe den bestimmungsgemaessen Zweck des KI-Systems gemaess AI Act Art. 13 (Transparenzpflichten). Enthalten sein sollen: 1) Zweckbestimmung, 2) Einsatzbereich und -grenzen, 3) Zielgruppe, 4) Vorhersehbarer Fehlgebrauch, 5) Leistungskennzahlen, 6) Einschraenkungen und bekannte Risiken.`,
|
|
|
|
SectionAIModelDescription: `Beschreibe das KI-Modell, die Trainingsdaten und die Architektur gemaess AI Act Anhang IV. Enthalten: 1) Modelltyp und Architektur, 2) Trainingsdaten (Herkunft, Umfang, Qualitaet), 3) Validierungsmethodik, 4) Leistungsmetriken, 5) Bekannte Verzerrungen (Bias), 6) Energie-/Ressourcenverbrauch.`,
|
|
|
|
SectionAIRiskManagement: `Erstelle eine Beschreibung des KI-Risikomanagementsystems gemaess AI Act Art. 9. Gliederung: 1) Risikomanagement-Prozess, 2) Identifizierte Risiken fuer Gesundheit/Sicherheit/Grundrechte, 3) Risikomindernde Massnahmen, 4) Restrisiken, 5) Ueberwachungs- und Aktualisierungsverfahren.`,
|
|
|
|
SectionAIHumanOversight: `Beschreibe die Massnahmen zur menschlichen Aufsicht (Human Oversight) gemaess AI Act Art. 14. Enthalten: 1) Aufsichtskonzept, 2) Rollen und Verantwortlichkeiten, 3) Eingriffsmoglichkeiten, 4) Uebersteuern/Abschalten, 5) Schulungsanforderungen, 6) Informationspflichten an Nutzer.`,
|
|
|
|
SectionComponentList: `Erstelle eine detaillierte Komponentenliste fuer die technische Dokumentation. Pro Komponente: Name, Typ, Version, Beschreibung, Sicherheitsrelevanz, Vernetzungsstatus. Kennzeichne sicherheitsrelevante und vernetzte Komponenten besonders. Gruppiere nach Komponententyp.`,
|
|
|
|
SectionClassificationReport: `Erstelle einen Klassifizierungsbericht, der die regulatorische Einordnung des Produkts zusammenfasst. Pro Verordnung (MVO, AI Act, CRA, NIS2): Klassifizierungsergebnis, Risikoklasse, Begruendung, daraus resultierende Anforderungen. Bewerte die Gesamtkonformitaetslage.`,
|
|
|
|
SectionMitigationReport: `Erstelle einen Massnahmenbericht (Mitigation Report) fuer die technische Dokumentation. Gliederung nach 3-Stufen-Methode: 1) Inhaerent sichere Konstruktion (Design), 2) Technische Schutzmassnahmen (Protective), 3) Benutzerinformation (Information). Pro Massnahme: Status, Verifikation, zugeordnete Gefaehrdung.`,
|
|
|
|
SectionVerificationReport: `Erstelle einen Verifikationsbericht ueber alle durchgefuehrten Pruef- und Nachweisverfahren. Enthalten: 1) Verifikationsplan-Uebersicht, 2) Durchgefuehrte Pruefungen nach Methode, 3) Ergebnisse und Bewertung, 4) Offene Verifikationen, 5) Gesamtstatus der Konformitaetsnachweise.`,
|
|
|
|
SectionEvidenceIndex: `Erstelle ein Nachweisverzeichnis (Evidence Index) fuer die technische Dokumentation. Liste alle vorhandenen Nachweisdokumente auf: Dateiname, Beschreibung, zugeordnete Massnahme, Dokumenttyp. Identifiziere fehlende Nachweise und empfehle Ergaenzungen.`,
|
|
|
|
SectionInstructionsForUse: `Erstelle eine Gliederung fuer die Betriebsanleitung gemaess EU-Maschinenverordnung 2023/1230 Anhang III Abschnitt 1.7.4. Enthalten: 1) Bestimmungsgemaesse Verwendung, 2) Inbetriebnahme, 3) Sicherer Betrieb, 4) Wartung, 5) Restrisiken und Warnhinweise, 6) Ausserbetriebnahme. Beruecksichtige identifizierte Gefaehrdungen.`,
|
|
|
|
SectionMonitoringPlan: `Erstelle einen Post-Market-Monitoring-Plan fuer das Produkt. Enthalten: 1) Ueberwachungsziele, 2) Datenquellen (Kundenfeedback, Vorfaelle, Updates), 3) Ueberwachungsintervalle, 4) Eskalationsverfahren, 5) Dokumentationspflichten, 6) Verantwortlichkeiten. Beruecksichtige AI Act Art. 72 (Post-Market Monitoring) falls KI-Komponenten vorhanden.`,
|
|
}
|
|
|
|
// ============================================================================
|
|
// RAG query mapping
|
|
// ============================================================================
|
|
|
|
func buildRAGQuery(sectionType string) string {
|
|
ragQueries := map[string]string{
|
|
SectionRiskAssessmentReport: "Risikobeurteilung ISO 12100 Risikobewertung Maschine Gefaehrdungsanalyse",
|
|
SectionHazardLogCombined: "Gefaehrdungsprotokoll Hazard Log Risikoanalyse Gefaehrdungsidentifikation",
|
|
SectionGeneralDescription: "Maschinenbeschreibung technische Dokumentation bestimmungsgemaesse Verwendung",
|
|
SectionEssentialRequirements: "grundlegende Anforderungen EHSR Maschinenverordnung Anhang III Sicherheitsanforderungen",
|
|
SectionDesignSpecifications: "Konstruktionsdaten Spezifikationen Systemarchitektur technische Dokumentation",
|
|
SectionTestReports: "Pruefberichte Verifikation Validierung Konformitaetsbewertung Testberichte",
|
|
SectionStandardsApplied: "harmonisierte Normen ISO 12100 ISO 13849 IEC 62443 Vermutungswirkung",
|
|
SectionDeclarationConformity: "EU-Konformitaetserklaerung Maschinenverordnung 2023/1230 Anhang IV CE-Kennzeichnung",
|
|
SectionAIIntendedPurpose: "bestimmungsgemaesser Zweck KI-System AI Act Art. 13 Transparenz Intended Purpose",
|
|
SectionAIModelDescription: "KI-Modell Trainingsdaten Architektur AI Act Anhang IV technische Dokumentation",
|
|
SectionAIRiskManagement: "KI-Risikomanagementsystem AI Act Art. 9 Risikomanagement kuenstliche Intelligenz",
|
|
SectionAIHumanOversight: "menschliche Aufsicht Human Oversight AI Act Art. 14 Kontrolle KI-System",
|
|
SectionComponentList: "Komponentenliste Systemkomponenten sicherheitsrelevante Bauteile technische Dokumentation",
|
|
SectionClassificationReport: "regulatorische Klassifizierung Risikoklasse AI Act CRA Maschinenverordnung",
|
|
SectionMitigationReport: "Risikomindernde Massnahmen 3-Stufen-Methode ISO 12100 Schutzmassnahmen",
|
|
SectionVerificationReport: "Verifikation Validierung Pruefnachweis Konformitaetsbewertung Pruefprotokoll",
|
|
SectionEvidenceIndex: "Nachweisdokumente Evidence Konformitaetsnachweis Dokumentenindex",
|
|
SectionInstructionsForUse: "Betriebsanleitung Benutzerinformation Maschinenverordnung Abschnitt 1.7.4 Sicherheitshinweise",
|
|
SectionMonitoringPlan: "Post-Market-Monitoring Ueberwachungsplan AI Act Art. 72 Marktbeobachtung",
|
|
}
|
|
|
|
if q, ok := ragQueries[sectionType]; ok {
|
|
return q
|
|
}
|
|
return "CE-Konformitaet technische Dokumentation Maschinenverordnung AI Act"
|
|
}
|
|
|
|
// ============================================================================
|
|
// BuildSectionContext — loads all project data + RAG context
|
|
// ============================================================================
|
|
|
|
// BuildSectionContext loads project data and RAG context for a given section type.
|
|
func (g *TechFileGenerator) BuildSectionContext(ctx context.Context, projectID uuid.UUID, sectionType string) (*SectionGenerationContext, error) {
|
|
// Load project
|
|
project, err := g.store.GetProject(ctx, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load project: %w", err)
|
|
}
|
|
if project == nil {
|
|
return nil, fmt.Errorf("project %s not found", projectID)
|
|
}
|
|
|
|
// Load components
|
|
components, err := g.store.ListComponents(ctx, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load components: %w", err)
|
|
}
|
|
|
|
// Load hazards
|
|
hazards, err := g.store.ListHazards(ctx, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load hazards: %w", err)
|
|
}
|
|
|
|
// Load assessments and mitigations per hazard
|
|
assessments := make(map[uuid.UUID][]RiskAssessment)
|
|
mitigations := make(map[uuid.UUID][]Mitigation)
|
|
for _, h := range hazards {
|
|
a, err := g.store.ListAssessments(ctx, h.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load assessments for hazard %s: %w", h.ID, err)
|
|
}
|
|
assessments[h.ID] = a
|
|
|
|
m, err := g.store.ListMitigations(ctx, h.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load mitigations for hazard %s: %w", h.ID, err)
|
|
}
|
|
mitigations[h.ID] = m
|
|
}
|
|
|
|
// Load classifications
|
|
classifications, err := g.store.GetClassifications(ctx, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load classifications: %w", err)
|
|
}
|
|
|
|
// Load evidence
|
|
evidence, err := g.store.ListEvidence(ctx, projectID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load evidence: %w", err)
|
|
}
|
|
|
|
// Perform RAG search for section-specific context
|
|
ragContext := ""
|
|
if g.ragClient != nil {
|
|
ragQuery := buildRAGQuery(sectionType)
|
|
results, ragErr := g.ragClient.SearchCollection(ctx, "bp_iace_libraries", ragQuery, nil, 5)
|
|
if ragErr == nil && len(results) > 0 {
|
|
var ragParts []string
|
|
for _, r := range results {
|
|
entry := fmt.Sprintf("[%s] %s", r.RegulationShort, truncateForPrompt(r.Text, 400))
|
|
ragParts = append(ragParts, entry)
|
|
}
|
|
ragContext = strings.Join(ragParts, "\n\n")
|
|
}
|
|
// RAG failure is non-fatal — we proceed without context
|
|
}
|
|
|
|
return &SectionGenerationContext{
|
|
Project: project,
|
|
Components: components,
|
|
Hazards: hazards,
|
|
Assessments: assessments,
|
|
Mitigations: mitigations,
|
|
Classifications: classifications,
|
|
Evidence: evidence,
|
|
RAGContext: ragContext,
|
|
}, nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// GenerateSection — main entry point
|
|
// ============================================================================
|
|
|
|
// GenerateSection generates the content for a technical file section using LLM.
|
|
// If LLM is unavailable, returns an enhanced placeholder with project data.
|
|
func (g *TechFileGenerator) GenerateSection(ctx context.Context, projectID uuid.UUID, sectionType string) (string, error) {
|
|
sctx, err := g.BuildSectionContext(ctx, projectID, sectionType)
|
|
if err != nil {
|
|
return "", fmt.Errorf("build section context: %w", err)
|
|
}
|
|
|
|
// Build prompts
|
|
systemPrompt := getSystemPrompt(sectionType)
|
|
userPrompt := buildUserPrompt(sctx, sectionType)
|
|
|
|
// Attempt LLM generation
|
|
resp, err := g.llmRegistry.Chat(ctx, &llm.ChatRequest{
|
|
Messages: []llm.Message{
|
|
{Role: "system", Content: systemPrompt},
|
|
{Role: "user", Content: userPrompt},
|
|
},
|
|
Temperature: 0.15,
|
|
MaxTokens: 4096,
|
|
})
|
|
if err != nil {
|
|
// LLM unavailable — return structured fallback with real project data
|
|
return buildFallbackContent(sctx, sectionType), nil
|
|
}
|
|
|
|
return resp.Message.Content, nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Prompt builders
|
|
// ============================================================================
|
|
|
|
func getSystemPrompt(sectionType string) string {
|
|
if prompt, ok := sectionSystemPrompts[sectionType]; ok {
|
|
return prompt
|
|
}
|
|
return "Du bist CE-Experte fuer technische Dokumentation. Erstelle den angeforderten Abschnitt der technischen Dokumentation basierend auf den bereitgestellten Projektdaten. Schreibe auf Deutsch, verwende Fachterminologie und beziehe dich auf die konkreten Daten."
|
|
}
|
|
|
|
func buildUserPrompt(sctx *SectionGenerationContext, sectionType string) string {
|
|
var b strings.Builder
|
|
|
|
if sctx == nil || sctx.Project == nil {
|
|
b.WriteString("## Maschine / System\n\n- Keine Projektdaten vorhanden.\n\n")
|
|
return b.String()
|
|
}
|
|
|
|
// Machine info — always included
|
|
b.WriteString("## Maschine / System\n\n")
|
|
b.WriteString(fmt.Sprintf("- **Name:** %s\n", sctx.Project.MachineName))
|
|
b.WriteString(fmt.Sprintf("- **Typ:** %s\n", sctx.Project.MachineType))
|
|
b.WriteString(fmt.Sprintf("- **Hersteller:** %s\n", sctx.Project.Manufacturer))
|
|
if sctx.Project.Description != "" {
|
|
b.WriteString(fmt.Sprintf("- **Beschreibung:** %s\n", sctx.Project.Description))
|
|
}
|
|
if sctx.Project.CEMarkingTarget != "" {
|
|
b.WriteString(fmt.Sprintf("- **CE-Kennzeichnungsziel:** %s\n", sctx.Project.CEMarkingTarget))
|
|
}
|
|
if sctx.Project.NarrativeText != "" {
|
|
b.WriteString(fmt.Sprintf("\n**Projektbeschreibung:** %s\n", truncateForPrompt(sctx.Project.NarrativeText, 500)))
|
|
}
|
|
b.WriteString("\n")
|
|
|
|
// Components — for most section types
|
|
if len(sctx.Components) > 0 && needsComponents(sectionType) {
|
|
b.WriteString("## Komponenten\n\n")
|
|
for i, c := range sctx.Components {
|
|
if i >= 20 {
|
|
b.WriteString(fmt.Sprintf("... und %d weitere Komponenten\n", len(sctx.Components)-20))
|
|
break
|
|
}
|
|
safety := ""
|
|
if c.IsSafetyRelevant {
|
|
safety = " [SICHERHEITSRELEVANT]"
|
|
}
|
|
networked := ""
|
|
if c.IsNetworked {
|
|
networked = " [VERNETZT]"
|
|
}
|
|
b.WriteString(fmt.Sprintf("- %s (Typ: %s)%s%s", c.Name, string(c.ComponentType), safety, networked))
|
|
if c.Description != "" {
|
|
b.WriteString(fmt.Sprintf(" — %s", truncateForPrompt(c.Description, 100)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// Hazards + assessments — for risk-related sections
|
|
if len(sctx.Hazards) > 0 && needsHazards(sectionType) {
|
|
b.WriteString("## Gefaehrdungen und Risikobewertungen\n\n")
|
|
for i, h := range sctx.Hazards {
|
|
if i >= 30 {
|
|
b.WriteString(fmt.Sprintf("... und %d weitere Gefaehrdungen\n", len(sctx.Hazards)-30))
|
|
break
|
|
}
|
|
b.WriteString(fmt.Sprintf("### %s\n", h.Name))
|
|
b.WriteString(fmt.Sprintf("- Kategorie: %s", h.Category))
|
|
if h.SubCategory != "" {
|
|
b.WriteString(fmt.Sprintf(" / %s", h.SubCategory))
|
|
}
|
|
b.WriteString("\n")
|
|
if h.LifecyclePhase != "" {
|
|
b.WriteString(fmt.Sprintf("- Lebenszyklusphase: %s\n", h.LifecyclePhase))
|
|
}
|
|
if h.Scenario != "" {
|
|
b.WriteString(fmt.Sprintf("- Szenario: %s\n", truncateForPrompt(h.Scenario, 150)))
|
|
}
|
|
if h.PossibleHarm != "" {
|
|
b.WriteString(fmt.Sprintf("- Moeglicher Schaden: %s\n", h.PossibleHarm))
|
|
}
|
|
if h.AffectedPerson != "" {
|
|
b.WriteString(fmt.Sprintf("- Betroffene Person: %s\n", h.AffectedPerson))
|
|
}
|
|
b.WriteString(fmt.Sprintf("- Status: %s\n", string(h.Status)))
|
|
|
|
// Latest assessment
|
|
if assessments, ok := sctx.Assessments[h.ID]; ok && len(assessments) > 0 {
|
|
a := assessments[len(assessments)-1] // latest
|
|
b.WriteString(fmt.Sprintf("- Bewertung: S=%d E=%d P=%d → Risiko=%.1f (%s) %s\n",
|
|
a.Severity, a.Exposure, a.Probability,
|
|
a.ResidualRisk, string(a.RiskLevel),
|
|
acceptableLabel(a.IsAcceptable)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
// Mitigations — for mitigation/verification sections
|
|
if needsMitigations(sectionType) {
|
|
designMeasures, protectiveMeasures, infoMeasures := groupMitigations(sctx)
|
|
if len(designMeasures)+len(protectiveMeasures)+len(infoMeasures) > 0 {
|
|
b.WriteString("## Risikomindernde Massnahmen (3-Stufen-Methode)\n\n")
|
|
writeMitigationGroup(&b, "Stufe 1: Inhaerent sichere Konstruktion (Design)", designMeasures)
|
|
writeMitigationGroup(&b, "Stufe 2: Technische Schutzmassnahmen (Protective)", protectiveMeasures)
|
|
writeMitigationGroup(&b, "Stufe 3: Benutzerinformation (Information)", infoMeasures)
|
|
}
|
|
}
|
|
|
|
// Classifications — for classification/standards sections
|
|
if len(sctx.Classifications) > 0 && needsClassifications(sectionType) {
|
|
b.WriteString("## Regulatorische Klassifizierungen\n\n")
|
|
for _, c := range sctx.Classifications {
|
|
b.WriteString(fmt.Sprintf("- **%s:** %s (Risiko: %s)\n",
|
|
string(c.Regulation), c.ClassificationResult, string(c.RiskLevel)))
|
|
if c.Reasoning != "" {
|
|
b.WriteString(fmt.Sprintf(" Begruendung: %s\n", truncateForPrompt(c.Reasoning, 200)))
|
|
}
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// Evidence — for evidence/verification sections
|
|
if len(sctx.Evidence) > 0 && needsEvidence(sectionType) {
|
|
b.WriteString("## Vorhandene Nachweise\n\n")
|
|
for i, e := range sctx.Evidence {
|
|
if i >= 30 {
|
|
b.WriteString(fmt.Sprintf("... und %d weitere Nachweise\n", len(sctx.Evidence)-30))
|
|
break
|
|
}
|
|
b.WriteString(fmt.Sprintf("- %s", e.FileName))
|
|
if e.Description != "" {
|
|
b.WriteString(fmt.Sprintf(" — %s", truncateForPrompt(e.Description, 100)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// RAG context — if available
|
|
if sctx.RAGContext != "" {
|
|
b.WriteString("## Relevante Rechtsgrundlagen (RAG)\n\n")
|
|
b.WriteString(sctx.RAGContext)
|
|
b.WriteString("\n\n")
|
|
}
|
|
|
|
// Instruction
|
|
b.WriteString("---\n\n")
|
|
b.WriteString("Erstelle den Abschnitt basierend auf den obigen Daten. Schreibe auf Deutsch, verwende Markdown-Formatierung und beziehe dich auf die konkreten Projektdaten.\n")
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// ============================================================================
|
|
// Section type → data requirements
|
|
// ============================================================================
|
|
|
|
func needsComponents(sectionType string) bool {
|
|
switch sectionType {
|
|
case SectionGeneralDescription, SectionDesignSpecifications, SectionComponentList,
|
|
SectionEssentialRequirements, SectionAIModelDescription, SectionAIIntendedPurpose,
|
|
SectionClassificationReport, SectionInstructionsForUse:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func needsHazards(sectionType string) bool {
|
|
switch sectionType {
|
|
case SectionRiskAssessmentReport, SectionHazardLogCombined, SectionEssentialRequirements,
|
|
SectionMitigationReport, SectionVerificationReport, SectionTestReports,
|
|
SectionAIRiskManagement, SectionInstructionsForUse, SectionMonitoringPlan:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func needsMitigations(sectionType string) bool {
|
|
switch sectionType {
|
|
case SectionRiskAssessmentReport, SectionMitigationReport, SectionVerificationReport,
|
|
SectionTestReports, SectionEssentialRequirements, SectionAIRiskManagement,
|
|
SectionAIHumanOversight, SectionInstructionsForUse:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func needsClassifications(sectionType string) bool {
|
|
switch sectionType {
|
|
case SectionClassificationReport, SectionEssentialRequirements, SectionStandardsApplied,
|
|
SectionDeclarationConformity, SectionAIIntendedPurpose, SectionAIRiskManagement,
|
|
SectionGeneralDescription:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func needsEvidence(sectionType string) bool {
|
|
switch sectionType {
|
|
case SectionEvidenceIndex, SectionVerificationReport, SectionTestReports,
|
|
SectionMitigationReport:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mitigation grouping helper
|
|
// ============================================================================
|
|
|
|
func groupMitigations(sctx *SectionGenerationContext) (design, protective, info []Mitigation) {
|
|
for _, mits := range sctx.Mitigations {
|
|
for _, m := range mits {
|
|
switch m.ReductionType {
|
|
case ReductionTypeDesign:
|
|
design = append(design, m)
|
|
case ReductionTypeProtective:
|
|
protective = append(protective, m)
|
|
case ReductionTypeInformation:
|
|
info = append(info, m)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func writeMitigationGroup(b *strings.Builder, title string, measures []Mitigation) {
|
|
if len(measures) == 0 {
|
|
return
|
|
}
|
|
b.WriteString(fmt.Sprintf("### %s\n\n", title))
|
|
for i, m := range measures {
|
|
if i >= 20 {
|
|
b.WriteString(fmt.Sprintf("... und %d weitere Massnahmen\n", len(measures)-20))
|
|
break
|
|
}
|
|
b.WriteString(fmt.Sprintf("- **%s** [%s]", m.Name, string(m.Status)))
|
|
if m.VerificationMethod != "" {
|
|
b.WriteString(fmt.Sprintf(" — Verifikation: %s", string(m.VerificationMethod)))
|
|
if m.VerificationResult != "" {
|
|
b.WriteString(fmt.Sprintf(" (%s)", m.VerificationResult))
|
|
}
|
|
}
|
|
b.WriteString("\n")
|
|
if m.Description != "" {
|
|
b.WriteString(fmt.Sprintf(" %s\n", truncateForPrompt(m.Description, 150)))
|
|
}
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// ============================================================================
|
|
// Fallback content (when LLM is unavailable)
|
|
// ============================================================================
|
|
|
|
func buildFallbackContent(sctx *SectionGenerationContext, sectionType string) string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString("[Automatisch generiert — LLM nicht verfuegbar]\n\n")
|
|
|
|
sectionTitle := sectionDisplayName(sectionType)
|
|
b.WriteString(fmt.Sprintf("# %s\n\n", sectionTitle))
|
|
|
|
b.WriteString(fmt.Sprintf("**Maschine:** %s (%s)\n", sctx.Project.MachineName, sctx.Project.MachineType))
|
|
b.WriteString(fmt.Sprintf("**Hersteller:** %s\n", sctx.Project.Manufacturer))
|
|
if sctx.Project.Description != "" {
|
|
b.WriteString(fmt.Sprintf("**Beschreibung:** %s\n", sctx.Project.Description))
|
|
}
|
|
b.WriteString("\n")
|
|
|
|
// Section-specific data summaries
|
|
switch sectionType {
|
|
case SectionComponentList, SectionGeneralDescription, SectionDesignSpecifications:
|
|
if len(sctx.Components) > 0 {
|
|
b.WriteString("## Komponenten\n\n")
|
|
b.WriteString(fmt.Sprintf("Anzahl: %d\n\n", len(sctx.Components)))
|
|
for _, c := range sctx.Components {
|
|
safety := ""
|
|
if c.IsSafetyRelevant {
|
|
safety = " [SICHERHEITSRELEVANT]"
|
|
}
|
|
b.WriteString(fmt.Sprintf("- %s (Typ: %s)%s\n", c.Name, string(c.ComponentType), safety))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
case SectionRiskAssessmentReport, SectionHazardLogCombined:
|
|
b.WriteString("## Risikoueberblick\n\n")
|
|
b.WriteString(fmt.Sprintf("Anzahl Gefaehrdungen: %d\n\n", len(sctx.Hazards)))
|
|
riskCounts := countRiskLevels(sctx)
|
|
for level, count := range riskCounts {
|
|
b.WriteString(fmt.Sprintf("- %s: %d\n", level, count))
|
|
}
|
|
b.WriteString("\n")
|
|
for _, h := range sctx.Hazards {
|
|
b.WriteString(fmt.Sprintf("- **%s** (%s) — Status: %s\n", h.Name, h.Category, string(h.Status)))
|
|
}
|
|
b.WriteString("\n")
|
|
|
|
case SectionMitigationReport:
|
|
design, protective, info := groupMitigations(sctx)
|
|
total := len(design) + len(protective) + len(info)
|
|
b.WriteString("## Massnahmenueberblick\n\n")
|
|
b.WriteString(fmt.Sprintf("Gesamt: %d Massnahmen\n", total))
|
|
b.WriteString(fmt.Sprintf("- Design: %d\n- Schutzmassnahmen: %d\n- Benutzerinformation: %d\n\n", len(design), len(protective), len(info)))
|
|
writeFallbackMitigationList(&b, "Design", design)
|
|
writeFallbackMitigationList(&b, "Schutzmassnahmen", protective)
|
|
writeFallbackMitigationList(&b, "Benutzerinformation", info)
|
|
|
|
case SectionClassificationReport:
|
|
if len(sctx.Classifications) > 0 {
|
|
b.WriteString("## Klassifizierungen\n\n")
|
|
for _, c := range sctx.Classifications {
|
|
b.WriteString(fmt.Sprintf("- **%s:** %s (Risiko: %s)\n",
|
|
string(c.Regulation), c.ClassificationResult, string(c.RiskLevel)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
case SectionEvidenceIndex:
|
|
b.WriteString("## Nachweisverzeichnis\n\n")
|
|
b.WriteString(fmt.Sprintf("Anzahl Nachweise: %d\n\n", len(sctx.Evidence)))
|
|
for _, e := range sctx.Evidence {
|
|
desc := e.Description
|
|
if desc == "" {
|
|
desc = "(keine Beschreibung)"
|
|
}
|
|
b.WriteString(fmt.Sprintf("- %s — %s\n", e.FileName, desc))
|
|
}
|
|
b.WriteString("\n")
|
|
|
|
default:
|
|
// Generic fallback data summary
|
|
b.WriteString(fmt.Sprintf("- Komponenten: %d\n", len(sctx.Components)))
|
|
b.WriteString(fmt.Sprintf("- Gefaehrdungen: %d\n", len(sctx.Hazards)))
|
|
b.WriteString(fmt.Sprintf("- Klassifizierungen: %d\n", len(sctx.Classifications)))
|
|
b.WriteString(fmt.Sprintf("- Nachweise: %d\n", len(sctx.Evidence)))
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
b.WriteString("---\n")
|
|
b.WriteString("*Dieser Abschnitt wurde ohne LLM-Unterstuetzung erstellt und enthaelt nur eine Datenuebersicht. Bitte erneut generieren, wenn der LLM-Service verfuegbar ist.*\n")
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func writeFallbackMitigationList(b *strings.Builder, title string, measures []Mitigation) {
|
|
if len(measures) == 0 {
|
|
return
|
|
}
|
|
b.WriteString(fmt.Sprintf("### %s\n\n", title))
|
|
for _, m := range measures {
|
|
b.WriteString(fmt.Sprintf("- %s [%s]\n", m.Name, string(m.Status)))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// ============================================================================
|
|
// Utility helpers
|
|
// ============================================================================
|
|
|
|
func countRiskLevels(sctx *SectionGenerationContext) map[string]int {
|
|
counts := make(map[string]int)
|
|
for _, h := range sctx.Hazards {
|
|
if assessments, ok := sctx.Assessments[h.ID]; ok && len(assessments) > 0 {
|
|
latest := assessments[len(assessments)-1]
|
|
counts[string(latest.RiskLevel)]++
|
|
}
|
|
}
|
|
return counts
|
|
}
|
|
|
|
func acceptableLabel(isAcceptable bool) string {
|
|
if isAcceptable {
|
|
return "[AKZEPTABEL]"
|
|
}
|
|
return "[NICHT AKZEPTABEL]"
|
|
}
|
|
|
|
func sectionDisplayName(sectionType string) string {
|
|
names := map[string]string{
|
|
SectionRiskAssessmentReport: "Zusammenfassung der Risikobeurteilung",
|
|
SectionHazardLogCombined: "Gefaehrdungsprotokoll (Hazard Log)",
|
|
SectionGeneralDescription: "Allgemeine Maschinenbeschreibung",
|
|
SectionEssentialRequirements: "Grundlegende Anforderungen (EHSR)",
|
|
SectionDesignSpecifications: "Konstruktionsdaten und Spezifikationen",
|
|
SectionTestReports: "Pruefberichte",
|
|
SectionStandardsApplied: "Angewandte Normen",
|
|
SectionDeclarationConformity: "EU-Konformitaetserklaerung",
|
|
SectionAIIntendedPurpose: "Bestimmungsgemaesser Zweck (KI)",
|
|
SectionAIModelDescription: "KI-Modellbeschreibung",
|
|
SectionAIRiskManagement: "KI-Risikomanagementsystem",
|
|
SectionAIHumanOversight: "Menschliche Aufsicht (Human Oversight)",
|
|
SectionComponentList: "Komponentenliste",
|
|
SectionClassificationReport: "Klassifizierungsbericht",
|
|
SectionMitigationReport: "Massnahmenbericht",
|
|
SectionVerificationReport: "Verifikationsbericht",
|
|
SectionEvidenceIndex: "Nachweisverzeichnis",
|
|
SectionInstructionsForUse: "Betriebsanleitung (Gliederung)",
|
|
SectionMonitoringPlan: "Post-Market-Monitoring-Plan",
|
|
}
|
|
if name, ok := names[sectionType]; ok {
|
|
return name
|
|
}
|
|
return sectionType
|
|
}
|
|
|
|
func truncateForPrompt(text string, maxLen int) string {
|
|
if len(text) <= maxLen {
|
|
return text
|
|
}
|
|
return text[:maxLen] + "..."
|
|
}
|