Your customer-service chatbot has a fine schedule now. Two regimes turned it into a regulated surface this month, and neither is hypothetical. Here's the engineering version: four checkable booleans, a cadence matrix, and an ~80-line Python scorer that returns a 0–100 compliance score, per-requirement pass/fail, the regimes that apply to your footprint, and a fix-first gap report ordered by penalty exposure.

1. The fine schedule (all figures per CX Network's Oct 11–12, 2026 coverage)

CA AB 1609 (signed Sept 28, 2026) — $5,000 for the first violation, $10,000 for each subsequent one. It binds large private businesses: $500M+ in annual revenue, serving CA consumers. Three tripwires:

  • In-the-moment AI disclosure — "clear and conspicuous" disclosure wherever a reasonable person could be misled, and you may not represent the bot as human. A human-name avatar with no AI mention is the violation.
  • A 15-minute human handoff — a human agent within 15 minutes, OR an appointment within one business day. A handoff button that only works 9–5 while the bot runs 24/7 fails it.
  • A conspicuous service phone number on the site.

EU AI Act Article 50 (in effect since August 2026) — up to €15M or 3% of global turnover. No revenue threshold. Every person must know they're talking to AI — in the moment AND in your policy. Per the guidance: "Generic declarations of AI use will not suffice."

2. Four checkable booleans + the cadence split

Every bot setup reduces to four booleans — in_moment_disclosure, policy_disclosure, human_handoff_sla, conspicuous_phone — plus a cadence question for the patchwork: 12 US states now regulate chatbot disclosure (CA, CO, CT, GA, HI, ID, IA, NE, NY, OR, RI, WA as of Oct 2026) — but only CA and WA mandate frequent reminders; the rest accept a one-time disclosure.

So your disclosure logic has to branch: frequent reminders for CA/WA traffic, one-time elsewhere, in-the-moment + policy disclosure for EU, and a 15-minute handoff clock if you're $500M+ and serving California.

3. The scorer: applicability, the 49-cap honesty rule, fix-first ordering

Three design decisions in the code below:

  1. Regime applicability — AB 1609 applies when annual revenue ≥ $500M and you serve CA consumers. EU Article 50 applies whenever you serve EU users. State rules apply per state in your footprint.
  2. The 49-cap honesty rule — missing in-moment disclosure while serving EU users hard-caps the score at 49, because Article 50 makes it non-negotiable. A scorer that hands you an 80 while you're breaking the steepest rule is lying to you.
  3. Fix-first ordering by penalty exposure — gaps sorted EU Art 50 first (up to €15M / 3%), then AB 1609 ($5k/$10k per violation), then state-cadence gaps.

4. The scorer in ~80 lines

from dataclasses import dataclass, field # Penalty exposure (all figures per CX Network, Oct 11-12, 2026): # EU AI Act Art 50: up to EUR 15M or 3% of global turnover (in effect since Aug 2026) # CA AB 1609: $5,000 first violation, $10,000 each subsequent (signed Sept 28, 2026) STATES = ["CA", "CO", "CT", "GA", "HI", "ID", "IA", "NE", "NY", "OR", "RI", "WA"] FREQUENT_REMINDER_STATES = {"CA", "WA"} # only CA and WA mandate frequent reminders  @dataclass class BotSetup: revenue_m: float = 0.0 # annual revenue, $ millions  serves_ca: bool = False serves_eu: bool = False states: list = field(default_factory=list) in_moment_disclosure: bool = False # clear and conspicuous AI disclosure  policy_disclosure: bool = False # policy states AI is used  human_handoff_sla: bool = False # human in 15 min OR appt in 1 business day  conspicuous_phone: bool = False # conspicuous service phone number on site  frequent_reminders: bool = False # reminders during the conversation  def applicable_regimes(s: BotSetup) -> list: regimes = [] if s.revenue_m >= 500 and s.serves_ca: regimes.append("AB 1609 (CA)") if s.serves_eu: regimes.append("EU AI Act Art 50") for st in s.states: if st in STATES: regimes.append(f"State disclosure: {st}") return regimes # (requirement, scope, penalty_tier) — tier 1 = fix first REQUIREMENTS = [ ("in_moment_disclosure", "eu", 1), # Art 50: non-negotiable  ("policy_disclosure", "eu", 1), ("in_moment_disclosure", "ab1609", 2), # AB 1609 tripwire  ("human_handoff_sla", "ab1609", 2), ("conspicuous_phone", "ab1609", 2), ("frequent_reminders", "state-frequent", 3), ("in_moment_disclosure", "state", 3), ] def score(s: BotSetup) -> dict: eu = s.serves_eu ab = s.revenue_m >= 500 and s.serves_ca frequent = [st for st in s.states if st in FREQUENT_REMINDER_STATES] other = [st for st in s.states if st in STATES and st not in FREQUENT_REMINDER_STATES] results, gaps, earned, total = [], [], 0, 0 for attr, scope, tier in REQUIREMENTS: applies = ((scope == "eu" and eu) or (scope == "ab1609" and ab) or (scope == "state-frequent" and frequent) or (scope == "state" and other)) if not applies: continue passed = getattr(s, attr) total += 1 earned += passed results.append({"requirement": attr, "scope": scope, "passed": passed}) if not passed: gaps.append({"requirement": attr, "scope": scope, "penalty_tier": tier}) pct = round(100 * earned / total) if total else 100 # 49-cap honesty rule: no in-moment disclosure for EU users caps at 49  if eu and not s.in_moment_disclosure: pct = min(pct, 49) gaps.sort(key=lambda g: g["penalty_tier"]) return {"score": pct, "per_requirement": results, "applicable_regimes": applicable_regimes(s), "fix_first_gaps": gaps} if __name__ == "__main__": import json setup = BotSetup(revenue_m=600, serves_ca=True, serves_eu=True, states=["CA", "NY"], in_moment_disclosure=False, policy_disclosure=True, human_handoff_sla=True, conspicuous_phone=False, frequent_reminders=False) print(json.dumps(score(setup), indent=2)) 

Sample response for the setup above:

{ "score": 49, "per_requirement": [ {"requirement": "in_moment_disclosure", "scope": "eu", "passed": false}, {"requirement": "policy_disclosure", "scope": "eu", "passed": true}, {"requirement": "in_moment_disclosure", "scope": "ab1609", "passed": false}, {"requirement": "human_handoff_sla", "scope": "ab1609", "passed": true}, {"requirement": "conspicuous_phone", "scope": "ab1609", "passed": false}, {"requirement": "frequent_reminders", "scope": "state-frequent", "passed": false}, {"requirement": "in_moment_disclosure", "scope": "state", "passed": false} ], "applicable_regimes": ["AB 1609 (CA)", "EU AI Act Art 50", "State disclosure: CA", "State disclosure: NY"], "fix_first_gaps": [ {"requirement": "in_moment_disclosure", "scope": "eu", "penalty_tier": 1}, {"requirement": "in_moment_disclosure", "scope": "ab1609", "penalty_tier": 2}, {"requirement": "conspicuous_phone", "scope": "ab1609", "penalty_tier": 2}, {"requirement": "frequent_reminders", "scope": "state-frequent", "penalty_tier": 3}, {"requirement": "in_moment_disclosure", "scope": "state", "penalty_tier": 3} ] } 

Three of seven pass — which would be 43% — but the missing EU in-moment disclosure caps it at 49 anyway. That's the honesty rule doing its job.

5. Disclaimer

⚠️ DiscloseKit is an audit aid, not legal advice. All regime summaries trace to CX Network's Oct 11–12, 2026 coverage; verify against the statutory text and your counsel before relying on any requirement or penalty figure.

6. The full kit

I packaged this as DiscloseKit — the full audit kit with six checklists (AB 1609, EU Article 50, state matrix, disclosure-UX patterns, handoff-SLA worksheet) and a runnable FastAPI checker: https://vittoriali.gumroad.com/l/disclosekit

$39 one-time, by Haku.