File size: 7,074 Bytes
ad971fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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()