# ───────────────────────────────────────────── # RegMap — AI Regulation Compliance Platform # Copyright (c) 2026 RegMap. All rights reserved. # License: CC BY-NC 4.0 # https://creativecommons.org/licenses/by-nc/4.0/ # Commercial use prohibited without prior written consent. # ───────────────────────────────────────────── import streamlit as st import io from datetime import datetime from phase2_rkb import ( QUALIFICATION_QUESTIONS, OBLIGATIONS, OVERLAP_ANALYSIS, GAP_ANALYSIS, REGULATION_URLS, OTHER_REG_ONE_LINERS, DIFC_CONTROLLER_NOTE, DISCLAIMER, ) # ── EU AI Act full exemptions (single source of truth) ── EU_FULL_EXEMPTIONS = [ "The AI system is used exclusively for military, defence, or national security purposes", "The AI system is used solely for scientific research and development and has not yet been placed on the market or put into service", "The AI system is used for purely personal, non-professional purposes by a natural person", "The AI system is operated by a third-country public authority under international law enforcement or judicial cooperation agreements", ] # ── Startup integrity check ── # Validates RKB data consistency at app start. If issues found, logs warning. def _validate_rkb(): AI_REGS = [ "EU AI Act (Regulation 2024/1689)", "EU AI Act — GPAI Framework (Chapter V)", "Colorado AI Act (SB 24-205)", "Texas TRAIGA (HB 149)", "Utah AI Policy Act (SB 149)", "California CCPA / ADMT Regulations", "Illinois HB 3773 (AI in Employment)", "DIFC Regulation 10 (AI Processing)", ] GAP_REGS = [r for r in AI_REGS if "GPAI" not in r] issues = [] for r in AI_REGS: if r not in OBLIGATIONS: issues.append(f"AI reg not in OBLIGATIONS: {r}") if r not in QUALIFICATION_QUESTIONS: issues.append(f"AI reg not in QUAL_QUESTIONS: {r}") if r not in REGULATION_URLS: issues.append(f"AI reg not in URLS: {r}") for src in GAP_REGS: if src not in GAP_ANALYSIS: issues.append(f"GAP missing source: {src}") else: for tgt in GAP_REGS: if tgt != src and tgt not in GAP_ANALYSIS[src]: issues.append(f"GAP missing: {src} → {tgt}") all_known = set(OBLIGATIONS.keys()) | set(OTHER_REG_ONE_LINERS.keys()) for ov in OVERLAP_ANALYSIS: for r in ov["regulations"]: if r not in all_known: issues.append(f"OVERLAP refs unknown: {r}") return issues _rkb_issues = _validate_rkb() if _rkb_issues: import logging logging.warning(f"RegMap RKB integrity: {len(_rkb_issues)} issues found") for i in _rkb_issues: logging.warning(f" RKB: {i}") # ───────────────────────────────────────────── # REGMAP — AI System ID Card # ───────────────────────────────────────────── st.set_page_config( page_title="RegMap — AI System ID Card", page_icon="◈", layout="centered", ) # ── Visual Identity ── st.markdown(""" """, unsafe_allow_html=True) # ── Reference Data ── INDUSTRY_SECTORS = [ "Agriculture & Food", "Automotive & Transportation", "Banking & Financial Services", "Construction & Real Estate", "Consulting & Professional Services", "Defence & Security", "Education & Training", "Energy & Utilities", "Entertainment & Media", "Environmental Services", "Government & Public Administration", "Healthcare & Life Sciences", "Hospitality & Tourism", "Human Resources & Recruitment", "Insurance", "Legal Services", "Logistics & Supply Chain", "Manufacturing", "Mining & Natural Resources", "Non-Profit & NGO", "Pharmaceuticals", "Retail & E-commerce", "Telecommunications", "Technology & Software", "Other", ] US_STATES = [ "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming", "District of Columbia", ] # States with specific AI regulation (for downstream analysis) US_STATES_WITH_AI_REGULATION = [ "California", "Colorado", "Illinois", "Texas", "Utah", ] EU_COUNTRIES = [ "Austria", "Belgium", "Bulgaria", "Croatia", "Cyprus", "Czechia", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden", "Iceland (EEA)", "Liechtenstein (EEA)", "Norway (EEA)", ] UAE_EMIRATES = [ "Dubai", "Abu Dhabi", "Sharjah", "Ajman", "Fujairah", "Ras Al Khaimah", "Umm Al Quwain", ] UAE_FREE_ZONES = { "Dubai": ["DIFC", "DMCC", "JAFZA", "Dubai Internet City", "Dubai Media City", "Dubai Silicon Oasis", "Dubai Healthcare City", "Dubai Design District (d3)", "Dubai South", "Dubai Knowledge Park", "DAFZA", "Dubai World Trade Centre", "Dubai Science Park", "Dubai Textile City", "DUCAMZ", "Dubai Maritime City", "Meydan Free Zone", "IFZA"], "Abu Dhabi": ["ADGM", "KIZAD", "Masdar City", "ADAFZ", "Khalifa Port Free Trade Zone", "Twofour54"], "Sharjah": ["Sharjah Media City (Shams)", "SAIF Zone", "Sharjah Publishing City", "Hamriyah Free Zone"], "Ras Al Khaimah": ["RAKEZ", "RAK Maritime City", "RAK Media City"], "Ajman": ["Ajman Free Zone"], "Fujairah": ["Fujairah Free Zone", "Fujairah Creative City"], "Umm Al Quwain": ["UAQ Free Trade Zone"], } ROLES = [ "Provider — You develop or commission the AI system", "Deployer — You use an AI system in your operations (you did not build it)", "Authorised Representative (EU) — You act on behalf of a non-EU provider to fulfill EU AI Act obligations", "Importer (EU) — You place on the EU market an AI system from a non-EU provider", "Distributor (EU) — You make an AI system available on the EU market (neither provider nor importer)", ] AI_TYPES = ["Machine Learning (ML)", "Generative AI (GenAI)", "Agentic AI", "Rule-based", "Hybrid"] DATA_TYPE_OPTIONS = [ "Personal data (e.g. name, email, ID)", "Pseudonymised data (e.g. hashed identifiers, tokenised records)", "Sensitive/special category data (e.g. health, race, religion, political opinions, sexual orientation)", "Biometric data (e.g. fingerprints, facial recognition, voice)", "Children's data (<18)", "Synthetic data", "Copyrighted content (text, images, audio, video protected by intellectual property)", "No personal data", ] TRAINING_SOURCES = ["Public web", "Licensed datasets", "Proprietary data", "User-generated content"] INVOLVEMENT_LEVELS = ["Fully automated", "Human-assisted", "Human decides"] ALL_COUNTRIES = [ "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahrain", "Bangladesh", "Belarus", "Belgium", "Bolivia", "Bosnia and Herzegovina", "Brazil", "Brunei", "Bulgaria", "Cambodia", "Cameroon", "Canada", "Chile", "China", "Colombia", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia", "Denmark", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Estonia", "Ethiopia", "Finland", "France", "Georgia", "Germany", "Ghana", "Greece", "Guatemala", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kuwait", "Latvia", "Lebanon", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Malaysia", "Malta", "Mexico", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Nigeria", "North Korea", "North Macedonia", "Norway", "Oman", "Pakistan", "Panama", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saudi Arabia", "Senegal", "Serbia", "Singapore", "Slovakia", "Slovenia", "Somalia", "South Africa", "South Korea", "Spain", "Sri Lanka", "Sudan", "Sweden", "Switzerland", "Syria", "Taiwan", "Tanzania", "Thailand", "Tunisia", "Turkey", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe", ] # ── Session State ── TOTAL_SCREENS = 10 if "screen" not in st.session_state: st.session_state.screen = 1 if "data" not in st.session_state: st.session_state.data = {} if "detected_regs" not in st.session_state: st.session_state.detected_regs = [] if "qualification_answers" not in st.session_state: st.session_state.qualification_answers = {} def go_next(): if st.session_state.screen < TOTAL_SCREENS + 1: st.session_state.screen += 1 def go_back(): if st.session_state.screen > 1: st.session_state.screen -= 1 def go_to(n): st.session_state.screen = n def render_progress_tracker(current_screen): """Render a horizontal 4-step progress tracker with time estimates.""" steps = [ {"num": 1, "label": "ID Card", "sub": "~3 min", "screens": range(1, 8)}, {"num": 2, "label": "Regulatory Map", "sub": "~1 min", "screens": [8]}, {"num": 3, "label": "Deep Dive", "sub": "~3 min", "screens": [9, 10]}, {"num": 4, "label": "Checklist & Export", "sub": "~1 min", "screens": [11]}, ] # Determine active step active_step = 1 for s in steps: if current_screen in s["screens"]: active_step = s["num"] break # Fill percentage: 0%, 33%, 66%, 100% fill_pct = int((active_step - 1) / (len(steps) - 1) * 100) dots_html = "" for s in steps: if s["num"] < active_step: state = "done" elif s["num"] == active_step: state = "current" else: state = "future" dots_html += f'''
{s["num"]}
{s["label"]}
{s["sub"]}
''' tracker = f'''
{dots_html}
''' st.markdown(tracker, unsafe_allow_html=True) def generate_full_pdf(data, detected_regs, qualification_answers, all_obligations, applicable_overlaps, disclaimer_text, map_only=False, gap_items=None): """Generate a comprehensive PDF report covering ID Card, Regulatory Map, Requirements, Synergies, Gaps, and Checklist.""" import re from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.colors import HexColor from reportlab.lib.units import mm from reportlab.lib.enums import TA_CENTER, TA_RIGHT from reportlab.platypus import ( SimpleDocTemplate, Paragraph, Spacer, HRFlowable, PageBreak, KeepTogether, ) buf = io.BytesIO() W, H = A4 system_name = data.get("name", "AI System") def clean(text): """Strip emojis and special characters that reportlab can't render.""" t = str(text) t = re.sub(r'[^\u0000-\uFFFF]', '', t) t = re.sub(r'[\uFE0E\uFE0F\u2600-\u27BF\u2B50-\u2B55\u23E9-\u23FA\u200D]', '', t) return t.strip() teal = HexColor("#0D9488") dark = HexColor("#0F2B46") grey = HexColor("#64748B") light_grey = HexColor("#94A3B8") def header_footer(canvas, doc): canvas.saveState() canvas.setStrokeColor(teal) canvas.setLineWidth(0.5) canvas.line(20*mm, H - 14*mm, W - 20*mm, H - 14*mm) canvas.setFont("Helvetica-Bold", 7) canvas.setFillColor(teal) canvas.drawString(20*mm, H - 12.5*mm, "RegMap") canvas.setFont("Helvetica", 7) canvas.setFillColor(light_grey) canvas.drawRightString(W - 20*mm, H - 12.5*mm, f"{clean(system_name)} AI System") canvas.setStrokeColor(light_grey) canvas.setLineWidth(0.3) canvas.line(20*mm, 14*mm, W - 20*mm, 14*mm) canvas.setFont("Helvetica", 6) canvas.setFillColor(light_grey) canvas.drawString(20*mm, 10*mm, "RegMap by Ines Bedar — License: CC BY-NC 4.0") canvas.drawRightString(W - 20*mm, 10*mm, f"Page {doc.page}") canvas.restoreState() doc = SimpleDocTemplate( buf, pagesize=A4, leftMargin=20*mm, rightMargin=20*mm, topMargin=22*mm, bottomMargin=22*mm, ) styles = getSampleStyleSheet() s_title = ParagraphStyle("rm_title", parent=styles["Title"], fontSize=20, textColor=dark, spaceAfter=2, leading=24) s_subtitle = ParagraphStyle("rm_sub", parent=styles["Normal"], fontSize=10, textColor=grey, spaceAfter=4, leading=14) s_meta = ParagraphStyle("rm_meta", parent=styles["Normal"], fontSize=8, textColor=light_grey, spaceAfter=2) s_h1 = ParagraphStyle("rm_h1", parent=styles["Heading1"], fontSize=14, textColor=dark, spaceBefore=16, spaceAfter=6, leading=18) s_h2 = ParagraphStyle("rm_h2", parent=styles["Heading2"], fontSize=11, textColor=teal, spaceBefore=10, spaceAfter=4, leading=14) s_body = ParagraphStyle("rm_body", parent=styles["Normal"], fontSize=9, textColor=dark, spaceAfter=2, leading=12.5) s_item = ParagraphStyle("rm_item", parent=styles["Normal"], fontSize=9, textColor=dark, leftIndent=6, spaceAfter=2.5, leading=12.5) s_source = ParagraphStyle("rm_src", parent=styles["Normal"], fontSize=7, textColor=grey, leftIndent=6, spaceAfter=4, leading=10) s_overlap_title = ParagraphStyle("rm_ovt", parent=styles["Normal"], fontSize=9, textColor=dark, leftIndent=6, spaceAfter=1, leading=12.5) s_overlap_body = ParagraphStyle("rm_ovb", parent=styles["Normal"], fontSize=8, textColor=grey, leftIndent=6, spaceAfter=6, leading=11) s_disclaimer = ParagraphStyle("rm_disc", parent=styles["Normal"], fontSize=7, textColor=light_grey, spaceBefore=12, leading=10) s_center = ParagraphStyle("rm_center", parent=styles["Normal"], fontSize=9, textColor=grey, alignment=TA_CENTER, spaceAfter=8) story = [] # ════════════════════════════════════════ # COVER # ════════════════════════════════════════ story.append(Spacer(1, 10)) story.append(Paragraph("RegMap", s_title)) story.append(Paragraph("AI Regulatory Compliance Report", s_subtitle)) story.append(HRFlowable(width="100%", thickness=1.5, color=teal, spaceAfter=8)) gen_date = datetime.now().strftime("%d %B %Y") story.append(Paragraph(f"System: {clean(system_name)} AI System", s_meta)) story.append(Paragraph(f"Generated: {gen_date}", s_meta)) story.append(Paragraph(f"Author: Ines Bedar", s_meta)) story.append(Spacer(1, 6)) if gap_items is None: gap_items = [] total_obl = sum(len(o["obligations"]) for o in all_obligations) if isinstance(all_obligations, list) else 0 # ════════════════════════════════════════ # 1. AI SYSTEM ID CARD # ════════════════════════════════════════ story.append(Paragraph("1. AI System ID Card", s_h1)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) # Build smart Markets string: US (states), UAE (free zones) countries = data.get("operating_countries", []) us_states = data.get("us_states", []) uae_fz = data.get("uae_free_zones", []) market_parts = [] for c in countries: if c == "United States" and us_states: market_parts.append(f"United States ({', '.join(us_states)})") elif c == "United Arab Emirates" and uae_fz: market_parts.append(f"United Arab Emirates ({', '.join(uae_fz)})") else: market_parts.append(c) markets_str = ", ".join(market_parts) if market_parts else "—" fields = [ ("System Name", f"{data.get('name', '—')} AI System"), ("Description", data.get("description", "—")), ("Lifecycle Stage", data.get("lifecycle", "—")), ("Sector(s)", ", ".join(data.get("sector", ["—"]))), ("Organisation Type", data.get("org_type", "—")), ("Public Services", ", ".join(ps) if (ps := data.get("provides_public_services", [])) and "None of the above" not in ps else "None"), ("Organisation Size", data.get("company_size", "—")), ("EU SME Status", "Yes" if data.get("is_sme", False) else "No"), ("Headquarters", data.get("company_base", "—")), ("Markets", markets_str), ("Role(s)", ", ".join(data.get("roles", ["—"]))), ("AI Capabilities", ", ".join(data.get("capabilities", ["—"]))), ("Data Types", ", ".join(data.get("data_types", ["—"]))), ] for label, value in fields: story.append(Paragraph(f"{label}: {clean(value)}", s_body)) # ════════════════════════════════════════ # 2. REGULATORY MAP # ════════════════════════════════════════ story.append(PageBreak()) story.append(Paragraph("2. Regulatory Map", s_h1)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph("Regulations detected based on geographic scope, sector, data types, and AI capabilities.", s_center)) # Merge GPAI into EU AI Act for display gpai_name = "EU AI Act — GPAI Framework (Chapter V)" has_gpai_pdf = any(n == gpai_name for _, n, _, _ in detected_regs) pdf_display_regs = [] for tag, name, cat, reason in detected_regs: if name == gpai_name: continue if name == "EU AI Act (Regulation 2024/1689)" and has_gpai_pdf: pdf_display_regs.append((tag, name, cat, reason + " — incl. GPAI provisions (Chapter V)")) else: pdf_display_regs.append((tag, name, cat, reason)) ai_regs = [(t, n, r) for t, n, c, r in pdf_display_regs if c == "ai"] other_regs = [(t, n, r) for t, n, c, r in pdf_display_regs if c == "other"] if ai_regs: story.append(Paragraph("AI-Specific Regulations", s_h2)) for tag, name, reason in ai_regs: url = REGULATION_URLS.get(name, "") link = f' [Official text]' if url else "" story.append(Paragraph(f"[{tag}] {clean(name)}{link}", s_item)) story.append(Paragraph(f"{clean(reason)}", s_source)) if other_regs: story.append(Paragraph("Other Applicable Regulations", s_h2)) for tag, name, reason in other_regs: url = REGULATION_URLS.get(name, "") link = f' [Official text]' if url else "" story.append(Paragraph(f"[{tag}] {clean(name)}{link}", s_item)) story.append(Paragraph(f"{clean(reason)}", s_source)) # ════════════════════════════════════════ # 3. REQUIREMENTS PER REGULATION (filtered by qualification) # ════════════════════════════════════════ if map_only: # Skip to disclaimer — map_only PDF only includes ID Card + Reg Map story.append(Spacer(1, 16)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph(f"Disclaimer: {clean(disclaimer_text)}", s_disclaimer)) story.append(Spacer(1, 4)) story.append(Paragraph( "RegMap by Ines Bedar. Licensed under Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0). " "Commercial use prohibited without prior written consent. https://creativecommons.org/licenses/by-nc/4.0/", s_disclaimer, )) doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer) buf.seek(0) return buf.getvalue() story.append(PageBreak()) story.append(Paragraph("3. Requirements per Regulation", s_h1)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph("Obligations filtered by your qualification answers. Only applicable categories are shown.", s_center)) if isinstance(all_obligations, list) and all_obligations: # Group by regulation from collections import OrderedDict pdf_reg_groups = OrderedDict() for obl in all_obligations: rn = obl["reg_name"] if rn not in pdf_reg_groups: pdf_reg_groups[rn] = [] pdf_reg_groups[rn].append(obl) for reg_name, obl_groups in pdf_reg_groups.items(): url = REGULATION_URLS.get(reg_name, "") url_text = f" [Official text]" if url else "" story.append(Paragraph(f"{clean(reg_name)}{url_text}", s_h2)) for obl_group in obl_groups: story.append(Paragraph(f"{clean(obl_group['category'])}", s_body)) for o in obl_group["obligations"]: story.append(Paragraph(f"[ ] {clean(o)}", s_item)) if obl_group.get("deadline"): story.append(Paragraph(f"Deadline: {clean(obl_group['deadline'])}", s_source)) else: # Fallback: list other detected regulations with one-liners for tag, reg_name, cat, reason in detected_regs: if reg_name in OTHER_REG_ONE_LINERS: story.append(Paragraph(f"{clean(reg_name)}", s_h2)) story.append(Paragraph(clean(OTHER_REG_ONE_LINERS[reg_name]), s_body)) # Also list other detected regs with one-liners (not in OBLIGATIONS) listed_regs = set(o["reg_name"] for o in all_obligations) if isinstance(all_obligations, list) else set() for tag, reg_name, cat, reason in detected_regs: if reg_name not in listed_regs and reg_name in OTHER_REG_ONE_LINERS and reg_name != "EU AI Act — GPAI Framework (Chapter V)": url = REGULATION_URLS.get(reg_name, "") url_text = f" [Official text]" if url else "" story.append(Paragraph(f"{clean(reg_name)}{url_text}", s_h2)) story.append(Paragraph(clean(OTHER_REG_ONE_LINERS[reg_name]), s_body)) # ════════════════════════════════════════ # 4. COMPLIANCE SYNERGIES & OVERLAPS # ════════════════════════════════════════ section_num = 4 if applicable_overlaps: story.append(PageBreak()) story.append(Paragraph(f"{section_num}. Compliance Synergies", s_h1)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph("Where obligations across regulations overlap. Implement each topic once to satisfy all tagged regulations.", s_center)) for ov in applicable_overlaps: active = ov.get("active_regulations", ov["regulations"]) icon_char = ov.get("icon", "") reg_labels = ov.get("reg_labels", {}) story.append(Paragraph(f"{clean(ov['title'])}", s_overlap_title)) # List participating regs with their specific label for r in active: short = r.replace("EU AI Act — GPAI Framework (Chapter V)", "EU AI Act (GPAI)") label = reg_labels.get(r, "") if label: story.append(Paragraph(f"- {clean(short)}: {clean(label)}", s_item)) else: story.append(Paragraph(f"- {clean(short)}", s_item)) story.append(Paragraph(f"Recommendation: {clean(ov['recommendation'])}", s_overlap_body)) all_short = ", ".join(r.replace("EU AI Act — GPAI Framework (Chapter V)", "EU AI Act (GPAI)") for r in active) for elem in ov.get("shared_elements", []): story.append(Paragraph(f"[ ] {clean(elem)} ({clean(all_short)})", s_item)) story.append(Spacer(1, 6)) section_num += 1 # ════════════════════════════════════════ # 5. GAP ANALYSIS # ════════════════════════════════════════ if gap_items: story.append(PageBreak()) story.append(Paragraph(f"{section_num}. Cross-Jurisdiction Gap Analysis", s_h1)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph("When complying with one regulation, estimated coverage and additional requirements for another.", s_center)) for src, tgt, gap in gap_items: cov = gap["coverage"] story.append(Paragraph(f"{clean(src)} -> {clean(tgt)} ({cov}% already covered)", s_overlap_title)) story.append(Paragraph("Already covered:", s_body)) for c in gap.get("covered", []): story.append(Paragraph(f"+ {clean(c)}", s_item)) story.append(Paragraph("Gaps to address:", s_body)) for g in gap.get("gaps", []): story.append(Paragraph(f"[ ] {clean(g)}", s_item)) story.append(Spacer(1, 4)) section_num += 1 # ════════════════════════════════════════ # DISCLAIMER + LICENSE # ════════════════════════════════════════ story.append(Spacer(1, 16)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_grey, spaceAfter=6)) story.append(Paragraph(f"Disclaimer: {clean(disclaimer_text)}", s_disclaimer)) story.append(Spacer(1, 4)) story.append(Paragraph( "RegMap by Ines Bedar. Licensed under Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0). " "Commercial use prohibited without prior written consent. https://creativecommons.org/licenses/by-nc/4.0/", s_disclaimer, )) doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer) buf.seek(0) return buf.getvalue() # ── Shared qualification helpers (used by Screen 10, 11, and PDF) ── def classify_eu_ai_act(answers, is_provider, is_deployer): """Return applicable EU AI Act categories and obligation keys.""" cats = [] prefix = "q_EU AI Act (Regulation 2024/1689)_" exceptions = answers.get(prefix + "euaia_exception", []) full_exemptions = EU_FULL_EXEMPTIONS if any(ex in exceptions for ex in full_exemptions): return ["exempt"], [] prohibited = answers.get(prefix + "euaia_prohibited", []) if prohibited and "None of the above" not in prohibited: return ["prohibited"], ["prohibited"] annex3 = answers.get(prefix + "euaia_annex3", []) obl_keys = [] if annex3 and "None of the above" not in annex3: art6_3 = answers.get(prefix + "euaia_art6_3", "— Select —") if "Yes" not in art6_3: cats.append("high_risk") if is_provider: obl_keys.append("high_risk_provider") if is_deployer: obl_keys.append("high_risk_deployer") transparency = answers.get(prefix + "euaia_transparency", []) if transparency and "None of the above" not in transparency: cats.append("limited_risk") obl_keys.append("limited_risk") if not cats: cats.append("minimal_risk") obl_keys.append("minimal_risk") return cats, obl_keys def classify_gpai(answers): """Return GPAI obligation key.""" prefix = "q_EU AI Act — GPAI Framework (Chapter V)_" systemic = answers.get(prefix + "gpai_systemic", "— Select —") open_source = answers.get(prefix + "gpai_open_source", "— Select —") if "Yes" in systemic: return "gpai_systemic" elif "open-source" in open_source.lower(): return "gpai_open_source" return "gpai_standard" def classify_colorado(answers, is_provider, is_deployer, data=None): """Return Colorado obligation keys or 'exempt'.""" prefix = "q_Colorado AI Act (SB 24-205)_" consequential = answers.get(prefix + "co_consequential", []) exceptions = answers.get(prefix + "co_exception", []) if not consequential or "None of the above — system does not make consequential decisions" in consequential: return ["exempt"] if exceptions and "None of the above" not in exceptions: if any("approved/regulated by a federal agency" in e.lower() for e in exceptions): return ["exempt"] keys = [] if is_provider: keys.append("developer") if is_deployer: # Auto-detect small deployer from company size (Point 5) is_small = (data or {}).get("is_small_business", False) use_as_intended = answers.get(prefix + "co_use_as_intended", "— Select —") if is_small and "Yes" in use_as_intended: keys.append("small_deployer_exemption") else: keys.append("deployer") return keys if keys else ["deployer"] def requires_fria(answers, data): """Determine if FRIA (Art. 27) is required based on sector and org type. Returns True if the deployer must conduct a FRIA.""" # 1. Public sector → always required if data.get("is_public_sector", False): return True # 2. Private sector providing public services → required (from ID Card) public_services = data.get("provides_public_services", []) if public_services and "None of the above" not in public_services: return True # 3. Credit scoring or life/health insurance (Annex III category 5) → required prefix = "q_EU AI Act (Regulation 2024/1689)_" annex3 = answers.get(prefix + "euaia_annex3", []) for a in annex3: if "credit scoring" in a.lower() or "insurance" in a.lower(): return True return False def classify_difc_reg10(answers): """Return DIFC Reg 10 obligation keys.""" prefix = "q_DIFC Regulation 10 (AI Processing)_" keys = [] autonomous = answers.get(prefix + "difc_autonomous", "— Select —") if "No" in autonomous: return ["exempt"] keys.append("deployer_operator") commercial = answers.get(prefix + "difc_commercial_high_risk", "— Select —") if "Yes" in commercial: keys.append("high_risk") return keys def classify_texas(answers, data): """Return Texas TRAIGA obligation keys based on entity type.""" prefix = "q_Texas TRAIGA (HB 149)_" entity = answers.get(prefix + "tx_entity_type", "— Select —") keys = ["all_covered"] # Prohibited practices apply to all if "state agency" in entity.lower() or "government" in entity.lower(): keys.append("government_deployer") if "healthcare" in entity.lower(): keys.append("healthcare_deployer") # Also check org_type from ID Card as fallback if data.get("is_public_sector", False) and "government_deployer" not in keys: keys.append("government_deployer") return keys def classify_illinois(answers): """Return Illinois HB 3773 obligation keys.""" prefix = "q_Illinois HB 3773 (AI in Employment)_" employment = answers.get(prefix + "il_employment_ai", []) if not employment or "None of the above" in employment: return ["exempt"] keys = ["employer_hb3773"] video = answers.get(prefix + "il_video_interview", "— Select —") if "Yes" in video: keys.append("employer_aivia") return keys def classify_california(answers, data): """Return California CCPA/ADMT obligation keys or 'exempt'.""" # Government and non-profit entities are exempt from CCPA entirely if data.get("is_public_sector", False) or data.get("org_type") == "Non-profit / NGO / academic institution": return ["exempt"] prefix = "q_California CCPA / ADMT Regulations_" threshold = answers.get(prefix + "ca_threshold", []) if not threshold or "None of the above" in threshold: return ["exempt"] admt = answers.get(prefix + "ca_admt", "— Select —") if "No" in admt: return ["exempt"] return ["deployer"] def collect_all_obligations(detected_regs, answers, roles, data=None): """Collect all applicable obligations, returning structured data. Returns: list of dicts: {'reg_name': str, 'category': str, 'obligations': [str], 'is_ai': bool} """ if data is None: data = {} is_provider = any("Provider" in r for r in roles) is_deployer = any("Deployer" in r for r in roles) gpai_name = "EU AI Act — GPAI Framework (Chapter V)" result = [] for tag, reg_name, cat, reason in detected_regs: if reg_name == gpai_name: continue # Handled under EU AI Act if reg_name not in OBLIGATIONS: continue reg_oblig = OBLIGATIONS[reg_name] is_ai = (cat == "ai") if reg_name == "EU AI Act (Regulation 2024/1689)": cats, obl_keys = classify_eu_ai_act(answers, is_provider, is_deployer) if "exempt" in cats: continue for key in obl_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", ""), "is_ai": True, }) # FRIA — only if conditions met (Point 6) if is_deployer and "high_risk" in cats and "high_risk_deployer_fria" in reg_oblig: if requires_fria(answers, data): fria = reg_oblig["high_risk_deployer_fria"] result.append({ "reg_name": reg_name, "category": fria.get("label", "FRIA"), "obligations": fria["obligations"], "deadline": fria.get("deadline", ""), "is_ai": True, }) # GPAI has_gpai = any(n == gpai_name for _, n, _, _ in detected_regs) if has_gpai and gpai_name in OBLIGATIONS: gpai_key = classify_gpai(answers) gpai_oblig = OBLIGATIONS[gpai_name] if gpai_key in gpai_oblig: result.append({ "reg_name": "EU AI Act (Regulation 2024/1689)", "category": gpai_oblig[gpai_key].get("label", gpai_key) + " (GPAI)", "obligations": gpai_oblig[gpai_key]["obligations"], "deadline": gpai_oblig[gpai_key].get("deadline", ""), "is_ai": True, }) elif reg_name == "Colorado AI Act (SB 24-205)": co_keys = classify_colorado(answers, is_provider, is_deployer, data) if "exempt" in co_keys: continue for key in co_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", ""), "is_ai": True, }) elif reg_name == "DIFC Regulation 10 (AI Processing)": difc_keys = classify_difc_reg10(answers) if "exempt" in difc_keys: continue for key in difc_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", ""), "is_ai": True, }) elif "key_obligations" in reg_oblig: # Privacy / other regs with key_obligations result.append({ "reg_name": reg_name, "category": "Key Obligations", "obligations": reg_oblig["key_obligations"], "deadline": "", "is_ai": False, }) elif reg_name == "Texas TRAIGA (HB 149)": tx_keys = classify_texas(answers, data) for key in tx_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", reg_oblig.get("deadline", "")), "is_ai": True, }) elif reg_name == "Illinois HB 3773 (AI in Employment)": il_keys = classify_illinois(answers) if "exempt" in il_keys: continue for key in il_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", ""), "is_ai": True, }) elif reg_name == "California CCPA / ADMT Regulations": ca_keys = classify_california(answers, data) if "exempt" in ca_keys: continue for key in ca_keys: if key in reg_oblig: result.append({ "reg_name": reg_name, "category": reg_oblig[key].get("label", key), "obligations": reg_oblig[key]["obligations"], "deadline": reg_oblig[key].get("deadline", reg_oblig.get("deadline", "")), "is_ai": True, }) else: # Utah and any other simple AI regs — render all obligation blocks for key, value in reg_oblig.items(): if key in ("deadline", "penalty", "scope_note", "threshold_note", "exemptions", "phased_deadlines", "enforcement_note"): continue if isinstance(value, dict) and "obligations" in value: result.append({ "reg_name": reg_name, "category": value.get("label", key), "obligations": value["obligations"], "deadline": value.get("deadline", ""), "is_ai": is_ai, }) return result # ── Header ── st.markdown("""

Navigate AI regulation across jurisdictions

🇪🇺 EU 🇺🇸 US 🇦🇪 UAE
""", unsafe_allow_html=True) # ── Progress ── current = st.session_state.screen screen_labels = { 1: "AI System Identity", 2: "Application Domain", 3: "Company Location", 4: "Geographic Scope", 5: "Role in Value Chain", 6: "Technology Profile", 7: "Data Profile", 8: "Regulatory Map", 9: "Qualification", 10: "Requirements Deep Dive", 11: "Compliance Checklist", } label = screen_labels.get(current, "") # Show intro/about/disclaimer before progress bar on Screen 1 only if current == 1: st.markdown('
From AI system profile to compliance checklist in minutes — RegMap identifies which regulations could apply to your AI system, synergies, and gaps across jurisdictions.
', unsafe_allow_html=True) with st.expander("ℹ️ About RegMap"): st.markdown(""" **RegMap** is a free, open-source tool that helps identify which regulations and requirements may apply to your AI systems across multiple jurisdictions. **Why RegMap?** AI regulation is fragmented — countries and regions legislate at different speeds and with varying requirements. An AI system is a complex stack of components, meaning multiple regulations may apply depending on the sector, data processed, people affected, and markets served. A single system can trigger dozens of obligations across multiple legal frameworks. RegMap solves this complexity in minutes. Describe your AI system, and the tool will detect applicable regulations across EU, US, and UAE (for now!), qualify your risk level, list specific obligations per regulation and role, identify cross-regulation synergies and jurisdiction gaps, and generate a consolidated compliance checklist exportable as PDF. RegMap does not provide legal advice. It is an orientation tool designed to give organisations a structured starting point for their AI compliance journey. --- **Built by Inès Bedar** AI governance and regulatory expert with 7+ years' experience across the French Government (Prime Minister's Services and Data Protection Authority) and multiple industries worldwide. 📄 License: CC BY-NC 4.0 """) with st.expander("🔬 Methodology"): st.markdown(""" RegMap's analysis is based on a structured Regulatory Knowledge Base built from official legal texts. The tool cross-references your AI system profile (jurisdiction, sector, purpose, data types, capabilities, role) against regulatory scope provisions to detect applicable regulations. Qualification questions refine the analysis by determining risk level, exemptions, and specific categories. Coverage percentages in gap analysis are estimated based on requirement-level overlap between regulations. Where multiple regulations require the same compliance activity (e.g. impact assessments, transparency notices, human oversight), RegMap identifies synergies and consolidates them into a single actionable requirement — so you know what to do, not just what each law says. This tool does not use AI to generate its analysis — all logic is rule-based and deterministic. """) st.markdown('
Beta version — This Space is public by default. We recommend not entering directly identifying data, especially in free text fields.
', unsafe_allow_html=True) render_progress_tracker(current) # ═══════════════════════════════════════════════ # SCREEN 1: AI System Identity # ═══════════════════════════════════════════════ if current == 1: st.markdown('

AI System ID Card

', unsafe_allow_html=True) st.markdown('

Basic information about your AI system

', unsafe_allow_html=True) name = st.text_input( "1.1 — AI system name", value=st.session_state.data.get("name", ""), placeholder="e.g. ResumeScreener, ChatAssist, FraudDetect", ) description = st.text_area( "1.2 — Brief description", value=st.session_state.data.get("description", ""), placeholder="Describe what your AI system does in 1-2 sentences", height=100, ) LIFECYCLE_STAGES = [ "Framing / Design — Defining purpose, scope, and intended use. No model trained yet", "Development — Building, training, or fine-tuning the model. Testing in controlled environments", "Pre-deployment — System built, undergoing validation, conformity assessment, or pilot before release", "In production — System is live, placed on the market or put into service", "Post-market monitoring — System in production, actively monitored for incidents and compliance", ] prev_lifecycle = st.session_state.data.get("lifecycle", None) lc_options = ["— Select —"] + LIFECYCLE_STAGES lc_default = 0 if prev_lifecycle: for i, opt in enumerate(lc_options): if opt.startswith(prev_lifecycle): lc_default = i break lifecycle = st.selectbox( "1.3 — Where are you in the AI system lifecycle?", options=lc_options, index=lc_default, ) col1, col2 = st.columns([3, 1]) with col2: if st.button("Next →", use_container_width=True, disabled=(not name.strip())): st.session_state.data["name"] = name.strip() st.session_state.data["description"] = description.strip() if lifecycle != "— Select —": st.session_state.data["lifecycle"] = lifecycle.split(" — ")[0] go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 2: Application Domain # ═══════════════════════════════════════════════ elif current == 2: st.markdown('

Application Domain

', unsafe_allow_html=True) st.markdown('

Industry sectors where your AI system is used

', unsafe_allow_html=True) prev_sector = st.session_state.data.get("sector", []) sector = st.multiselect( "2.1 — Industry sector(s) (select all that apply)", INDUSTRY_SECTORS, default=[s for s in prev_sector if s in INDUSTRY_SECTORS], ) other_sector_text = "" if "Other" in sector: other_sector_text = st.text_input( "2.2 — Please specify your sector", value=st.session_state.data.get("other_sector_text", ""), placeholder="e.g. Space industry, Maritime, Blockchain", key="q_other_sector", ) ORG_TYPES = [ "Private sector entity", "Government / public sector entity", "Non-profit / NGO / academic institution", ] prev_org_type = st.session_state.data.get("org_type", None) org_type_idx = ORG_TYPES.index(prev_org_type) if prev_org_type in ORG_TYPES else None org_type = st.selectbox( "2.3 — Organisation type", ORG_TYPES, index=org_type_idx, placeholder="Choose an option", key="q_org_type", help="This determines which obligations apply under certain regulations (e.g. Texas TRAIGA disclosure requirements apply primarily to government entities).", ) # ── Public services question (private orgs only) ── PUBLIC_SERVICES_OPTIONS = [ "Education", "Healthcare", "Social services", "Housing", "Administration of justice", "Public administration", "None of the above", ] provides_public_services = [] if org_type is not None and org_type != "Government / public sector entity": prev_ps = st.session_state.data.get("provides_public_services", []) default_ps = [o for o in prev_ps if o in PUBLIC_SERVICES_OPTIONS] provides_public_services = st.multiselect( "2.3b — As a private organisation, does your entity provide any of the following public services?", PUBLIC_SERVICES_OPTIONS, default=default_ps, key="q_public_services", help="Private entities providing public services must conduct a Fundamental Rights Impact Assessment (FRIA) under the EU AI Act Art. 27 when deploying high-risk AI systems, just like public bodies.", ) COMPANY_SIZES = [ "Fewer than 50 employees", "50–249 employees", "250–999 employees", "1,000+ employees", ] prev_size = st.session_state.data.get("company_size", None) size_idx = COMPANY_SIZES.index(prev_size) if prev_size in COMPANY_SIZES else None company_size = st.selectbox( "2.4 — Organisation size", COMPANY_SIZES, index=size_idx, placeholder="Choose an option", key="q_company_size", ) can_proceed = len(sector) > 0 and company_size is not None and org_type is not None if "Other" in sector and not other_sector_text.strip(): can_proceed = False col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not can_proceed)): st.session_state.data["sector"] = sector st.session_state.data["other_sector_text"] = other_sector_text.strip() if "Other" in sector else "" st.session_state.data["org_type"] = org_type st.session_state.data["is_public_sector"] = org_type == "Government / public sector entity" st.session_state.data["provides_public_services"] = provides_public_services st.session_state.data["company_size"] = company_size st.session_state.data["is_small_business"] = company_size == "Fewer than 50 employees" go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 3: Company Location # ═══════════════════════════════════════════════ elif current == 3: st.markdown('

Company Location

', unsafe_allow_html=True) st.markdown('

Legal establishment of your organisation

', unsafe_allow_html=True) prev_base = st.session_state.data.get("company_base", None) base_idx = ALL_COUNTRIES.index(prev_base) if prev_base in ALL_COUNTRIES else None company_base = st.selectbox( "3.1 — Country of legal establishment", ALL_COUNTRIES, index=base_idx, placeholder="Choose a country", key="q_company_base", ) col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not company_base)): st.session_state.data["company_base"] = company_base go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 4: Geographic Scope # ═══════════════════════════════════════════════ elif current == 4: st.markdown('

Geographic Scope

', unsafe_allow_html=True) st.markdown('

Countries where your AI system has a presence

', unsafe_allow_html=True) prev_countries = st.session_state.data.get("operating_countries", []) operating_countries = st.multiselect( "4.1 — Countries where the AI system is deployed, distributed, or has end users (select all that apply)", ALL_COUNTRIES, default=[c for c in prev_countries if c in ALL_COUNTRIES], key="q_operating_countries", ) EU_COUNTRY_NAMES_CLEAN = [c.replace(" (EEA)", "") for c in EU_COUNTRIES] selected_eu = [c for c in operating_countries if c in EU_COUNTRY_NAMES_CLEAN or c in ["Iceland", "Liechtenstein", "Norway"]] has_us = "United States" in operating_countries has_uae = "United Arab Emirates" in operating_countries us_states = [] if has_us: prev_states = st.session_state.data.get("us_states", []) us_states = st.multiselect( "4.2 — US states (select all that apply)", US_STATES, default=[s for s in prev_states if s in US_STATES], key="q_us_states", ) uae_emirates = [] if has_uae: prev_emirates = st.session_state.data.get("uae_emirates", []) uae_emirates = st.multiselect( "4.3 — UAE emirates (select all that apply)", UAE_EMIRATES, default=[e for e in prev_emirates if e in UAE_EMIRATES], key="q_uae_emirates", ) # 4.4 — UAE free zones (shown for all emirates) uae_free_zones = [] if uae_emirates: available_fz = [] for em in uae_emirates: if em in UAE_FREE_ZONES: available_fz.extend(UAE_FREE_ZONES[em]) if available_fz: prev_fz = st.session_state.data.get("uae_free_zones", []) uae_free_zones = st.multiselect( "4.4 — UAE free zones (select all that apply, or leave empty if none)", available_fz, default=[f for f in prev_fz if f in available_fz], key="q_uae_free_zones", ) can_proceed = len(operating_countries) > 0 if has_us and not us_states: can_proceed = False if has_uae and not uae_emirates: can_proceed = False col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not can_proceed)): st.session_state.data["operating_countries"] = operating_countries st.session_state.data["us_states"] = us_states if has_us else [] st.session_state.data["us_states_with_regulation"] = [s for s in us_states if s in US_STATES_WITH_AI_REGULATION] if has_us else [] st.session_state.data["eu_countries"] = selected_eu st.session_state.data["uae_emirates"] = uae_emirates if has_uae else [] st.session_state.data["uae_free_zones"] = uae_free_zones if has_uae else [] regions = [] if has_us: regions.append("United States") if selected_eu: regions.append("Europe") if has_uae: regions.append("UAE") st.session_state.data["regions"] = regions go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 5: Role in Value Chain # ═══════════════════════════════════════════════ elif current == 5: st.markdown('

Role in Value Chain

', unsafe_allow_html=True) st.markdown('

Your organisation\'s position in the AI value chain

', unsafe_allow_html=True) prev_roles = st.session_state.data.get("roles", []) roles = st.multiselect( "5.1 — Organisation's role (select all that apply)", ROLES, default=[r for r in prev_roles if r in ROLES], key="q_roles", ) col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not roles)): st.session_state.data["roles"] = roles go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 6: Technology Profile # ═══════════════════════════════════════════════ elif current == 6: st.markdown('

Technology Profile

', unsafe_allow_html=True) st.markdown('

Technical characteristics of your AI system

', unsafe_allow_html=True) # 6.1 AI type prev_type = st.session_state.data.get("ai_type", None) type_idx = AI_TYPES.index(prev_type) if prev_type in AI_TYPES else None ai_type = st.selectbox("6.1 — AI system type", AI_TYPES, index=type_idx, placeholder="Choose an option", key="q_ai_type") # 6.2 Model type MODEL_TYPES = [ "Foundation model — general-purpose, trained on broad data (e.g. GPT, Claude, Llama)", "Fine-tuned model — adapted from a foundation model for specific tasks", "Task-specific model — built and trained for a single purpose", ] prev_model = st.session_state.data.get("model_type", None) model_idx = MODEL_TYPES.index(prev_model) if prev_model in MODEL_TYPES else None model_type = st.selectbox("6.2 — Model type", MODEL_TYPES, index=model_idx, placeholder="Choose an option", key="q_model_type") # 6.3 Capabilities CAPABILITY_OPTIONS = [ "Interacts directly with end users", "Makes or supports decisions that affect individuals", "Profiles individuals (builds user profiles based on behaviour, preferences, or characteristics)", "Generates synthetic content (text, images, audio, video, code)", "Performs emotion recognition", ] prev_caps = st.session_state.data.get("capabilities", []) capabilities = st.multiselect( "6.3 — Capabilities (select all that apply)", CAPABILITY_OPTIONS, default=[c for c in prev_caps if c in CAPABILITY_OPTIONS], key="q_capabilities", ) # 6.4 Human involvement — always shown prev_involvement = st.session_state.data.get("human_involvement", None) INVOLVEMENT_WITH_NA = ["N/A — no decisions affecting individuals", "Fully automated", "Human-assisted", "Human decides"] inv_idx = INVOLVEMENT_WITH_NA.index(prev_involvement) if prev_involvement in INVOLVEMENT_WITH_NA else None human_involvement = st.selectbox( "6.4 — Human involvement in decisions", INVOLVEMENT_WITH_NA, index=inv_idx, placeholder="Choose an option", key="q_involvement", ) # 6.5 Synthetic content types — always shown SYNTHETIC_TYPES = ["Text", "Images", "Audio", "Video", "Code", "None of the above"] prev_content = st.session_state.data.get("synthetic_content", []) synthetic_content = st.multiselect( "6.5 — Synthetic content types (select all that apply)", SYNTHETIC_TYPES, default=[s for s in prev_content if s in SYNTHETIC_TYPES], key="q_content", ) # Enforce None exclusivity none_opt_sc = "None of the above" if none_opt_sc in synthetic_content and len(synthetic_content) > 1: if none_opt_sc not in prev_content: synthetic_content = [none_opt_sc] else: synthetic_content = [s for s in synthetic_content if s != none_opt_sc] st.session_state.data["synthetic_content"] = synthetic_content st.rerun() can_proceed_6 = ai_type is not None and model_type is not None and human_involvement is not None col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not can_proceed_6)): st.session_state.data["ai_type"] = ai_type st.session_state.data["model_type"] = model_type st.session_state.data["gpai"] = model_type.startswith("Foundation model") st.session_state.data["capabilities"] = capabilities st.session_state.data["direct_interaction"] = "Interacts directly with end users" in capabilities st.session_state.data["decision_making"] = "Makes or supports decisions that affect individuals" in capabilities st.session_state.data["profiling"] = "Profiles individuals (builds user profiles based on behaviour, preferences, or characteristics)" in capabilities is_decision = "Makes or supports decisions that affect individuals" in capabilities st.session_state.data["human_involvement"] = human_involvement if is_decision and not human_involvement.startswith("N/A") else "N/A" is_synthetic = "Generates synthetic content (text, images, audio, video, code)" in capabilities st.session_state.data["synthetic_content"] = synthetic_content if is_synthetic else [] st.session_state.data["emotion_recognition"] = "Performs emotion recognition" in capabilities go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 7: Data Profile # ═══════════════════════════════════════════════ elif current == 7: st.markdown('

Data Profile

', unsafe_allow_html=True) st.markdown('

Data processed and training sources

', unsafe_allow_html=True) prev_data_types = st.session_state.data.get("data_types", []) selected_data_types = st.multiselect( "7.1 — Data types processed (select all that apply)", DATA_TYPE_OPTIONS, default=[dt for dt in prev_data_types if dt in DATA_TYPE_OPTIONS], ) st.caption("Select all that apply. Most AI systems process a combination of data types (e.g. both personal and non-personal data).") st.divider() prev_sources = st.session_state.data.get("training_sources", []) training_sources = st.multiselect( "7.2 — Training data sources (select all that apply)", TRAINING_SOURCES, default=[s for s in prev_sources if s in TRAINING_SOURCES], ) col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("View Summary →", use_container_width=True, disabled=(not selected_data_types)): st.session_state.data["data_types"] = selected_data_types st.session_state.data["training_sources"] = training_sources go_next() st.rerun() # ═══════════════════════════════════════════════ # SCREEN 8: Summary # ═══════════════════════════════════════════════ elif current == 8: d = st.session_state.data st.markdown('

AI System ID Card — Summary

', unsafe_allow_html=True) st.markdown(f'

Review your inputs for {d.get("name", "")} AI System

', unsafe_allow_html=True) # Identity lifecycle_str = d.get("lifecycle", "") lifecycle_html = f'

Lifecycle: {lifecycle_str}

' if lifecycle_str else "" st.markdown(f'

AI System Identity

Name: {d.get("name", "—")}

Description: {d.get("description", "—") or "—"}

{lifecycle_html}
', unsafe_allow_html=True) # Domain sectors = d.get("sector", []) sectors_display = ", ".join(sectors) if sectors else "—" other_sec = d.get("other_sector_text", "") if other_sec: sectors_display = sectors_display.replace("Other", f"Other ({other_sec})") st.markdown(f'

Application Domain

Sector(s): {sectors_display}

', unsafe_allow_html=True) # Geography geo_lines = f'

Company base: {d.get("company_base", "—")}

' geo_lines += f'

Organisation type: {d.get("org_type", "—")}

' ps = d.get("provides_public_services", []) if ps and "None of the above" not in ps: geo_lines += f'

Public services provided: {", ".join(ps)}

' geo_lines += f'

Organisation size: {d.get("company_size", "—")}

' op_countries = d.get("operating_countries", []) if op_countries: geo_lines += f'

Countries: {", ".join(op_countries)}

' if d.get("us_states"): regulated = d.get("us_states_with_regulation", []) non_regulated = [s for s in d["us_states"] if s not in regulated] geo_lines += f'

US states: {", ".join(d["us_states"])}

' if regulated: geo_lines += f'

States with AI regulation: {", ".join(regulated)}

' if non_regulated: geo_lines += f'

States without specific AI regulation: {", ".join(non_regulated)}

' if d.get("eu_countries"): geo_lines += f'

EU/EEA countries detected: {", ".join(d["eu_countries"])}

' if d.get("uae_emirates"): geo_lines += f'

UAE emirates: {", ".join(d["uae_emirates"])}

' if d.get("uae_free_zones"): geo_lines += f'

UAE free zones: {", ".join(d["uae_free_zones"])}

' st.markdown(f'

Geographic Scope

{geo_lines}
', unsafe_allow_html=True) # Roles roles_display = ", ".join(d.get("roles", ["—"])) st.markdown(f'

Role in Value Chain

{roles_display}

', unsafe_allow_html=True) # Technology model_type_display = d.get("model_type", "—") caps = d.get("capabilities", []) caps_display = ", ".join(caps) if caps else "None selected" synthetic = ", ".join(d.get("synthetic_content", [])) or "None" involvement = d.get("human_involvement", "N/A") tech_lines = f'

AI type: {d.get("ai_type", "—")}

' tech_lines += f'

Model: {model_type_display}

' tech_lines += f'

Capabilities: {caps_display}

' if d.get("decision_making"): tech_lines += f'

Human involvement in decisions: {involvement}

' if d.get("synthetic_content"): tech_lines += f'

Synthetic content types: {synthetic}

' st.markdown(f'

Technology Profile

{tech_lines}
', unsafe_allow_html=True) # Data data_types_display = ", ".join(d.get("data_types", ["—"])) sources_display = ", ".join(d.get("training_sources", ["—"])) st.markdown(f'

Data Profile

Data types: {data_types_display}

Training sources: {sources_display}

', unsafe_allow_html=True) # ── Regulation detection ── # Tuples: (region_tag, regulation_name, category) # category: "ai" = AI-specific, "other" = related regulation detected_regs = [] # Emoji flags for regions FLAG_MAP = { "EU": "🇪🇺", "FR": "🇫🇷", "US": "🇺🇸", "UAE": "🇦🇪", "DIFC": "🇦🇪", "ADGM": "🇦🇪", } # Helper flags from ID card data data_types = d.get("data_types", []) sectors = d.get("sector", []) roles = d.get("roles", []) capabilities = d.get("capabilities", []) personal_data_types = [ "Personal data (e.g. name, email, ID)", "Pseudonymised data (e.g. hashed identifiers, tokenised records)", "Sensitive/special category data (e.g. health, race, religion, political opinions, sexual orientation)", "Biometric data (e.g. fingerprints, facial recognition, voice)", "Children's data (<18)", ] has_personal_data = any(dt in data_types for dt in personal_data_types) has_copyrighted = "Copyrighted content (text, images, audio, video protected by intellectual property)" in data_types has_biometric = "Biometric data (e.g. fingerprints, facial recognition, voice)" in data_types has_children_data = "Children's data (<18)" in data_types has_decision_making = d.get("decision_making", False) has_direct_interaction = d.get("direct_interaction", False) is_provider = any("Provider" in r for r in roles) is_sme = d.get("is_sme", False) is_employment_sector = "Human Resources & Recruitment" in sectors is_financial_sector = any(s in sectors for s in ["Banking & Financial Services", "Insurance"]) is_healthcare_sector = "Healthcare & Life Sciences" in sectors is_education_sector = "Education & Training" in sectors is_critical_sector = any(s in sectors for s in [ "Energy & Utilities", "Healthcare & Life Sciences", "Banking & Financial Services", "Government & Public Administration", "Telecommunications", "Logistics & Supply Chain", ]) is_manufacturing_or_automotive = any(s in sectors for s in ["Manufacturing", "Automotive & Transportation"]) is_robotics = False is_iot_connected = False is_platform = False # ── Keyword matching on AI system description + other sector ── other_sector_text = d.get("other_sector_text", "") or "" desc_text = ((d.get("description", "") or "") + " " + other_sector_text).lower() kw_sector_map = { "is_healthcare_sector": ["medical", "diagnosis", "diagnostic", "patient", "clinical", "health", "hospital", "pharma", "drug", "therapy", "radiology", "pathology"], "is_financial_sector": ["credit", "loan", "lending", "insurance", "underwriting", "banking", "trading", "investment", "portfolio", "risk scoring", "fraud detection"], "is_employment_sector": ["hiring", "recruitment", "recruiting", "candidate", "resume", "cv screening", "job applicant", "employee", "workforce", "hr ", "human resources", "talent acquisition", "performance review", "workplace monitoring", "employee tracking", "employee surveillance", "worker monitoring"], "is_education_sector": ["student", "school", "university", "education", "learning platform", "grading", "academic", "classroom", "tutor", "e-learning"], "is_real_estate": ["housing", "tenant", "rental", "real estate", "property", "mortgage", "landlord"], "is_critical_sector": ["energy", "power grid", "water supply", "telecom", "transport", "infrastructure", "government"], "is_robotics": ["robot", "robotic", "autonomous", "drone", "unmanned", "self-driving", "autonomous vehicle", "cobot", "exoskeleton", "automated machine", "industrial automation"], "is_iot_connected": ["iot", "connected device", "embedded ai", "edge ai", "smart device", "wearable", "sensor", "smart home", "smart city"], "is_platform": ["platform", "marketplace", "content moderation", "recommendation system", "recommender", "feed algorithm", "content ranking", "social media", "online platform", "user-generated content"], } kw_data_map = { "has_personal_data": ["personal data", "user data", "customer data", "pii", "name", "email", "identity", "user profile"], "has_biometric": ["facial recognition", "fingerprint", "biometric", "face detection", "voice recognition", "iris", "retina", "surveillance", "cctv", "video monitoring", "camera monitoring", "video analytics"], "has_children_data": ["children", "child", "minor", "kid", "underage", "under 13", "under 18", "youth", "parental"], "has_copyrighted": ["copyrighted", "copyright", "licensed content", "intellectual property", "training data", "scraped content", "web scraping"], "has_decision_making": ["decision", "scoring", "ranking", "classify", "classification", "prediction", "recommend", "eligibility", "approval", "rejection", "screening", "assessment", "evaluate", "monitoring", "surveillance", "tracking"], "has_direct_interaction": ["chatbot", "chat bot", "virtual assistant", "conversational", "customer service", "customer support", "interact with users", "end user", "consumer facing"], } # Enrich sector flags from description if desc_text: for kw in kw_sector_map["is_healthcare_sector"]: if kw in desc_text: is_healthcare_sector = True; break for kw in kw_sector_map["is_financial_sector"]: if kw in desc_text: is_financial_sector = True; break for kw in kw_sector_map["is_employment_sector"]: if kw in desc_text: is_employment_sector = True; break for kw in kw_sector_map["is_education_sector"]: if kw in desc_text: is_education_sector = True; break if any(kw in desc_text for kw in kw_sector_map["is_real_estate"]): if "Construction & Real Estate" not in sectors: sectors = sectors + ["Construction & Real Estate"] for kw in kw_sector_map["is_critical_sector"]: if kw in desc_text: is_critical_sector = True; break for kw in kw_sector_map["is_robotics"]: if kw in desc_text: is_robotics = True; break for kw in kw_sector_map["is_iot_connected"]: if kw in desc_text: is_iot_connected = True; break for kw in kw_sector_map["is_platform"]: if kw in desc_text: is_platform = True; break # Enrich data flags from description for kw in kw_data_map["has_personal_data"]: if kw in desc_text: has_personal_data = True; break for kw in kw_data_map["has_biometric"]: if kw in desc_text: has_biometric = True; break for kw in kw_data_map["has_children_data"]: if kw in desc_text: has_children_data = True; break for kw in kw_data_map["has_copyrighted"]: if kw in desc_text: has_copyrighted = True; break for kw in kw_data_map["has_decision_making"]: if kw in desc_text: has_decision_making = True; break for kw in kw_data_map["has_direct_interaction"]: if kw in desc_text: has_direct_interaction = True; break # Sector-based flags (outside desc_text block) if is_manufacturing_or_automotive: is_robotics = True # ── EU ── company_base = d.get("company_base", "") eu_countries = d.get("eu_countries", []) EU_BASE_NAMES = [c.replace(" (EEA)", "") for c in EU_COUNTRIES] has_eu = len(eu_countries) > 0 or company_base in EU_BASE_NAMES if has_eu: # AI-specific detected_regs.append(("EU", "EU AI Act (Regulation 2024/1689)", "ai", "AI system operates in or targets the EU market")) if d.get("gpai", False): detected_regs.append(("EU", "EU AI Act — GPAI Framework (Chapter V)", "ai", "General-purpose AI model with EU presence")) # Related if has_personal_data: detected_regs.append(("EU", "GDPR (Regulation 2016/679)", "other", "Personal data is processed within the EU")) # France-specific: Loi Informatique et Libertés if "France" in eu_countries or company_base == "France": detected_regs.append(("FR", "Loi Informatique et Libertés (Loi n° 78-17)", "other", "Personal data processed in France — national GDPR implementation with CNIL-specific requirements")) if has_copyrighted: detected_regs.append(("EU", "Copyright Directive (2019/790)", "other", "Copyrighted content is used (training or output)")) if is_critical_sector: detected_regs.append(("EU", "NIS2 Directive (2022/2555)", "other", "Critical infrastructure sector in the EU")) if is_provider: detected_regs.append(("EU", "Product Liability Directive (2024/2853)", "other", "You are a provider — strict liability applies to AI products")) if has_decision_making: detected_regs.append(("EU", "Equal Treatment Directives", "other", "AI system makes or supports decisions affecting individuals")) if has_direct_interaction: detected_regs.append(("EU", "Consumer Rights Directive / GPSR", "other", "AI system interacts directly with consumers")) detected_regs.append(("EU", "ePrivacy Directive (2002/58/EC)", "other", "Direct interaction may involve electronic communications data")) if is_healthcare_sector: detected_regs.append(("EU", "Medical Device Regulation (MDR 2017/745)", "other", "Healthcare sector — AI may qualify as a medical device")) if is_robotics: detected_regs.append(("EU", "Machinery Regulation (2023/1230)", "other", "AI integrated in machinery, robotics, or autonomous systems")) if is_platform: detected_regs.append(("EU", "Digital Services Act (DSA 2022/2065)", "other", "Online platform with algorithmic content moderation or recommendation")) if is_iot_connected: detected_regs.append(("EU", "Radio Equipment Directive (RED 2014/53)", "other", "AI embedded in connected devices or IoT hardware")) # ── US ── has_us = "United States" in d.get("operating_countries", []) us_regulated = d.get("us_states_with_regulation", []) # Federal laws first if has_us: detected_regs.append(("US", "FTC Act Section 5 (Unfair/Deceptive Practices)", "other", "AI system operates in the US market")) if is_employment_sector: detected_regs.append(("US", "Title VII (Civil Rights Act)", "other", "Employment sector — AI must not discriminate on protected characteristics")) detected_regs.append(("US", "ADA (Americans with Disabilities Act)", "other", "Employment sector — AI must accommodate disabilities")) if is_financial_sector: detected_regs.append(("US", "ECOA (Equal Credit Opportunity Act)", "other", "Financial sector — credit decisions must be non-discriminatory")) detected_regs.append(("US", "FCRA (Fair Credit Reporting Act)", "other", "Financial sector — AI may qualify as consumer reporting agency")) if "Construction & Real Estate" in sectors: detected_regs.append(("US", "Fair Housing Act", "other", "Real estate sector — AI must not discriminate in housing decisions")) if is_healthcare_sector and has_personal_data: detected_regs.append(("US", "HIPAA (Health Insurance Portability and Accountability Act)", "other", "Healthcare sector with personal health data")) if has_children_data: detected_regs.append(("US", "COPPA (Children's Online Privacy Protection Act)", "other", "System processes children's data (<13)")) if is_education_sector and has_personal_data: detected_regs.append(("US", "FERPA (Family Educational Rights and Privacy Act)", "other", "Education sector with student personal data")) # State-level laws if has_personal_data and has_us: detected_regs.append(("US", "State Data Protection Laws (CCPA, CTDPA, etc.)", "other", "Personal data processed in the US")) # AI-specific state laws if "Colorado" in us_regulated: detected_regs.append(("US", "Colorado AI Act (SB 24-205)", "ai", "AI system operates in Colorado")) if "Texas" in us_regulated: if d.get("is_public_sector", False): detected_regs.append(("US", "Texas TRAIGA (HB 149)", "ai", "Government entity operating AI in Texas — full disclosure + prohibited practices apply")) else: detected_regs.append(("US", "Texas TRAIGA (HB 149)", "ai", "AI system operates in Texas — prohibited practices apply (disclosure obligations apply to government entities only)")) if "Utah" in us_regulated: detected_regs.append(("US", "Utah AI Policy Act (SB 149)", "ai", "AI system operates in Utah")) if "California" in us_regulated: if d.get("is_public_sector", False) or d.get("org_type") == "Non-profit / NGO / academic institution": detected_regs.append(("US", "California CCPA / ADMT Regulations", "ai", "AI system operates in California — NOTE: CCPA/ADMT does not apply to government agencies and non-profit organisations")) else: detected_regs.append(("US", "California CCPA / ADMT Regulations", "ai", "AI system operates in California")) if "Illinois" in us_regulated: detected_regs.append(("US", "Illinois HB 3773 (AI in Employment)", "ai", "AI system operates in Illinois")) if has_biometric: detected_regs.append(("US", "Illinois BIPA (Biometric Information Privacy Act)", "other", "Biometric data processed in Illinois")) # ── UAE ── uae_emirates = d.get("uae_emirates", []) if uae_emirates: # Federal laws first if has_personal_data: detected_regs.append(("UAE", "UAE Federal PDPL (Decree-Law 45/2021)", "other", "Personal data processed in the UAE")) if has_copyrighted: detected_regs.append(("UAE", "Copyright Law (Decree-Law 38/2021) — No TDM exception", "other", "Copyrighted content used — no text and data mining exception in UAE")) detected_regs.append(("UAE", "Cybercrime Law (Decree-Law 34/2021)", "other", "AI system operates in the UAE")) detected_regs.append(("UAE", "Civil Transactions Law (Federal Law 5/1985)", "other", "General tort liability applies to AI in the UAE")) if has_direct_interaction: detected_regs.append(("UAE", "Consumer Protection (Federal Law 15/2020)", "other", "AI system interacts directly with consumers in the UAE")) if has_decision_making or is_employment_sector: detected_regs.append(("UAE", "Anti-Discrimination (Decree-Law 34/2023)", "other", "AI makes decisions or is used in employment in the UAE")) detected_regs.append(("UAE", "Labour Law (Decree-Law 33/2021)", "other", "AI makes decisions or is used in employment in the UAE")) # Free zone level — only if specific free zone selected uae_fz = d.get("uae_free_zones", []) if "DIFC" in uae_fz and has_personal_data: detected_regs.append(("UAE", "DIFC Data Protection Law (Law No. 5 of 2020)", "other", "Personal data processed within DIFC free zone")) detected_regs.append(("UAE", "DIFC Regulation 10 (AI Processing)", "ai", "AI processes personal data within DIFC free zone")) if "ADGM" in uae_fz and has_personal_data: detected_regs.append(("UAE", "ADGM Data Protection Regulations 2021", "other", "Personal data processed within ADGM free zone")) # ── Build banner ── # Merge GPAI into EU AI Act for display (keep separate internally for obligations) has_gpai = any(n == "EU AI Act — GPAI Framework (Chapter V)" for _, n, _, _ in detected_regs) display_regs = [] for tag, name, cat, reason in detected_regs: if name == "EU AI Act — GPAI Framework (Chapter V)": continue # Skip — merged into EU AI Act display if name == "EU AI Act (Regulation 2024/1689)" and has_gpai: display_regs.append((tag, name, cat, reason + " — incl. GPAI provisions (Chapter V)")) else: display_regs.append((tag, name, cat, reason)) ai_regs = [(t, n, r) for t, n, c, r in display_regs if c == "ai"] other_regs = [(t, n, r) for t, n, c, r in display_regs if c == "other"] if detected_regs: banner_content = "" if ai_regs: banner_content += '

AI-Specific Regulations

' banner_content += '' if other_regs: label_class = "reg-section-label" if ai_regs else "reg-section-label-first" banner_content += f'

Related Regulations

' banner_content += '' banner_html = f"""

⚖️ Based on your AI System ID Card, these regulations could apply:

{banner_content}

This list is not exhaustive. Other regulations may apply. Always consult qualified legal counsel.

""" st.markdown(banner_html, unsafe_allow_html=True) else: st.info("No specific AI regulations detected based on your inputs. This may change as more jurisdictions are added.") # Navigation st.session_state.detected_regs = detected_regs if ai_regs: st.markdown("""

Want to go further? Continue to get qualification questions, detailed obligations per regulation, gap analysis, synergies, and a consolidated compliance checklist.

""", unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 1, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col2: if detected_regs: try: empty_themes = {} pdf_bytes = generate_full_pdf(d, detected_regs, {}, empty_themes, [], DISCLAIMER, map_only=True) safe_name = d.get("name", "AI_System").replace(" ", "_")[:30] st.download_button( "Export PDF", data=pdf_bytes, file_name=f"RegMap_{safe_name}_map.pdf", mime="application/pdf", use_container_width=True, ) except Exception: pass with col3: if ai_regs: if st.button("Next →", use_container_width=True): go_to(9) st.rerun() else: st.info("No AI-specific regulations detected.") # ═══════════════════════════════════════════════ # SCREEN 9: Qualification Questions # ═══════════════════════════════════════════════ elif current == 9: st.markdown('

Qualification

', unsafe_allow_html=True) st.markdown('

Refine regulation applicability by answering scope questions for each detected AI-specific regulation.

', unsafe_allow_html=True) # Helper: enforce "None of the above" mutual exclusion def enforce_none_exclusive(selected, previous, options): """If 'None of the above' and other items coexist, keep the most recent intent.""" none_opts = [o for o in options if o.startswith("None of the above")] if not none_opts: return selected none_opt = none_opts[0] has_none = none_opt in selected other_items = [s for s in selected if s != none_opt] if not has_none or not other_items: return selected # Both "None" and other items selected — resolve conflict prev_had_none = none_opt in previous if not prev_had_none: # User just added "None" → keep only "None" return [none_opt] else: # User added something else → remove "None" return other_items detected = st.session_state.get("detected_regs", []) ai_specific = [(tag, name, cat, reason) for tag, name, cat, reason in detected if cat == "ai"] if not ai_specific: st.warning("No AI-specific regulations detected. Please go back and complete the ID Card.") else: answers = st.session_state.qualification_answers d = st.session_state.data gpai_name = "EU AI Act — GPAI Framework (Chapter V)" has_gpai = any(n == gpai_name for _, n, _, _ in ai_specific) displayed = 0 for idx, (tag, reg_name, _, reason) in enumerate(ai_specific): if reg_name == gpai_name: continue # Show inside EU AI Act expander label = f"{tag} — {reg_name}" if reg_name == "EU AI Act (Regulation 2024/1689)" and has_gpai: label += " (incl. GPAI)" with st.expander(label, expanded=True): st.caption(f"Detected because: {reason}") if reg_name in QUALIFICATION_QUESTIONS: questions = QUALIFICATION_QUESTIONS[reg_name]["questions"] for q in questions: q_key = f"q_{reg_name}_{q['id']}" # ── If full exemption selected, skip ALL remaining EU questions ── if reg_name == "EU AI Act (Regulation 2024/1689)" and q["id"] != "euaia_exception": exception_key = f"q_{reg_name}_euaia_exception" exception_val = answers.get(exception_key, []) full_exemptions = EU_FULL_EXEMPTIONS if any(ex in exception_val for ex in full_exemptions): answers[q_key] = [] if q["type"] == "multi_select" else "— Select —" continue # ── If prohibited practice selected, skip remaining EU questions (not exception/sme/prohibited) ── if reg_name == "EU AI Act (Regulation 2024/1689)" and q["id"] not in ("euaia_exception", "euaia_sme", "euaia_prohibited"): prohibited_key = f"q_{reg_name}_euaia_prohibited" prohibited_val = answers.get(prohibited_key, []) if prohibited_val and "None of the above" not in prohibited_val: # User selected a prohibited practice → skip all further questions answers[q_key] = [] if q["type"] == "multi_select" else "— Select —" continue # Point 4b: Skip Art. 6(3) if Annex III = None if q["id"] == "euaia_art6_3": annex3_key = f"q_{reg_name}_euaia_annex3" annex3_val = answers.get(annex3_key, []) if not annex3_val or "None of the above" in annex3_val: answers[q_key] = "— Select —" continue # ── Public services: now collected on ID Card, always skip here ── if q["id"] == "euaia_public_services": answers[q_key] = [] continue # ── Point 5: Show Colorado use-as-intended only if <50 employees ── if q["id"] == "co_use_as_intended": if not d.get("is_small_business", False): answers[q_key] = "— Select —" continue # Point 7: DIFC high-risk helper if q["id"] == "difc_commercial_high_risk": st.caption("ℹ️ **High-risk processing activities** under DIFC include: (a) new/different technologies, (b) considerable amount of personal data with high risk, (c) systematic profiling with legal effects, (d) material amount of special category data.") if q["type"] == "multi_select": prev_val = answers.get(q_key, []) selected = st.multiselect( q["text"], options=q["options"], default=prev_val, key=q_key + "_widget", ) cleaned = enforce_none_exclusive(selected, prev_val, q["options"]) if cleaned != selected: answers[q_key] = cleaned st.rerun() answers[q_key] = cleaned elif q["type"] == "single_select": options_with_blank = ["— Select —"] + q["options"] prev = answers.get(q_key, "— Select —") default_idx = options_with_blank.index(prev) if prev in options_with_blank else 0 selected = st.selectbox( q["text"], options=options_with_blank, index=default_idx, key=q_key + "_widget", ) answers[q_key] = selected else: st.caption("No qualification questions available for this regulation yet.") # Show GPAI questions inside EU AI Act expander if reg_name == "EU AI Act (Regulation 2024/1689)" and has_gpai: st.markdown("---") st.markdown("**GPAI Provisions (Chapter V)**") if gpai_name in QUALIFICATION_QUESTIONS: gpai_questions = QUALIFICATION_QUESTIONS[gpai_name]["questions"] for q in gpai_questions: q_key = f"q_{gpai_name}_{q['id']}" if q["type"] == "multi_select": prev_val = answers.get(q_key, []) selected = st.multiselect(q["text"], options=q["options"], default=prev_val, key=q_key + "_widget") cleaned = enforce_none_exclusive(selected, prev_val, q["options"]) if cleaned != selected: answers[q_key] = cleaned st.rerun() answers[q_key] = cleaned elif q["type"] == "single_select": options_with_blank = ["— Select —"] + q["options"] prev = answers.get(q_key, "— Select —") default_idx = options_with_blank.index(prev) if prev in options_with_blank else 0 selected = st.selectbox(q["text"], options=options_with_blank, index=default_idx, key=q_key + "_widget") answers[q_key] = selected displayed += 1 st.session_state.qualification_answers = answers # Validate all questions answered all_answered = True d = st.session_state.data if ai_specific: for tag, reg_name, _, reason in ai_specific: if reg_name == gpai_name: continue if reg_name in QUALIFICATION_QUESTIONS: for q in QUALIFICATION_QUESTIONS[reg_name]["questions"]: q_key = f"q_{reg_name}_{q['id']}" # Skip EU questions after full exemption if reg_name == "EU AI Act (Regulation 2024/1689)" and q["id"] != "euaia_exception": exception_key = f"q_{reg_name}_euaia_exception" exception_val = answers.get(exception_key, []) full_exemptions = EU_FULL_EXEMPTIONS if any(ex in exception_val for ex in full_exemptions): continue # Skip EU questions after prohibited practice if reg_name == "EU AI Act (Regulation 2024/1689)" and q["id"] not in ("euaia_exception", "euaia_sme", "euaia_prohibited"): prohibited_key = f"q_{reg_name}_euaia_prohibited" prohibited_val = answers.get(prohibited_key, []) if prohibited_val and "None of the above" not in prohibited_val: continue # Skip Art 6(3) if Annex III = None if q["id"] == "euaia_art6_3": annex3_key = f"q_{reg_name}_euaia_annex3" annex3_val = answers.get(annex3_key, []) if not annex3_val or "None of the above" in annex3_val: continue # Skip public services — now collected on ID Card if q["id"] == "euaia_public_services": continue # Skip use-as-intended if not small deployer if q["id"] == "co_use_as_intended": if not d.get("is_small_business", False): continue val = answers.get(q_key) if q["type"] == "multi_select" and not val: all_answered = False elif q["type"] == "single_select" and (not val or val == "— Select —"): all_answered = False # Check GPAI questions if reg_name == "EU AI Act (Regulation 2024/1689)" and has_gpai and gpai_name in QUALIFICATION_QUESTIONS: for q in QUALIFICATION_QUESTIONS[gpai_name]["questions"]: q_key = f"q_{gpai_name}_{q['id']}" val = answers.get(q_key) if q["type"] == "multi_select" and not val: all_answered = False elif q["type"] == "single_select" and (not val or val == "— Select —"): all_answered = False # Navigation if not all_answered: st.info("Please answer all qualification questions to continue.") col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True, disabled=(not all_answered)): # Derive is_sme from EU AI Act qualification answer — only if not exempt sme_key = "q_EU AI Act (Regulation 2024/1689)_euaia_sme" exception_key = "q_EU AI Act (Regulation 2024/1689)_euaia_exception" sme_answer = st.session_state.qualification_answers.get(sme_key, "") exception_val = st.session_state.qualification_answers.get(exception_key, []) full_exemptions = EU_FULL_EXEMPTIONS is_exempt = any(ex in exception_val for ex in full_exemptions) st.session_state.data["is_sme"] = "Yes" in sme_answer and not is_exempt go_to(10) st.rerun() # ═══════════════════════════════════════════════ # SCREEN 10: Requirements Deep Dive # ═══════════════════════════════════════════════ elif current == 10: st.markdown('

Requirements Deep Dive

', unsafe_allow_html=True) st.markdown('

Obligations, synergies, and cross-jurisdiction gap analysis for your AI system.

', unsafe_allow_html=True) detected = st.session_state.get("detected_regs", []) answers = st.session_state.get("qualification_answers", {}) d = st.session_state.data roles = d.get("roles", []) is_provider = any("Provider" in r for r in roles) is_deployer = any("Deployer" in r for r in roles) is_sme = d.get("is_sme", False) company_size = d.get("company_size", "") ai_specific = [(tag, name) for tag, name, cat, _ in detected if cat == "ai"] privacy_regs = [(tag, name) for tag, name, cat, _ in detected if cat == "other" and name in OBLIGATIONS and "key_obligations" in OBLIGATIONS.get(name, {})] other_detected = [(tag, name) for tag, name, cat, _ in detected if cat == "other" and name in OTHER_REG_ONE_LINERS] all_reg_names = [name for _, name, _, _ in detected] # ─── Helper: regulation link ─── def reg_link(reg_name): url = REGULATION_URLS.get(reg_name) if url: return f'📄 Official text' return "" # ─── Helper: render obligations as single HTML block ─── def render_obligations(obligations, label=None): html = '
' if label: html += f'

{label}

' for o in obligations: html += f'

☐ {o}

' html += '
' st.markdown(html, unsafe_allow_html=True) # ─── Qualification helpers ─── def get_eu_ai_act_categories(answers): cats = [] prefix = "q_EU AI Act (Regulation 2024/1689)_" exceptions = answers.get(prefix + "euaia_exception", []) full_exemptions = EU_FULL_EXEMPTIONS if any(ex in exceptions for ex in full_exemptions): return ["exempt"] prohibited = answers.get(prefix + "euaia_prohibited", []) if prohibited and "None of the above" not in prohibited: cats.append("prohibited") annex3 = answers.get(prefix + "euaia_annex3", []) if annex3 and "None of the above" not in annex3: art6_3 = answers.get(prefix + "euaia_art6_3", "— Select —") if "Yes" not in art6_3: cats.append("high_risk") transparency = answers.get(prefix + "euaia_transparency", []) if transparency and "None of the above" not in transparency: cats.append("limited_risk") if not cats: cats.append("minimal_risk") return cats def get_gpai_categories(answers): prefix = "q_EU AI Act — GPAI Framework (Chapter V)_" systemic = answers.get(prefix + "gpai_systemic", "— Select —") open_source = answers.get(prefix + "gpai_open_source", "— Select —") if "Yes" in systemic: return ["gpai_systemic"] elif "open-source" in open_source.lower(): return ["gpai_open_source"] else: return ["gpai_standard"] def get_colorado_status(answers): prefix = "q_Colorado AI Act (SB 24-205)_" consequential = answers.get(prefix + "co_consequential", []) exceptions = answers.get(prefix + "co_exception", []) if not consequential or "None of the above — system does not make consequential decisions" in consequential: return ["exempt"] if exceptions and "None of the above" not in exceptions: if any("approved/regulated by a federal agency" in e.lower() for e in exceptions): return ["exempt"] roles_out = [] if is_provider: roles_out.append("developer") if is_deployer: roles_out.append("deployer") return roles_out if roles_out else ["deployer"] # ─── Compute counts for jump nav ─── gpai_name = "EU AI Act — GPAI Framework (Chapter V)" has_gpai_s10 = any(n == gpai_name for _, n in ai_specific) ai_count = len([1 for _, n in ai_specific if n != gpai_name]) privacy_count = len(privacy_regs) other_count = len(other_detected) # Determine which AI regs are NOT exempt (for gap analysis AND synergies) non_exempt_ai = [] for tag, reg_name in ai_specific: if reg_name == gpai_name: continue if reg_name == "EU AI Act (Regulation 2024/1689)": eu_cats = get_eu_ai_act_categories(answers) if "exempt" in eu_cats or "prohibited" in eu_cats: continue elif reg_name == "Colorado AI Act (SB 24-205)": if "exempt" in get_colorado_status(answers): continue elif reg_name == "Illinois HB 3773 (AI in Employment)": if "exempt" in classify_illinois(answers): continue elif reg_name == "California CCPA / ADMT Regulations": if "exempt" in classify_california(answers, d): continue elif reg_name == "DIFC Regulation 10 (AI Processing)": if "exempt" in classify_difc_reg10(answers): continue non_exempt_ai.append(reg_name) # Build list of all active (non-exempt) regulation names for synergy filtering active_reg_names = list(non_exempt_ai) # Add privacy/other regs (always active if detected) for tag, name, cat, _ in detected: if cat != "ai" and name not in active_reg_names: active_reg_names.append(name) # Add GPAI if EU AI Act is active if "EU AI Act (Regulation 2024/1689)" in non_exempt_ai: if gpai_name not in active_reg_names: active_reg_names.append(gpai_name) # Topic-based synergy filtering: show synergy if 2+ listed regulations apply applicable_overlaps = [] for ov in OVERLAP_ANALYSIS: matching_regs = [r for r in ov["regulations"] if r in active_reg_names] if len(matching_regs) >= 2: applicable_overlaps.append({**ov, "active_regulations": matching_regs}) synergy_count = len(applicable_overlaps) ai_reg_names_for_gap = non_exempt_ai gap_count = 0 for src in ai_reg_names_for_gap: if src in GAP_ANALYSIS: for tgt in ai_reg_names_for_gap: if tgt != src and tgt in GAP_ANALYSIS[src]: gap_count += 1 # ─── Jump nav ─── jump_links = f'⚙️ AI-Specific ({ai_count})' if privacy_count: jump_links += f' 🔒 Privacy ({privacy_count})' if other_count: jump_links += f' 📋 Other ({other_count})' if synergy_count: jump_links += f' 🔗 Synergies ({synergy_count})' if gap_count: jump_links += f' 📊 Gap Analysis ({gap_count})' st.markdown(f"""

On this page

""", unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION A: AI-SPECIFIC OBLIGATIONS # ════════════════════════════════════════ st.markdown(f'
⚙️ AI-Specific Regulations — Obligations {ai_count} regulations
', unsafe_allow_html=True) for tag, reg_name in ai_specific: if reg_name == gpai_name: continue display_label = f"{tag} — {reg_name}" if reg_name == "EU AI Act (Regulation 2024/1689)" and has_gpai_s10: display_label += " (incl. GPAI)" link_html = reg_link(reg_name) with st.expander(display_label, expanded=True): st.markdown(link_html, unsafe_allow_html=True) if reg_name not in OBLIGATIONS: st.caption("Detailed obligations not yet available for this regulation.") continue reg_oblig = OBLIGATIONS[reg_name] # ── EU AI Act ── if reg_name == "EU AI Act (Regulation 2024/1689)": prefix = "q_EU AI Act (Regulation 2024/1689)_" cats = get_eu_ai_act_categories(answers) if "exempt" in cats: st.markdown('EXEMPT', unsafe_allow_html=True) st.write("Based on your answers, your AI system falls under an exception to the EU AI Act. No specific obligations apply under this regulation.") continue if "prohibited" in cats: st.markdown('PROHIBITED PRACTICE DETECTED', unsafe_allow_html=True) render_obligations(reg_oblig["prohibited"]["obligations"]) st.error("⚠️ If your AI system performs a prohibited practice under Art. 5, it must not be placed on the market, put into service, or used in the EU. Existing systems must be withdrawn or recalled. Fines: up to €35M or 7% of global annual turnover.") if is_sme: st.info("💡 **SME penalty cap applies.** As an SME, penalties are capped at whichever is **lower** between the percentage and the absolute amount (Art. 99(5)).") st.info("💡 **Next step:** If you modify your system to remove the prohibited characteristics, re-run this assessment. Your system will likely fall into another EU AI Act category (high-risk, limited-risk, or minimal-risk) with different, manageable compliance obligations.") continue if "high_risk" in cats: os_option = "The AI system is released under a free and open-source licence with publicly available parameters, including weights" exceptions_selected = answers.get(prefix + "euaia_exception", []) if os_option in exceptions_selected: st.warning("⚠️ **Open-source does not exempt your system.** Under Art. 2(12), the open-source exception does NOT apply to high-risk AI systems (Annex III), prohibited practices (Art. 5), or GPAI models with systemic risk. All obligations apply in full.") st.markdown('HIGH-RISK AI SYSTEM', unsafe_allow_html=True) if is_sme: st.success("💡 **SME provisions apply.** As an SME/start-up, you benefit from: simplified technical documentation (Art. 11), reduced conformity assessment fees (Art. 62), priority access to regulatory sandboxes, and capped penalties (whichever is lower, not higher).") if is_provider and "high_risk_provider" in reg_oblig: render_obligations(reg_oblig["high_risk_provider"]["obligations"], reg_oblig["high_risk_provider"]["label"]) if is_deployer and "high_risk_deployer" in reg_oblig: render_obligations(reg_oblig["high_risk_deployer"]["obligations"], reg_oblig["high_risk_deployer"]["label"]) if is_deployer and "high_risk_deployer_fria" in reg_oblig: if requires_fria(answers, d): fria = reg_oblig["high_risk_deployer_fria"] render_obligations(fria["obligations"], fria["label"]) if "limited_risk" in cats: label = "Transparency Obligations (Art. 50)" if "high_risk" in cats else reg_oblig["limited_risk"]["label"] render_obligations(reg_oblig["limited_risk"]["obligations"], label) if "minimal_risk" in cats: render_obligations(reg_oblig["minimal_risk"]["obligations"], reg_oblig["minimal_risk"]["label"]) # GPAI obligations if has_gpai_s10 and gpai_name in OBLIGATIONS: st.markdown("---") st.markdown("**GPAI Provisions (Chapter V)**") gpai_oblig = OBLIGATIONS[gpai_name] gpai_cats = get_gpai_categories(answers) for cat in gpai_cats: if cat in gpai_oblig: render_obligations(gpai_oblig[cat]["obligations"], gpai_oblig[cat]["label"]) # ── Colorado ── elif reg_name == "Colorado AI Act (SB 24-205)": cats = get_colorado_status(answers) if "exempt" in cats: st.markdown('NOT APPLICABLE', unsafe_allow_html=True) st.write("Based on your answers, your system does not make consequential decisions or falls under a federal exemption.") else: is_small_deployer = is_deployer and d.get("is_small_business", False) if is_small_deployer: st.success("💡 **Small business deployer provisions may apply.** Deployers with fewer than 50 FTE are exempt from risk management programs, impact assessments, and public statements — **only if** you do not train the AI system with your own data and use it only as intended by the developer. You must still provide consumers with the developer's impact assessment and required notices.") for cat in cats: if cat in reg_oblig: render_obligations(reg_oblig[cat]["obligations"], reg_oblig[cat]["label"]) # ── DIFC ── elif reg_name == "DIFC Regulation 10 (AI Processing)": st.info(f"ℹ️ **Deployer vs. Operator:** {DIFC_CONTROLLER_NOTE}") if "deployer_operator" in reg_oblig: render_obligations(reg_oblig["deployer_operator"]["obligations"], reg_oblig["deployer_operator"]["label"]) difc_hr = answers.get("q_DIFC Regulation 10 (AI Processing)_difc_commercial_high_risk", "— Select —") if "Yes" in difc_hr and "high_risk" in reg_oblig: obl_set = reg_oblig["high_risk"] render_obligations(obl_set["obligations"], obl_set["label"]) if obl_set.get("note"): st.caption(obl_set["note"]) # ── Texas ── elif reg_name == "Texas TRAIGA (HB 149)": tx_keys = classify_texas(answers, d) for key in tx_keys: if key in reg_oblig: render_obligations(reg_oblig[key]["obligations"], reg_oblig[key]["label"]) if d.get("is_public_sector", False) or "government_deployer" in tx_keys: st.info("ℹ️ As a government entity, full disclosure obligations apply in addition to prohibited practices.") else: st.caption("Prohibited practices apply to all entities. Government disclosure obligations are not applicable to private-sector entities.") # ── Illinois ── elif reg_name == "Illinois HB 3773 (AI in Employment)": il_keys = classify_illinois(answers) if "exempt" in il_keys: st.markdown('NOT APPLICABLE', unsafe_allow_html=True) st.write("Based on your answers, this AI system is not used for employment-related decisions in Illinois.") else: for key in il_keys: if key in reg_oblig: render_obligations(reg_oblig[key]["obligations"], reg_oblig[key]["label"]) if reg_oblig[key].get("scope"): st.caption(f"Scope: {reg_oblig[key]['scope']}") if "employer_aivia" not in il_keys: st.caption("The AI Video Interview Act (AIVIA) does not apply — your system does not analyse video interviews.") # ── California ── elif reg_name == "California CCPA / ADMT Regulations": ca_keys = classify_california(answers, d) if "exempt" in ca_keys: st.markdown('NOT APPLICABLE', unsafe_allow_html=True) exemptions = reg_oblig.get("exemptions", "") if d.get("is_public_sector", False) or d.get("org_type") == "Non-profit / NGO / academic institution": st.write(f"**Exempt:** {exemptions}") else: st.write("Based on your answers, your organisation does not meet CCPA thresholds or does not use ADMT for significant decisions.") else: for key in ca_keys: if key in reg_oblig: render_obligations(reg_oblig[key]["obligations"], reg_oblig[key]["label"]) # Show phased deadlines phased = reg_oblig.get("phased_deadlines", {}) if phased: st.markdown("**Phased Compliance Deadlines:**") for milestone, date in phased.items(): label = milestone.replace("_", " ").title() st.markdown(f"- **{label}:** {date}") # ── All other AI-specific (Utah, etc.) ── else: for key, value in reg_oblig.items(): if key in ("deadline", "penalty", "scope_note", "threshold_note", "exemptions", "phased_deadlines", "enforcement_note"): continue if isinstance(value, dict) and "obligations" in value: render_obligations(value["obligations"], value.get("label", key)) # ════════════════════════════════════════ # SECTION B: PRIVACY OBLIGATIONS # ════════════════════════════════════════ if privacy_regs: st.markdown(f'
🔒 Privacy & Related — Key AI Obligations {privacy_count} regulations
', unsafe_allow_html=True) for tag, reg_name in privacy_regs: link_html = reg_link(reg_name) with st.expander(f"{tag} — {reg_name}", expanded=True): st.markdown(link_html, unsafe_allow_html=True) reg_oblig = OBLIGATIONS.get(reg_name, {}) key_obligs = reg_oblig.get("key_obligations", []) if key_obligs: render_obligations(key_obligs) if reg_oblig.get("ai_relevant_note"): st.caption(reg_oblig['ai_relevant_note']) # ════════════════════════════════════════ # SECTION C: OTHER APPLICABLE REGULATIONS # ════════════════════════════════════════ if other_detected: st.markdown(f'
📋 Other Applicable Regulations {other_count} regulations
', unsafe_allow_html=True) for tag, reg_name in other_detected: link_html = reg_link(reg_name) one_liner = OTHER_REG_ONE_LINERS.get(reg_name, "") with st.expander(f"{tag} — {reg_name}", expanded=True): st.markdown(link_html, unsafe_allow_html=True) if one_liner: st.markdown(f'

{one_liner}

', unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION D: COMPLIANCE SYNERGIES # ════════════════════════════════════════ if applicable_overlaps: st.markdown(f'
🔗 Compliance Synergies {synergy_count} topics identified
', unsafe_allow_html=True) st.caption("Where obligations across regulations overlap. Comply once to satisfy multiple requirements.") for ov in applicable_overlaps: active = ov.get("active_regulations", ov["regulations"]) icon = ov.get("icon", "🔗") reg_labels = ov.get("reg_labels", {}) # Build per-regulation label list regs_html = "" for r in active: short = r.replace("EU AI Act — GPAI Framework (Chapter V)", "EU AI Act (GPAI)") label = reg_labels.get(r, "") if label: regs_html += f'

{short}: {label}

' else: regs_html += f'

• {short}

' st.markdown(f"""

{icon} {ov['title']}

{regs_html}

Recommendation: {ov['recommendation']}

""", unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION E: GAP ANALYSIS (non-exempt AI regs only) # ════════════════════════════════════════ if gap_count > 0 and len(ai_reg_names_for_gap) >= 2: st.markdown(f'
📊 Gap Analysis {gap_count} comparisons
', unsafe_allow_html=True) st.caption("Estimated coverage when complying with one regulation, then expanding to another.") for src in ai_reg_names_for_gap: if src not in GAP_ANALYSIS: continue for tgt in ai_reg_names_for_gap: if tgt == src or tgt not in GAP_ANALYSIS[src]: continue gap = GAP_ANALYSIS[src][tgt] cov = gap["coverage"] rem = 100 - cov covered_html = "".join(f'

+ {c}

' for c in gap["covered"]) gaps_html = "".join(f'

− {g}

' for g in gap["gaps"]) filled_w = max(cov, 15) empty_w = max(rem, 5) st.markdown(f"""

{src} → {tgt}

{cov}% covered
{rem}% gap

Already covered:

{covered_html}

Gaps to address:

{gaps_html}
""", unsafe_allow_html=True) # ════════════════════════════════════════ # DISCLAIMER + NAVIGATION # ════════════════════════════════════════ st.markdown(f'
⚠️ Disclaimer: {DISCLAIMER}
', unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: if st.button("Next →", use_container_width=True): go_to(11) st.rerun() # ═══════════════════════════════════════════════ # SCREEN 11: Compliance Checklist # Faithful to Deep Dive: obligations, synergies, gaps # ═══════════════════════════════════════════════ elif current == 11: st.markdown('

Compliance Checklist

', unsafe_allow_html=True) st.markdown('

Consolidated requirements taking synergies into account. Export the full PDF for gap analysis and detailed breakdown.

', unsafe_allow_html=True) detected = st.session_state.get("detected_regs", []) answers = st.session_state.get("qualification_answers", {}) d = st.session_state.data roles = d.get("roles", []) is_provider = any("Provider" in r for r in roles) is_deployer = any("Deployer" in r for r in roles) system_name = d.get("name", "Your AI system") all_reg_names = [name for _, name, _, _ in detected] # ── Collect all applicable obligations ── all_obligations = collect_all_obligations(detected, answers, roles, d) # ── Compute non-exempt AI regs (for synergies and gap analysis) ── gpai_name = "EU AI Act — GPAI Framework (Chapter V)" ai_specific = [(tag, name) for tag, name, cat, _ in detected if cat == "ai"] non_exempt_ai = [] for tag, reg_name in ai_specific: if reg_name == gpai_name: continue if reg_name == "EU AI Act (Regulation 2024/1689)": cats, _ = classify_eu_ai_act(answers, is_provider, is_deployer) if "exempt" in cats or "prohibited" in cats: continue elif reg_name == "Colorado AI Act (SB 24-205)": co_keys = classify_colorado(answers, is_provider, is_deployer, d) if "exempt" in co_keys: continue elif reg_name == "Illinois HB 3773 (AI in Employment)": il_keys = classify_illinois(answers) if "exempt" in il_keys: continue elif reg_name == "California CCPA / ADMT Regulations": ca_keys = classify_california(answers, d) if "exempt" in ca_keys: continue elif reg_name == "DIFC Regulation 10 (AI Processing)": difc_keys = classify_difc_reg10(answers) if "exempt" in difc_keys: continue non_exempt_ai.append(reg_name) # Build active (non-exempt) reg list for synergy filtering active_reg_names = list(non_exempt_ai) for tag, name, cat, _ in detected: if cat != "ai" and name not in active_reg_names: active_reg_names.append(name) if "EU AI Act (Regulation 2024/1689)" in non_exempt_ai and gpai_name not in active_reg_names: active_reg_names.append(gpai_name) # ── Compute applicable synergies (topic-based) ── applicable_overlaps = [] for ov in OVERLAP_ANALYSIS: matching_regs = [r for r in ov["regulations"] if r in active_reg_names] if len(matching_regs) >= 2: applicable_overlaps.append({**ov, "active_regulations": matching_regs}) gap_items = [] for src in non_exempt_ai: if src in GAP_ANALYSIS: for tgt in non_exempt_ai: if tgt != src and tgt in GAP_ANALYSIS[src]: gap_items.append((src, tgt, GAP_ANALYSIS[src][tgt])) # ── Count totals ── total_reg_obligations = sum(len(o["obligations"]) for o in all_obligations) gap_total = sum(len(g[2]["gaps"]) for g in gap_items) st.markdown(f"""
{system_name} — {total_reg_obligations} obligations across {len(set(o['reg_name'] for o in all_obligations))} regulations {f' · {len(applicable_overlaps)} synergies consolidating your compliance effort' if applicable_overlaps else ''}
""", unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION A: CONSOLIDATED REQUIREMENTS # Synergy-aware: show shared obligations once with all regs tagged # ════════════════════════════════════════ if applicable_overlaps: st.markdown(f'
✅ Consolidated Requirements {len(applicable_overlaps)} synergy topics
', unsafe_allow_html=True) st.caption("Where multiple regulations require the same action, we list it once. Implement each item to satisfy all tagged regulations simultaneously.") for ov in applicable_overlaps: active = ov.get("active_regulations", ov["regulations"]) icon = ov.get("icon", "🔗") reg_labels = ov.get("reg_labels", {}) # Regulation tags tags_html = "" for r in active: short = r.replace("EU AI Act — GPAI Framework (Chapter V)", "EU AI Act (GPAI)") label = reg_labels.get(r, "") if label: tags_html += f'

{short}: {label}

' else: tags_html += f'

• {short}

' # Checklist items from shared_elements checklist_html = "" all_short = ", ".join(r.replace("EU AI Act — GPAI Framework (Chapter V)", "EU AI Act (GPAI)") for r in active) for elem in ov.get("shared_elements", []): checklist_html += f'

☐ {elem} ({all_short})

' st.markdown(f"""

{icon} {ov['title']}

{tags_html}

Recommendation: {ov['recommendation']}

{checklist_html}
""", unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION B: REGULATION-SPECIFIC OBLIGATIONS # (what remains after synergies) # ════════════════════════════════════════ # Group by regulation from collections import OrderedDict reg_groups = OrderedDict() for obl in all_obligations: rn = obl["reg_name"] if rn not in reg_groups: reg_groups[rn] = [] reg_groups[rn].append(obl) ai_reg_names = set(o["reg_name"] for o in all_obligations if o["is_ai"]) privacy_reg_names = set(o["reg_name"] for o in all_obligations if not o["is_ai"]) # AI-specific regulations ai_regs_to_show = [rn for rn in reg_groups if rn in ai_reg_names] if ai_regs_to_show: st.markdown(f'
⚙️ AI Obligations by Regulation {len(ai_regs_to_show)} regulations
', unsafe_allow_html=True) for reg_name in ai_regs_to_show: url = REGULATION_URLS.get(reg_name, "") link_html = f' 📄 Official text' if url else "" with st.expander(reg_name, expanded=True): if url: st.markdown(link_html, unsafe_allow_html=True) for obl_group in reg_groups[reg_name]: items_html = f'

{obl_group["category"]}

' for o in obl_group["obligations"]: items_html += f'

☐ {o}

' if obl_group.get("deadline"): items_html += f'

⏰ Deadline: {obl_group["deadline"]}

' st.markdown(f'
{items_html}
', unsafe_allow_html=True) # Privacy regulations priv_regs_to_show = [rn for rn in reg_groups if rn in privacy_reg_names] if priv_regs_to_show: st.markdown(f'
🔒 Data Protection & Privacy {len(priv_regs_to_show)} regulations
', unsafe_allow_html=True) for reg_name in priv_regs_to_show: url = REGULATION_URLS.get(reg_name, "") link_html = f' 📄 Official text' if url else "" with st.expander(reg_name, expanded=True): if url: st.markdown(link_html, unsafe_allow_html=True) for obl_group in reg_groups[reg_name]: items_html = "" for o in obl_group["obligations"]: items_html += f'

☐ {o}

' st.markdown(f'
{items_html}
', unsafe_allow_html=True) # ════════════════════════════════════════ # SECTION C: OTHER APPLICABLE REGULATIONS # ════════════════════════════════════════ other_detected = [(tag, name) for tag, name, cat, _ in detected if cat == "other" and name in OTHER_REG_ONE_LINERS and name not in reg_groups] if other_detected: st.markdown(f'
📋 Other Applicable Regulations {len(other_detected)} regulations
', unsafe_allow_html=True) for tag, reg_name in other_detected: one_liner = OTHER_REG_ONE_LINERS.get(reg_name, "") url = REGULATION_URLS.get(reg_name, "") link_html = f' 📄' if url else "" st.markdown(f'

{tag} — {reg_name}{link_html}
{one_liner}

', unsafe_allow_html=True) # ── What's Next + CTA ── st.markdown("""

What's next?

This checklist is your starting point. The next steps depend on your system's lifecycle stage, risk level, and organisational context: prioritising obligations, building internal processes, preparing documentation, and engaging with authorities where required.

Export the full PDF report for detailed synergies, gap analysis, and the complete compliance breakdown.

Need help implementing these requirements? Let's talk

""", unsafe_allow_html=True) # ── Disclaimer + Navigation ── st.markdown(f'
⚠️ Disclaimer: {DISCLAIMER}
', unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("← Back", use_container_width=True): go_back() st.rerun() with col3: try: pdf_bytes = generate_full_pdf(d, detected, answers, all_obligations, applicable_overlaps, DISCLAIMER, gap_items=gap_items) safe_name = system_name.replace(" ", "_")[:30] st.download_button( "📥 Export Full PDF Report", data=pdf_bytes, file_name=f"RegMap_{safe_name}_report.pdf", mime="application/pdf", use_container_width=True, ) except ImportError: if st.button("Export PDF", use_container_width=True): st.toast("PDF generation requires reportlab. Add it to requirements.txt.", icon="⚠️") except Exception as e: if st.button("Export PDF", use_container_width=True): st.error(f"PDF generation failed: {e}")