Spaces:
Running
Running
| """ | |
| 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, | |
| ) | |