Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,62 +1,196 @@
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import textwrap
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
#
|
| 5 |
-
#
|
| 6 |
-
#
|
| 7 |
-
# ----------------------------
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 21 |
-
-
|
| 22 |
-
-
|
| 23 |
-
-
|
|
|
|
|
|
|
| 24 |
"""
|
| 25 |
).strip()
|
| 26 |
|
| 27 |
response = f"""
|
| 28 |
-
PFI ANALYSIS (PREVIEW)
|
| 29 |
|
| 30 |
Question (received):
|
| 31 |
-
- {
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
Cognitive Decomposition:
|
| 39 |
-
- Primary variables
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
"""
|
| 53 |
|
| 54 |
return textwrap.dedent(response).strip()
|
| 55 |
|
| 56 |
|
| 57 |
-
#
|
| 58 |
-
#
|
| 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
|
| 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 |
-
"
|
| 77 |
-
"Ambiguity reduces output quality."
|
| 78 |
),
|
| 79 |
lines=4,
|
| 80 |
)
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
output_box = gr.Textbox(
|
| 85 |
label="PFI Output (Exploratory · Non-Executable)",
|
| 86 |
-
lines=10,
|
| 87 |
)
|
| 88 |
|
| 89 |
-
|
| 90 |
-
fn=
|
| 91 |
-
inputs=question_input,
|
| 92 |
-
outputs=output_box,
|
| 93 |
)
|
| 94 |
|
| 95 |
-
# (B) Locked section
|
| 96 |
gr.Markdown(
|
| 97 |
-
"""
|
| 98 |
---
|
| 99 |
-
🔒 **Deeper Structural Reasoning —
|
| 100 |
|
| 101 |
-
Personalized constraints, scenario
|
| 102 |
are available only under **PFI Licensed Access**.
|
| 103 |
|
| 104 |
-
👉
|
| 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.
|