bpmredacademy commited on
Commit
e2aa97d
·
verified ·
1 Parent(s): 8983830

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -55
app.py CHANGED
@@ -1,62 +1,196 @@
 
 
1
  import gradio as gr
2
  import textwrap
 
 
3
 
4
- # ----------------------------
5
- # Core PFI reasoning stub
6
- # (preview-only, non-executive)
7
- # ----------------------------
8
 
9
- MIN_CHARS = 20
 
 
 
 
 
10
 
11
- def pfi_reasoning(question: str) -> str:
12
- if not question or len(question.strip()) < MIN_CHARS:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  return textwrap.dedent(
14
- """
15
  [Input rejected]
16
 
17
- PFI requires a precise, high-density financial question.
18
  Ambiguous or underspecified inputs reduce analytical value.
19
 
20
- Tip:
21
- - Include horizon (e.g., 3–7 years)
22
- - Include constraints (liquidity, taxes, drawdown limits)
23
- - Include context (income stability, concentration risk, jurisdiction)
 
 
24
  """
25
  ).strip()
26
 
27
  response = f"""
28
- PFI ANALYSIS (PREVIEW)
29
 
30
  Question (received):
31
- - {question.strip()}
32
 
33
- Question Structure:
34
- - Topic domain identified
35
- - Scope appears conceptual, not executable
36
- - Time horizon and constraints are partially defined
 
 
37
 
38
  Cognitive Decomposition:
39
- - Primary variables detected
40
- - Secondary dependencies inferred
41
- - Risk vectors identified at structural level
42
-
43
- Reasoning Notes:
44
- - This output is exploratory and non-executive
45
- - No prediction, advice, or action is implied
46
- - Further precision would increase analytical depth
47
-
48
- Next Step (Optional):
49
- - Narrow the scope
50
- - Specify constraints
51
- - Clarify the decision context
 
 
 
 
 
 
 
52
  """
53
 
54
  return textwrap.dedent(response).strip()
55
 
56
 
57
- # ----------------------------
58
- # Gradio UI
59
- # ----------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
62
  gr.Markdown(
@@ -64,50 +198,54 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
64
  # 🧠 Personal Financial Intelligence (PFI)
65
  **High-density financial reasoning · Research Preview**
66
 
67
- ⚠️ *PFI is a research preview system.
68
- It does NOT provide financial advice or execute decisions.*
69
  """
70
  )
71
 
 
 
 
 
 
 
 
 
 
 
72
  with gr.Row():
73
  question_input = gr.Textbox(
74
  label="Your Question",
75
  placeholder=(
76
- "Formulate one precise, high-impact financial question.\n"
77
- "Ambiguity reduces output quality."
78
  ),
79
  lines=4,
80
  )
81
 
82
- analyze_button = gr.Button("Request Structural Analysis (Preview)")
83
 
84
  output_box = gr.Textbox(
85
  label="PFI Output (Exploratory · Non-Executable)",
86
- lines=10, # (C) premium feel: tighter box
87
  )
88
 
89
- analyze_button.click(
90
- fn=pfi_reasoning,
91
- inputs=question_input,
92
- outputs=output_box,
93
  )
94
 
95
- # (B) Locked section
96
  gr.Markdown(
97
- """
98
  ---
99
- 🔒 **Deeper Structural Reasoning — Locked**
100
 
101
- Personalized constraints, scenario trade-offs, and capital structure reasoning
102
  are available only under **PFI Licensed Access**.
103
 
104
- 👉 *Request access:* **pfi@bpmred.academy**
105
- """
106
- )
107
 
108
- # Footer disclaimer
109
- gr.Markdown(
110
- """
111
  ---
112
  ### Disclaimer
113
  PFI outputs are exploratory and informational only.
 
1
+ import os
2
+ import time
3
  import gradio as gr
4
  import textwrap
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Tuple
7
 
8
+ # ============================================================
9
+ # PFI Elite Access Control (per-user access codes)
10
+ # ============================================================
 
11
 
12
+ # Set in Hugging Face Space -> Settings -> Variables and secrets
13
+ # Example:
14
+ # PFI_ACCESS_CODES = "PFI-EDIN-001,PFI-CLIENT-002,PFI-CLIENT-003"
15
+ # PFI_DAILY_QUOTA = "3"
16
+ # PFI_BRAND_CONTACT = "pfi@bpmred.academy"
17
+ # PFI_MIN_CHARS = "40"
18
 
19
+ ACCESS_CODES_RAW = os.getenv("PFI_ACCESS_CODES", "").strip()
20
+ DAILY_QUOTA = int(os.getenv("PFI_DAILY_QUOTA", "3").strip() or "3")
21
+ BRAND_CONTACT = os.getenv("PFI_BRAND_CONTACT", "pfi@bpmred.academy").strip()
22
+ MIN_CHARS = int(os.getenv("PFI_MIN_CHARS", "40").strip() or "40")
23
+
24
+ # Normalized set of valid codes
25
+ VALID_CODES = {c.strip() for c in ACCESS_CODES_RAW.split(",") if c.strip()}
26
+
27
+ # 24h window (seconds)
28
+ WINDOW_SECONDS = 24 * 60 * 60
29
+
30
+
31
+ @dataclass
32
+ class UsageEntry:
33
+ # timestamps of successful requests
34
+ ts: list = field(default_factory=list)
35
+
36
+
37
+ @dataclass
38
+ class AccessState:
39
+ # code -> usage
40
+ usage: Dict[str, UsageEntry] = field(default_factory=dict)
41
+ # tiny audit log (session-local)
42
+ audit: list = field(default_factory=list)
43
+
44
+
45
+ def _now() -> float:
46
+ return time.time()
47
+
48
+
49
+ def _clean_old(ts_list: list, now: float) -> list:
50
+ """Keep only timestamps inside rolling 24h window."""
51
+ cutoff = now - WINDOW_SECONDS
52
+ return [t for t in ts_list if t >= cutoff]
53
+
54
+
55
+ def validate_code(code: str) -> Tuple[bool, str]:
56
+ code = (code or "").strip()
57
+ if not code:
58
+ return False, "Access Code is required."
59
+ if not VALID_CODES:
60
+ # If admin forgot to set PFI_ACCESS_CODES, fail closed (safest)
61
+ return False, "PFI is not configured for access codes yet. Please contact support."
62
+ if code not in VALID_CODES:
63
+ return False, "Invalid Access Code."
64
+ return True, "Access granted."
65
+
66
+
67
+ # ============================================================
68
+ # Core PFI reasoning stub (preview-only, non-executive)
69
+ # Replace this later with your real model/API call.
70
+ # ============================================================
71
+
72
+ def pfi_reasoning_preview(question: str) -> str:
73
+ q = (question or "").strip()
74
+ if len(q) < MIN_CHARS:
75
  return textwrap.dedent(
76
+ f"""
77
  [Input rejected]
78
 
79
+ PFI requires a precise, high-density financial question (min {MIN_CHARS} characters).
80
  Ambiguous or underspecified inputs reduce analytical value.
81
 
82
+ Use this template:
83
+ - Objective:
84
+ - Horizon:
85
+ - Constraints (max drawdown / liquidity / taxes / jurisdiction):
86
+ - Current exposures (concentration risk):
87
+ - What "irreversible downside" means to you:
88
  """
89
  ).strip()
90
 
91
  response = f"""
92
+ PFI STRUCTURAL ANALYSIS (PREVIEW · NON-EXECUTABLE)
93
 
94
  Question (received):
95
+ - {q}
96
 
97
+ Structural Map:
98
+ 1) Decision domain: capital allocation / risk architecture
99
+ 2) Time structure: near-term liquidity vs long-horizon convexity
100
+ 3) Constraint set: drawdown tolerance, cash-flow needs, optionality preservation
101
+ 4) Failure modes: forced selling, duration mismatch, correlation spikes, policy shock
102
+ 5) Control levers: rebalancing rules, hedges, reserve sizing, exposure caps
103
 
104
  Cognitive Decomposition:
105
+ - Primary variables:
106
+ income stability / career risk
107
+ liquidity needs and timing
108
+ • inflation/regime risk
109
+ concentration risk (single asset / geography / employer)
110
+ tax / jurisdiction constraints
111
+ - Secondary dependencies:
112
+ correlation behavior under stress
113
+ • funding liquidity vs market liquidity
114
+ refinancing risk / rate sensitivity
115
+ - Irreversible downside candidates:
116
+ ruin risk (capital impairment that changes future opportunity set)
117
+ forced liquidation triggers
118
+ • leverage or short-vol exposure under volatility expansion
119
+
120
+ Next Questions (to deepen analysis):
121
+ - Define max acceptable drawdown and time-to-recover.
122
+ - Specify liquidity schedule (must-pay obligations by month/quarter).
123
+ - List top 3 concentrated exposures and whether they can be reduced.
124
+ - Clarify jurisdiction + tax constraints (capital gains, withholding).
125
  """
126
 
127
  return textwrap.dedent(response).strip()
128
 
129
 
130
+ # ============================================================
131
+ # Gated handler (enforces per-code quota)
132
+ # ============================================================
133
+
134
+ def gated_request(access_code: str, question: str, state: AccessState) -> Tuple[str, AccessState]:
135
+ state = state or AccessState()
136
+ now = _now()
137
+
138
+ ok, msg = validate_code(access_code)
139
+ code = (access_code or "").strip()
140
+
141
+ # audit
142
+ state.audit.append((int(now), "validate", code, ok))
143
+
144
+ if not ok:
145
+ locked = f"""
146
+ 🔒 **PFI Licensed Access Required**
147
+
148
+ Reason: **{msg}**
149
+
150
+ To request a licensed Access Code, contact: **{BRAND_CONTACT}**
151
+ """
152
+ return textwrap.dedent(locked).strip(), state
153
+
154
+ # init usage
155
+ if code not in state.usage:
156
+ state.usage[code] = UsageEntry(ts=[])
157
+
158
+ # rolling window cleanup
159
+ state.usage[code].ts = _clean_old(state.usage[code].ts, now)
160
+ used = len(state.usage[code].ts)
161
+
162
+ if used >= DAILY_QUOTA:
163
+ remaining_time = int((state.usage[code].ts[0] + WINDOW_SECONDS) - now)
164
+ hours = max(0, remaining_time // 3600)
165
+ minutes = max(0, (remaining_time % 3600) // 60)
166
+
167
+ quota_msg = f"""
168
+ ⛔ **Daily quota reached** for this Access Code.
169
+
170
+ - Quota: **{DAILY_QUOTA} requests / 24h**
171
+ - Next reset in: **~{hours}h {minutes}m**
172
+
173
+ If you need expanded access, contact: **{BRAND_CONTACT}**
174
+ """
175
+ state.audit.append((int(now), "quota_block", code, used))
176
+ return textwrap.dedent(quota_msg).strip(), state
177
+
178
+ # Consume 1 quota
179
+ state.usage[code].ts.append(now)
180
+ state.audit.append((int(now), "quota_consume", code, used + 1))
181
+
182
+ # Run preview reasoning
183
+ out = pfi_reasoning_preview(question)
184
+
185
+ # Add header with usage
186
+ used_after = len(state.usage[code].ts)
187
+ header = f"✅ Access Code: {code} | Usage: {used_after}/{DAILY_QUOTA} (rolling 24h)\n\n"
188
+ return header + out, state
189
+
190
+
191
+ # ============================================================
192
+ # UI
193
+ # ============================================================
194
 
195
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
196
  gr.Markdown(
 
198
  # 🧠 Personal Financial Intelligence (PFI)
199
  **High-density financial reasoning · Research Preview**
200
 
201
+ ⚠️ *PFI is a licensed research interface.
202
+ It does NOT provide financial advice, trading signals, or execute decisions.*
203
  """
204
  )
205
 
206
+ # Session state (in-memory per user session)
207
+ state = gr.State(AccessState())
208
+
209
+ with gr.Row():
210
+ access_code = gr.Textbox(
211
+ label="PFI Access Code (Licensed)",
212
+ placeholder="e.g., PFI-CLIENT-002",
213
+ type="password",
214
+ )
215
+
216
  with gr.Row():
217
  question_input = gr.Textbox(
218
  label="Your Question",
219
  placeholder=(
220
+ "Write ONE precise, high-impact financial question.\n"
221
+ "Include horizon, constraints, and context. Ambiguity reduces output quality."
222
  ),
223
  lines=4,
224
  )
225
 
226
+ btn = gr.Button("Request Structural Analysis (Preview)")
227
 
228
  output_box = gr.Textbox(
229
  label="PFI Output (Exploratory · Non-Executable)",
230
+ lines=10,
231
  )
232
 
233
+ btn.click(
234
+ fn=gated_request,
235
+ inputs=[access_code, question_input, state],
236
+ outputs=[output_box, state],
237
  )
238
 
 
239
  gr.Markdown(
240
+ f"""
241
  ---
242
+ 🔒 **Deeper Structural Reasoning — Licensed Layer**
243
 
244
+ Personalized constraints, scenario stress tests, and capital structure reasoning
245
  are available only under **PFI Licensed Access**.
246
 
247
+ 👉 Request access: **{BRAND_CONTACT}**
 
 
248
 
 
 
 
249
  ---
250
  ### Disclaimer
251
  PFI outputs are exploratory and informational only.