reguai-engine / src /reasoning /fine_calculator.py
gitmodelmujtaba's picture
Deploy ReguAI: Neuro-Symbolic AI GRC & Automated Conformity Assessment Engine
99102a1 verified
Raw
History Blame Contribute Delete
5.66 kB
"""
Article 99 Statutory Fine Liability & SME Exposure Calculator.
Calculates maximum corporate balance sheet exposure under Regulation (EU) 2024/1689:
- Tier 1 (Article 5 Prohibitions): Up to 35M€ or 7% global turnover
- Tier 2 (Chapter III High-Risk Obligations): Up to 15M€ or 3% global turnover
- Tier 3 (Misinformation / Notified Body Requests): Up to 7.5M€ or 1.5% global turnover
- Article 99(6) Special Cap: Fines on SMEs/startups use min(fixed, percentage)
"""
import json
from pathlib import Path
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
from src.core.config import BENCHMARKS_DIR
from src.core.models import ValidationViolation
class FineExposureEstimate(BaseModel):
is_non_compliant: bool
highest_tier_triggered: str
statutory_legal_basis: str
maximum_fine_eur: float
turnover_percentage: float
applicable_ceiling_eur: float
is_sme_discount_applied: bool
assumed_annual_turnover_eur: float
affected_articles: List[str] = Field(default_factory=list)
executive_liability_summary: str
class FineLiabilityCalculator:
def __init__(self, guidelines_path: Optional[Path] = None):
self.guidelines_file = guidelines_path or (BENCHMARKS_DIR / "rules" / "ai_act_fine_guidelines.json")
self.guidelines: Dict[str, Any] = {}
self._load_guidelines()
def _load_guidelines(self) -> None:
if self.guidelines_file.exists():
with open(self.guidelines_file, "r", encoding="utf-8") as f:
self.guidelines = json.load(f)
else:
# Standard statutory default fallbacks under Article 99
self.guidelines = {
"tiers": [
{"tier": "TIER_1_PROHIBITED_AI", "maximum_fine_eur": 35000000, "maximum_turnover_pct": 7.0, "legal_basis": "Article 99(3)"},
{"tier": "TIER_2_HIGH_RISK_OBLIGATIONS", "maximum_fine_eur": 15000000, "maximum_turnover_pct": 3.0, "legal_basis": "Article 99(4)"},
{"tier": "TIER_3_MISINFORMATION_NOTIFICATION", "maximum_fine_eur": 7500000, "maximum_turnover_pct": 1.5, "legal_basis": "Article 99(5)"},
]
}
def calculate_exposure(
self,
violations: List[ValidationViolation],
annual_turnover_eur: float = 0.0,
is_sme: bool = False,
) -> FineExposureEstimate:
"""
Calculates maximum fine liability exposure according to Article 99 rules.
"""
if not violations:
return FineExposureEstimate(
is_non_compliant=False,
highest_tier_triggered="NONE",
statutory_legal_basis="Article 99 (Full Compliance)",
maximum_fine_eur=0.0,
turnover_percentage=0.0,
applicable_ceiling_eur=0.0,
is_sme_discount_applied=is_sme,
assumed_annual_turnover_eur=annual_turnover_eur,
affected_articles=[],
executive_liability_summary="Zero statutory fine liability. System satisfies audited regulatory constraints.",
)
affected_articles = sorted(set(v.regulatory_article for v in violations))
has_prohibited = any("5" in art for art in affected_articles)
has_high_risk = any(any(hr in art for hr in ["9", "10", "11", "12", "13", "14", "15", "16", "26", "27"]) for art in affected_articles)
if has_prohibited:
tier_name = "TIER_1_PROHIBITED_AI"
fixed_max = 35_000_000.0
pct_max = 7.0
legal_basis = "Article 99(3) [Infringement of Prohibited AI Practices in Article 5]"
elif has_high_risk:
tier_name = "TIER_2_HIGH_RISK_OBLIGATIONS"
fixed_max = 15_000_000.0
pct_max = 3.0
legal_basis = "Article 99(4) [Non-compliance with Chapter III High-Risk Requirements]"
else:
tier_name = "TIER_3_MISINFORMATION_NOTIFICATION"
fixed_max = 7_500_000.0
pct_max = 1.5
legal_basis = "Article 99(5) [General Infringement]"
turnover_based_fine = (annual_turnover_eur * (pct_max / 100.0)) if annual_turnover_eur > 0 else 0.0
if is_sme:
# Article 99(6): min(fixed_max, percentage_based_fine)
if annual_turnover_eur > 0:
applicable_ceiling = min(fixed_max, turnover_based_fine)
else:
applicable_ceiling = fixed_max
summary = (
f"SME Status Active (Article 99(6)): Maximum statutory fine capped at "
f"€{applicable_ceiling:,.2f} under {legal_basis} (subject to the lower of €{fixed_max:,.0f} or {pct_max}% of turnover)."
)
else:
# Standard enterprise: whichever is higher
applicable_ceiling = max(fixed_max, turnover_based_fine)
summary = (
f"Enterprise Exposure: Maximum statutory fine of up to "
f"€{applicable_ceiling:,.2f} under {legal_basis} (higher of €{fixed_max:,.0f} or {pct_max}% worldwide annual turnover)."
)
return FineExposureEstimate(
is_non_compliant=True,
highest_tier_triggered=tier_name,
statutory_legal_basis=legal_basis,
maximum_fine_eur=fixed_max,
turnover_percentage=pct_max,
applicable_ceiling_eur=applicable_ceiling,
is_sme_discount_applied=is_sme,
assumed_annual_turnover_eur=annual_turnover_eur,
affected_articles=affected_articles,
executive_liability_summary=summary,
)