bpmredacademy's picture
Create app.py
ad971fd verified
import os
import re
import textwrap
import gradio as gr
ENGAGEMENT_EMAIL = os.getenv("ENGAGEMENT_EMAIL", "engagement@bpm.ba")
GOVERNANCE_EMAIL = os.getenv("GOVERNANCE_EMAIL", "governance@bpm.ba")
# Optional gating (comma-separated)
ACCESS_CODES_RAW = os.getenv("FINC2E_ACCESS_CODES", "").strip()
ACCESS_CODES = {c.strip() for c in ACCESS_CODES_RAW.split(",") if c.strip()}
TITLE = "FinC2E — Governance Gateway"
TAGLINE = "Structured, audit-oriented reasoning for regulated decision support (preview)."
DISCLAIMER = (
"FinC2E outputs are informational and non-executive. "
"No legal advice, financial advice, trading signals, or decision execution is provided. "
"Users remain responsible for compliance, policies, and final decisions."
)
RISK_DOMAINS = [
"AML / CFT",
"KYC / CDD",
"Sanctions screening",
"Fraud / transaction anomaly",
"Market conduct / suitability",
"Operational risk",
"Model risk / AI governance",
"Data privacy / security",
"Other (specify)"
]
def normalize(s: str) -> str:
return (s or "").strip()
def valid_access(code: str) -> bool:
if not ACCESS_CODES:
return True # no gate
return normalize(code) in ACCESS_CODES
def ensure_min_len(field: str, n: int, label: str):
if len(normalize(field)) < n:
return f"[Missing/too short] {label} (min {n} chars)"
return None
def finc2e_reason(case_id, jurisdiction, domain, other_domain, objective, facts, constraints, decision_stage, access_code):
# Gate
if not valid_access(access_code):
return (
"ACCESS DENIED\n"
"------------\n"
"This preview requires a valid FinC2E access code.\n"
f"Request access: {ENGAGEMENT_EMAIL}\n"
)
domain_final = other_domain if domain == "Other (specify)" else domain
domain_final = normalize(domain_final) or domain
# Basic validation
issues = []
for (val, n, label) in [
(objective, 25, "Objective"),
(facts, 40, "Case Facts / Data"),
(constraints, 20, "Constraints / Policies"),
(jurisdiction, 2, "Jurisdiction"),
]:
m = ensure_min_len(val, n, label)
if m:
issues.append(m)
if issues:
return "INPUT QUALITY WARNING\n---------------------\n" + "\n".join(f"- {i}" for i in issues) + "\n\nProvide clearer inputs for higher analytical value."
# Produce a structured, audit-oriented "preview" (still non-executive)
out = f"""
FINC2E GOVERNANCE OUTPUT (PREVIEW) — NON-EXECUTIVE
=================================================
Case Header
-----------
- Case ID: {normalize(case_id) or "N/A"}
- Jurisdiction: {normalize(jurisdiction)}
- Domain: {domain_final}
- Decision Stage: {normalize(decision_stage) or "N/A"}
1) Decision Objective (What must be decided?)
--------------------------------------------
{normalize(objective)}
2) Known Facts / Inputs (What is observed?)
------------------------------------------
{normalize(facts)}
3) Constraints & Policies (What must be respected?)
--------------------------------------------------
{normalize(constraints)}
4) Risk Framing (What can go wrong?)
-----------------------------------
- Primary risk vectors (domain-dependent)
- Irreversibility / tail risk considerations
- Compliance exposure and evidentiary needs
5) Minimum Evidence Checklist (Audit-oriented)
---------------------------------------------
- Identity / entity attributes sufficient for the domain
- Source of funds / source of wealth (if relevant)
- Transaction narrative coherence (if relevant)
- Sanctions/PEP screening artifacts (if relevant)
- Data provenance + timestamps + responsible reviewer
6) Structured Questions (What must be clarified next?)
-----------------------------------------------------
- What assumptions are being made due to missing data?
- Which policy threshold(s) apply in this jurisdiction?
- What would change the classification outcome?
- What is the acceptable false-positive / false-negative posture?
7) Suggested Review Path (Human-in-the-loop)
-------------------------------------------
- Step A: Normalize inputs and verify provenance
- Step B: Apply policy thresholds to classify risk band
- Step C: Collect missing evidence (if required)
- Step D: Document rationale and reviewer accountability
- Step E: Escalate if any high-severity triggers are present
Governance Notes
----------------
- Output is informational; no decision is executed.
- For production pilots: policy binding, audit logs, and access controls are mandatory.
DISCLAIMER
----------
{DISCLAIMER}
© 2026 BPM RED Academy — All rights reserved.
"""
return textwrap.dedent(out).strip()
with gr.Blocks(theme=gr.themes.Soft(), title=TITLE) as demo:
gr.Markdown(
f"""
# {TITLE}
**{TAGLINE}**
⚠️ **Preview only.** {DISCLAIMER}
---
"""
)
# Optional gate UI
if ACCESS_CODES:
access_code = gr.Textbox(label="FinC2E Access Code (Licensed)", placeholder="e.g., FINC2E-CLIENT-001")
else:
access_code = gr.Textbox(label="FinC2E Access Code (optional)", placeholder="(optional)")
with gr.Row():
case_id = gr.Textbox(label="Case ID (optional)", placeholder="e.g., CASE-2026-0007")
jurisdiction = gr.Textbox(label="Jurisdiction", placeholder="e.g., EU / UK / BiH / UAE")
with gr.Row():
domain = gr.Dropdown(label="Domain", choices=RISK_DOMAINS, value="AML / CFT")
other_domain = gr.Textbox(label="If Other, specify domain", placeholder="e.g., Payments monitoring, UBO risk, etc.")
decision_stage = gr.Textbox(label="Decision stage (optional)", placeholder="e.g., onboarding / ongoing monitoring / escalation review")
objective = gr.Textbox(
label="Decision objective",
lines=2,
placeholder="Describe the decision to be supported (not executed). Example: classify risk level and define what evidence is required for review."
)
facts = gr.Textbox(
label="Case facts / inputs",
lines=6,
placeholder="Provide relevant facts only (no unnecessary personal data). Include amounts, timelines, entity types, triggers, and what is already verified."
)
constraints = gr.Textbox(
label="Constraints / policies",
lines=4,
placeholder="List policy constraints: thresholds, prohibited conditions, escalation triggers, documentation requirements, and any jurisdictional rules you must respect."
)
run = gr.Button("Generate Governance Output (Preview)", variant="primary")
output = gr.Textbox(label="FinC2E Output (Non-Executive)", lines=20)
run.click(
finc2e_reason,
inputs=[case_id, jurisdiction, domain, other_domain, objective, facts, constraints, decision_stage, access_code],
outputs=[output]
)
gr.Markdown(
f"""
---
### Request access / enterprise pilots
- Engagement: **{ENGAGEMENT_EMAIL}**
- Governance: **{GOVERNANCE_EMAIL}**
© 2026 BPM RED Academy — All rights reserved.
"""
)
if __name__ == "__main__":
demo.launch()