feat: BetrVG-Compliance-Modul — Obligations, Konflikt-Score, Frontend

1. BetrVG Obligations (JSON V2): 12 Pflichten basierend auf §87, §90, §94, §95, §99, §111
   - BAG-Rechtsprechung referenziert (M365, SAP, Standardsoftware)
   - Applicability: DE + >=5 Mitarbeiter
2. Betriebsrats-Konflikt-Score (0-100): Gewichtete Formel aus 8 Faktoren
   - Ueberwachungseignung, HR-Bezug, Individualisierbarkeit, Automation
   - Escalation-Trigger: Score>=50 ohne BR → E2, Score>=75 → E3
3. Frontend: 3 neue Intake-Felder (Monitoring, HR, BR-Konsultation)
   - BR-Konflikt-Badge in Use-Case-Liste + Detail-Seite
   - Farbcodierung: gruen/gelb/orange/rot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-12 10:49:56 +02:00
parent bc75b4455d
commit c55a6ab995
10 changed files with 437 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
package ucca
import (
"fmt"
"time"
"github.com/google/uuid"
@@ -187,6 +188,12 @@ func (t *EscalationTrigger) DetermineEscalationLevel(result *AssessmentResult) (
}
}
// BetrVG E3: Very high conflict score without consultation
if result.BetrvgConflictScore >= 75 && !result.Intake.WorksCouncilConsulted {
reasons = append(reasons, "BetrVG-Konfliktpotenzial sehr hoch (Score "+fmt.Sprintf("%d", result.BetrvgConflictScore)+") ohne BR-Konsultation")
return EscalationLevelE3, joinReasons(reasons, "E3 erforderlich: ")
}
if hasArt9 || result.DSFARecommended || result.RiskScore > t.E2RiskThreshold {
if result.DSFARecommended {
reasons = append(reasons, "DSFA empfohlen")
@@ -197,6 +204,12 @@ func (t *EscalationTrigger) DetermineEscalationLevel(result *AssessmentResult) (
return EscalationLevelE2, joinReasons(reasons, "DSB-Konsultation erforderlich: ")
}
// BetrVG E2: High conflict score
if result.BetrvgConflictScore >= 50 && result.BetrvgConsultationRequired && !result.Intake.WorksCouncilConsulted {
reasons = append(reasons, "BetrVG-Mitbestimmung erforderlich (Score "+fmt.Sprintf("%d", result.BetrvgConflictScore)+"), BR nicht konsultiert")
return EscalationLevelE2, joinReasons(reasons, "BR-Konsultation erforderlich: ")
}
// E1: Low priority checks
// - WARN rules triggered
// - Risk 20-40

View File

@@ -56,6 +56,8 @@ func (m *JSONRegulationModule) defaultApplicability(facts *UnifiedFacts) bool {
return facts.Organization.EUMember && facts.AIUsage.UsesAI
case "dora":
return facts.Financial.DORAApplies || facts.Financial.IsRegulated
case "betrvg":
return facts.Organization.Country == "DE" && facts.Organization.EmployeeCount >= 5
default:
return true
}

View File

@@ -217,6 +217,11 @@ type UseCaseIntake struct {
// Only applicable for financial domains (banking, finance, insurance, investment)
FinancialContext *FinancialContext `json:"financial_context,omitempty"`
// BetrVG / works council context (Germany)
EmployeeMonitoring bool `json:"employee_monitoring,omitempty"` // System can monitor employee behavior/performance
HRDecisionSupport bool `json:"hr_decision_support,omitempty"` // System supports HR decisions (hiring, evaluation, termination)
WorksCouncilConsulted bool `json:"works_council_consulted,omitempty"` // Works council has been consulted
// Opt-in to store raw text (otherwise only hash)
StoreRawText bool `json:"store_raw_text,omitempty"`
}
@@ -471,6 +476,10 @@ type Assessment struct {
Art22Risk bool `json:"art22_risk"`
TrainingAllowed TrainingAllowed `json:"training_allowed"`
// BetrVG Conflict Score (0-100) — works council escalation risk
BetrvgConflictScore int `json:"betrvg_conflict_score"`
BetrvgConsultationRequired bool `json:"betrvg_consultation_required"`
// Corpus Versioning (RAG)
CorpusVersionID *uuid.UUID `json:"corpus_version_id,omitempty"`
CorpusVersion string `json:"corpus_version,omitempty"`

View File

@@ -338,6 +338,9 @@ func (e *PolicyEngine) Evaluate(intake *UseCaseIntake) *AssessmentResult {
// Determine complexity
result.Complexity = e.calculateComplexity(result)
// Calculate BetrVG Conflict Score (Germany only, employees >= 5)
result.BetrvgConflictScore, result.BetrvgConsultationRequired = e.calculateBetrvgConflictScore(intake)
// Check if DSFA is recommended
result.DSFARecommended = e.shouldRecommendDSFA(intake, result)
@@ -880,3 +883,70 @@ func categorizeControl(id string) string {
}
return "organizational"
}
// calculateBetrvgConflictScore computes a works council conflict score (0-100).
// Higher score = higher risk of escalation with works council.
// Only relevant for German organizations with >= 5 employees.
func (e *PolicyEngine) calculateBetrvgConflictScore(intake *UseCaseIntake) (int, bool) {
if intake.Domain == "" {
return 0, false
}
score := 0
consultationRequired := false
// Factor 1: Employee data processing (+10)
if intake.DataTypes.PersonalData && intake.DataTypes.EmployeeData {
score += 10
consultationRequired = true
}
// Factor 2: System can monitor behavior/performance (+20)
if intake.EmployeeMonitoring {
score += 20
consultationRequired = true
}
// Factor 3: Individualized usage data / logging (+15)
if intake.Retention.StorePrompts || intake.Retention.StoreResponses {
score += 15
}
// Factor 4: Communication analysis (+10)
if intake.Purpose.CustomerSupport || intake.Purpose.Marketing {
// These purposes on employee data suggest communication analysis
if intake.DataTypes.EmployeeData {
score += 10
}
}
// Factor 5: HR / Recruiting context (+20)
if intake.HRDecisionSupport {
score += 20
consultationRequired = true
}
// Factor 6: Scoring / Ranking of employees (+10)
if intake.Outputs.Rankings || intake.Outputs.Recommendations {
if intake.DataTypes.EmployeeData {
score += 10
}
}
// Factor 7: Fully automated decisions (+10)
if intake.Automation == "fully_automated" {
score += 10
}
// Factor 8: Works council NOT consulted (+5)
if consultationRequired && !intake.WorksCouncilConsulted {
score += 5
}
// Cap at 100
if score > 100 {
score = 100
}
return score, consultationRequired
}