bpmredacademy commited on
Commit
ad971fd
·
verified ·
1 Parent(s): 0ae150d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +208 -0
app.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import textwrap
4
+ import gradio as gr
5
+
6
+ ENGAGEMENT_EMAIL = os.getenv("ENGAGEMENT_EMAIL", "engagement@bpm.ba")
7
+ GOVERNANCE_EMAIL = os.getenv("GOVERNANCE_EMAIL", "governance@bpm.ba")
8
+
9
+ # Optional gating (comma-separated)
10
+ ACCESS_CODES_RAW = os.getenv("FINC2E_ACCESS_CODES", "").strip()
11
+ ACCESS_CODES = {c.strip() for c in ACCESS_CODES_RAW.split(",") if c.strip()}
12
+
13
+ TITLE = "FinC2E — Governance Gateway"
14
+ TAGLINE = "Structured, audit-oriented reasoning for regulated decision support (preview)."
15
+
16
+ DISCLAIMER = (
17
+ "FinC2E outputs are informational and non-executive. "
18
+ "No legal advice, financial advice, trading signals, or decision execution is provided. "
19
+ "Users remain responsible for compliance, policies, and final decisions."
20
+ )
21
+
22
+ RISK_DOMAINS = [
23
+ "AML / CFT",
24
+ "KYC / CDD",
25
+ "Sanctions screening",
26
+ "Fraud / transaction anomaly",
27
+ "Market conduct / suitability",
28
+ "Operational risk",
29
+ "Model risk / AI governance",
30
+ "Data privacy / security",
31
+ "Other (specify)"
32
+ ]
33
+
34
+ def normalize(s: str) -> str:
35
+ return (s or "").strip()
36
+
37
+ def valid_access(code: str) -> bool:
38
+ if not ACCESS_CODES:
39
+ return True # no gate
40
+ return normalize(code) in ACCESS_CODES
41
+
42
+ def ensure_min_len(field: str, n: int, label: str):
43
+ if len(normalize(field)) < n:
44
+ return f"[Missing/too short] {label} (min {n} chars)"
45
+ return None
46
+
47
+ def finc2e_reason(case_id, jurisdiction, domain, other_domain, objective, facts, constraints, decision_stage, access_code):
48
+ # Gate
49
+ if not valid_access(access_code):
50
+ return (
51
+ "ACCESS DENIED\n"
52
+ "------------\n"
53
+ "This preview requires a valid FinC2E access code.\n"
54
+ f"Request access: {ENGAGEMENT_EMAIL}\n"
55
+ )
56
+
57
+ domain_final = other_domain if domain == "Other (specify)" else domain
58
+ domain_final = normalize(domain_final) or domain
59
+
60
+ # Basic validation
61
+ issues = []
62
+ for (val, n, label) in [
63
+ (objective, 25, "Objective"),
64
+ (facts, 40, "Case Facts / Data"),
65
+ (constraints, 20, "Constraints / Policies"),
66
+ (jurisdiction, 2, "Jurisdiction"),
67
+ ]:
68
+ m = ensure_min_len(val, n, label)
69
+ if m:
70
+ issues.append(m)
71
+
72
+ if issues:
73
+ return "INPUT QUALITY WARNING\n---------------------\n" + "\n".join(f"- {i}" for i in issues) + "\n\nProvide clearer inputs for higher analytical value."
74
+
75
+ # Produce a structured, audit-oriented "preview" (still non-executive)
76
+ out = f"""
77
+ FINC2E GOVERNANCE OUTPUT (PREVIEW) — NON-EXECUTIVE
78
+ =================================================
79
+
80
+ Case Header
81
+ -----------
82
+ - Case ID: {normalize(case_id) or "N/A"}
83
+ - Jurisdiction: {normalize(jurisdiction)}
84
+ - Domain: {domain_final}
85
+ - Decision Stage: {normalize(decision_stage) or "N/A"}
86
+
87
+ 1) Decision Objective (What must be decided?)
88
+ --------------------------------------------
89
+ {normalize(objective)}
90
+
91
+ 2) Known Facts / Inputs (What is observed?)
92
+ ------------------------------------------
93
+ {normalize(facts)}
94
+
95
+ 3) Constraints & Policies (What must be respected?)
96
+ --------------------------------------------------
97
+ {normalize(constraints)}
98
+
99
+ 4) Risk Framing (What can go wrong?)
100
+ -----------------------------------
101
+ - Primary risk vectors (domain-dependent)
102
+ - Irreversibility / tail risk considerations
103
+ - Compliance exposure and evidentiary needs
104
+
105
+ 5) Minimum Evidence Checklist (Audit-oriented)
106
+ ---------------------------------------------
107
+ - Identity / entity attributes sufficient for the domain
108
+ - Source of funds / source of wealth (if relevant)
109
+ - Transaction narrative coherence (if relevant)
110
+ - Sanctions/PEP screening artifacts (if relevant)
111
+ - Data provenance + timestamps + responsible reviewer
112
+
113
+ 6) Structured Questions (What must be clarified next?)
114
+ -----------------------------------------------------
115
+ - What assumptions are being made due to missing data?
116
+ - Which policy threshold(s) apply in this jurisdiction?
117
+ - What would change the classification outcome?
118
+ - What is the acceptable false-positive / false-negative posture?
119
+
120
+ 7) Suggested Review Path (Human-in-the-loop)
121
+ -------------------------------------------
122
+ - Step A: Normalize inputs and verify provenance
123
+ - Step B: Apply policy thresholds to classify risk band
124
+ - Step C: Collect missing evidence (if required)
125
+ - Step D: Document rationale and reviewer accountability
126
+ - Step E: Escalate if any high-severity triggers are present
127
+
128
+ Governance Notes
129
+ ----------------
130
+ - Output is informational; no decision is executed.
131
+ - For production pilots: policy binding, audit logs, and access controls are mandatory.
132
+
133
+ DISCLAIMER
134
+ ----------
135
+ {DISCLAIMER}
136
+
137
+ © 2026 BPM RED Academy — All rights reserved.
138
+ """
139
+ return textwrap.dedent(out).strip()
140
+
141
+ with gr.Blocks(theme=gr.themes.Soft(), title=TITLE) as demo:
142
+ gr.Markdown(
143
+ f"""
144
+ # {TITLE}
145
+ **{TAGLINE}**
146
+
147
+ ⚠️ **Preview only.** {DISCLAIMER}
148
+
149
+ ---
150
+ """
151
+ )
152
+
153
+ # Optional gate UI
154
+ if ACCESS_CODES:
155
+ access_code = gr.Textbox(label="FinC2E Access Code (Licensed)", placeholder="e.g., FINC2E-CLIENT-001")
156
+ else:
157
+ access_code = gr.Textbox(label="FinC2E Access Code (optional)", placeholder="(optional)")
158
+
159
+ with gr.Row():
160
+ case_id = gr.Textbox(label="Case ID (optional)", placeholder="e.g., CASE-2026-0007")
161
+ jurisdiction = gr.Textbox(label="Jurisdiction", placeholder="e.g., EU / UK / BiH / UAE")
162
+
163
+ with gr.Row():
164
+ domain = gr.Dropdown(label="Domain", choices=RISK_DOMAINS, value="AML / CFT")
165
+ other_domain = gr.Textbox(label="If Other, specify domain", placeholder="e.g., Payments monitoring, UBO risk, etc.")
166
+
167
+ decision_stage = gr.Textbox(label="Decision stage (optional)", placeholder="e.g., onboarding / ongoing monitoring / escalation review")
168
+
169
+ objective = gr.Textbox(
170
+ label="Decision objective",
171
+ lines=2,
172
+ placeholder="Describe the decision to be supported (not executed). Example: classify risk level and define what evidence is required for review."
173
+ )
174
+
175
+ facts = gr.Textbox(
176
+ label="Case facts / inputs",
177
+ lines=6,
178
+ placeholder="Provide relevant facts only (no unnecessary personal data). Include amounts, timelines, entity types, triggers, and what is already verified."
179
+ )
180
+
181
+ constraints = gr.Textbox(
182
+ label="Constraints / policies",
183
+ lines=4,
184
+ placeholder="List policy constraints: thresholds, prohibited conditions, escalation triggers, documentation requirements, and any jurisdictional rules you must respect."
185
+ )
186
+
187
+ run = gr.Button("Generate Governance Output (Preview)", variant="primary")
188
+ output = gr.Textbox(label="FinC2E Output (Non-Executive)", lines=20)
189
+
190
+ run.click(
191
+ finc2e_reason,
192
+ inputs=[case_id, jurisdiction, domain, other_domain, objective, facts, constraints, decision_stage, access_code],
193
+ outputs=[output]
194
+ )
195
+
196
+ gr.Markdown(
197
+ f"""
198
+ ---
199
+ ### Request access / enterprise pilots
200
+ - Engagement: **{ENGAGEMENT_EMAIL}**
201
+ - Governance: **{GOVERNANCE_EMAIL}**
202
+
203
+ © 2026 BPM RED Academy — All rights reserved.
204
+ """
205
+ )
206
+
207
+ if __name__ == "__main__":
208
+ demo.launch()