Update app.py
Browse files
app.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
"grit": 0.18, "risk": 0.12, "time_per_week": 0.14, "runway_months": 0.16,
|
| 8 |
-
"customer_access": 0.12, "validation_skill": 0.10, "ops_skill": 0.08, "finance_skill": 0.10
|
| 9 |
-
}
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
PATHS = {
|
| 12 |
"tech": [
|
| 13 |
{"path":"Micro-SaaS","difficulty":3,"benefits":"High margins, scalable, global reach",
|
|
@@ -55,56 +57,116 @@ PATHS = {
|
|
| 55 |
]
|
| 56 |
}
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
# ------------------------------
|
| 64 |
-
# Readiness scoring (0–100)
|
| 65 |
# ------------------------------
|
| 66 |
def compute_readiness(payload):
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
RUBRIC_WEIGHTS["ops_skill"]*ops_skill +
|
| 85 |
-
RUBRIC_WEIGHTS["finance_skill"]*finance_skill
|
| 86 |
-
)
|
| 87 |
-
score = int(round(score))
|
| 88 |
tier = "Starter" if score < 55 else ("Building" if score < 75 else "Launch-ready")
|
| 89 |
|
| 90 |
tips = []
|
| 91 |
-
|
| 92 |
-
if
|
| 93 |
-
|
| 94 |
-
if
|
| 95 |
-
|
| 96 |
-
if
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
# ------------------------------
|
| 100 |
-
# Path suggestions helper
|
| 101 |
# ------------------------------
|
| 102 |
def suggest_paths(interests, experience):
|
| 103 |
results = []
|
| 104 |
for domain in interests or []:
|
| 105 |
for item in PATHS.get(domain, []):
|
| 106 |
diff = item["difficulty"]
|
| 107 |
-
# adjust for experience
|
| 108 |
if experience == "beginner": diff = min(5, diff + 1)
|
| 109 |
if experience == "advanced": diff = max(1, diff - 1)
|
| 110 |
results.append({
|
|
@@ -115,7 +177,6 @@ def suggest_paths(interests, experience):
|
|
| 115 |
"key_skills": item["skills"],
|
| 116 |
"starter_plan": item["starter"],
|
| 117 |
})
|
| 118 |
-
# keep it short
|
| 119 |
return results[:6]
|
| 120 |
|
| 121 |
# ------------------------------
|
|
@@ -124,16 +185,15 @@ def suggest_paths(interests, experience):
|
|
| 124 |
def local_reply(user_msg, history, assessment_state, path_state):
|
| 125 |
text = (user_msg or "").lower()
|
| 126 |
|
| 127 |
-
# reference readiness
|
| 128 |
if "score" in text or "ready" in text or "readiness" in text:
|
| 129 |
if isinstance(assessment_state, dict) and "score" in assessment_state:
|
| 130 |
s = assessment_state["score"]
|
| 131 |
t = assessment_state.get("tier", "?")
|
| 132 |
-
|
|
|
|
| 133 |
else:
|
| 134 |
return "Run **Assess readiness** on the right, then ask me again 🙂"
|
| 135 |
|
| 136 |
-
# reference paths
|
| 137 |
if "path" in text or "idea" in text or "domain" in text:
|
| 138 |
if isinstance(path_state, dict) and path_state.get("suggestions"):
|
| 139 |
sug = path_state["suggestions"][0]
|
|
@@ -144,7 +204,6 @@ def local_reply(user_msg, history, assessment_state, path_state):
|
|
| 144 |
else:
|
| 145 |
return "Pick a few interests under **Path Explorer**, hit **Suggest paths**, then ask me which to start with."
|
| 146 |
|
| 147 |
-
# detect domain mention
|
| 148 |
for domain in PATHS.keys():
|
| 149 |
if domain in text:
|
| 150 |
first = PATHS[domain][0]
|
|
@@ -152,7 +211,6 @@ def local_reply(user_msg, history, assessment_state, path_state):
|
|
| 152 |
f"(difficulty {first['difficulty']}/5). "
|
| 153 |
f"Why it can pay off: {first['benefits']}. "
|
| 154 |
f"Starter plan: {', '.join(first['starter'][:3])}.")
|
| 155 |
-
# default helpful tip
|
| 156 |
return ("Start with a small **demand test**: a landing page and 5–10 user interviews. "
|
| 157 |
"Measure real intent (signups/preorders) before you build.")
|
| 158 |
|
|
@@ -160,7 +218,7 @@ def local_reply(user_msg, history, assessment_state, path_state):
|
|
| 160 |
# Gradio UI
|
| 161 |
# ------------------------------
|
| 162 |
with gr.Blocks(theme="soft", fill_height=True) as demo:
|
| 163 |
-
gr.Markdown("## Entrepreneurial Tutor
|
| 164 |
|
| 165 |
with gr.Row():
|
| 166 |
# Chat
|
|
@@ -172,21 +230,32 @@ with gr.Blocks(theme="soft", fill_height=True) as demo:
|
|
| 172 |
|
| 173 |
# Tools
|
| 174 |
with gr.Column(scale=2):
|
| 175 |
-
gr.Markdown("### Quick Readiness Assessment")
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
assess_btn = gr.Button("Assess readiness")
|
| 186 |
assessment_state = gr.State({})
|
| 187 |
assess_out = gr.JSON(label="Assessment Result")
|
| 188 |
|
| 189 |
-
gr.Markdown("### Path Explorer")
|
| 190 |
interests = gr.CheckboxGroup(
|
| 191 |
choices=list(PATHS.keys()),
|
| 192 |
value=["tech"],
|
|
@@ -202,18 +271,22 @@ with gr.Blocks(theme="soft", fill_height=True) as demo:
|
|
| 202 |
paths_out = gr.JSON(label="Suggested Paths")
|
| 203 |
|
| 204 |
# Wiring
|
| 205 |
-
def do_assess(
|
|
|
|
|
|
|
| 206 |
payload = {
|
| 207 |
-
"
|
| 208 |
-
"
|
| 209 |
-
"
|
| 210 |
}
|
| 211 |
result = compute_readiness(payload)
|
| 212 |
return result, result # show + store
|
| 213 |
|
| 214 |
assess_btn.click(
|
| 215 |
do_assess,
|
| 216 |
-
inputs=[
|
|
|
|
|
|
|
| 217 |
outputs=[assess_out, assessment_state]
|
| 218 |
)
|
| 219 |
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
import gradio as gr
|
| 3 |
+
import json
|
| 4 |
|
| 5 |
+
SYSTEM_PROMPT = (
|
| 6 |
+
"You are a friendly tutor for Entrepreneurial Readiness and Paths. "
|
| 7 |
+
"Be practical and concise. If the user ran the assessment or path explorer, use that info."
|
| 8 |
+
)
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
# =========================
|
| 11 |
+
# Domains (unchanged)
|
| 12 |
+
# =========================
|
| 13 |
PATHS = {
|
| 14 |
"tech": [
|
| 15 |
{"path":"Micro-SaaS","difficulty":3,"benefits":"High margins, scalable, global reach",
|
|
|
|
| 57 |
]
|
| 58 |
}
|
| 59 |
|
| 60 |
+
# =========================
|
| 61 |
+
# Readiness factors (your list)
|
| 62 |
+
# =========================
|
| 63 |
+
WEIGHTS = {
|
| 64 |
+
"savings": 0.10,
|
| 65 |
+
"income": 0.08,
|
| 66 |
+
"bills": 0.08, # inverse
|
| 67 |
+
"entertainment": 0.04, # inverse
|
| 68 |
+
"assets": 0.06,
|
| 69 |
+
"sales_skills": 0.14,
|
| 70 |
+
"confidence": 0.10,
|
| 71 |
+
"dependents": 0.06, # inverse
|
| 72 |
+
"age": 0.04, # very small, symmetric
|
| 73 |
+
"idea_level": 0.10,
|
| 74 |
+
"entrepreneurial_experience": 0.10,
|
| 75 |
+
"risk": 0.10,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
CAP = {
|
| 79 |
+
"savings": 20000, # USD
|
| 80 |
+
"income": 8000, # USD / month
|
| 81 |
+
"bills": 5000, # USD / month (inverse)
|
| 82 |
+
"entertainment": 1000, # USD / month (inverse)
|
| 83 |
+
"assets": 100000, # USD (liquid-ish)
|
| 84 |
+
"dependents_max": 3, # 0..3+ (inverse)
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
IDEA_LEVEL_MAP = {
|
| 88 |
+
"none": 0.0,
|
| 89 |
+
"exploring": 0.25,
|
| 90 |
+
"validated_problem": 0.5,
|
| 91 |
+
"prototype": 0.75,
|
| 92 |
+
"mvp_with_users": 1.0,
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
def _nz(x, default=0.0):
|
| 96 |
+
try:
|
| 97 |
+
return float(x) if x is not None else float(default)
|
| 98 |
+
except Exception:
|
| 99 |
+
return float(default)
|
| 100 |
+
|
| 101 |
+
def _clamp01(v): return max(0.0, min(1.0, float(v)))
|
| 102 |
+
def _direct_norm(v, cap): return _clamp01(_nz(v) / float(cap))
|
| 103 |
+
def _inverse_norm(v, cap): return 1.0 - _direct_norm(v, cap)
|
| 104 |
+
|
| 105 |
+
def _age_norm(age):
|
| 106 |
+
"""Mild symmetric curve centered ~35; tiny effect (≤0.2)."""
|
| 107 |
+
a = _nz(age, 35)
|
| 108 |
+
penalty = min(abs(a - 35.0) / 35.0, 1.0) * 0.2
|
| 109 |
+
return _clamp01(1.0 - penalty)
|
| 110 |
+
|
| 111 |
+
def _dependents_norm(dep):
|
| 112 |
+
d = max(0, int(_nz(dep, 0)))
|
| 113 |
+
base = 1.0 - min(d / float(CAP["dependents_max"]), 1.0)
|
| 114 |
+
return _clamp01(base)
|
| 115 |
|
| 116 |
# ------------------------------
|
| 117 |
+
# Readiness scoring (0–100) using your factors
|
| 118 |
# ------------------------------
|
| 119 |
def compute_readiness(payload):
|
| 120 |
+
n = {
|
| 121 |
+
"savings": _direct_norm(payload["savings"], CAP["savings"]),
|
| 122 |
+
"income": _direct_norm(payload["income"], CAP["income"]),
|
| 123 |
+
"bills": _inverse_norm(payload["bills"], CAP["bills"]),
|
| 124 |
+
"entertainment": _inverse_norm(payload["entertainment"], CAP["entertainment"]),
|
| 125 |
+
"assets": _direct_norm(payload["assets"], CAP["assets"]),
|
| 126 |
+
"sales_skills": _clamp01(_nz(payload["sales_skills"]) / 10.0),
|
| 127 |
+
"confidence": _clamp01(_nz(payload["confidence"]) / 10.0),
|
| 128 |
+
"dependents": _dependents_norm(payload["dependents"]),
|
| 129 |
+
"age": _age_norm(payload["age"]),
|
| 130 |
+
"idea_level": IDEA_LEVEL_MAP.get(payload["idea_level"], 0.0),
|
| 131 |
+
"entrepreneurial_experience": _clamp01(_nz(payload["entrepreneurial_experience"]) / 10.0),
|
| 132 |
+
"risk": _clamp01(_nz(payload["risk"]) / 10.0),
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
total = sum(WEIGHTS[k] * n[k] for k in WEIGHTS.keys())
|
| 136 |
+
score = int(round(100 * total))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
tier = "Starter" if score < 55 else ("Building" if score < 75 else "Launch-ready")
|
| 138 |
|
| 139 |
tips = []
|
| 140 |
+
# Finance levers
|
| 141 |
+
if n["savings"] < 0.5 or n["assets"] < 0.4:
|
| 142 |
+
tips.append("Run a 60–90 day runway sprint: reduce burn and build a buffer.")
|
| 143 |
+
if n["bills"] < 0.6 or n["entertainment"] < 0.6:
|
| 144 |
+
tips.append("Do a weekly expense audit and a 30-day no-spend on non-essentials.")
|
| 145 |
+
if n["income"] < 0.5:
|
| 146 |
+
tips.append("Spin up a simple service for cash: 3 outreach/day → 2 pilot clients.")
|
| 147 |
+
# Skill/mindset levers
|
| 148 |
+
if n["sales_skills"] < 0.6:
|
| 149 |
+
tips.append("Do 20 cold DMs/day for 5 days; refine your offer script.")
|
| 150 |
+
if n["confidence"] < 0.6:
|
| 151 |
+
tips.append("Post build-in-public for 10 days to desensitize and attract allies.")
|
| 152 |
+
if n["entrepreneurial_experience"] < 0.5:
|
| 153 |
+
tips.append("Run a 2-week micro-project: interviews → small paid pilot.")
|
| 154 |
+
if n["idea_level"] < 0.5:
|
| 155 |
+
tips.append("Advance one notch: exploring → validated problem → prototype → MVP.")
|
| 156 |
+
# Risk
|
| 157 |
+
if n["risk"] < 0.5:
|
| 158 |
+
tips.append("Set a ‘risk budget’: small, reversible tests with clear stop-lines.")
|
| 159 |
+
|
| 160 |
+
return {"score": score, "tier": tier, "tips": tips[:5], "normalized": n}
|
| 161 |
|
| 162 |
# ------------------------------
|
| 163 |
+
# Path suggestions helper (unchanged)
|
| 164 |
# ------------------------------
|
| 165 |
def suggest_paths(interests, experience):
|
| 166 |
results = []
|
| 167 |
for domain in interests or []:
|
| 168 |
for item in PATHS.get(domain, []):
|
| 169 |
diff = item["difficulty"]
|
|
|
|
| 170 |
if experience == "beginner": diff = min(5, diff + 1)
|
| 171 |
if experience == "advanced": diff = max(1, diff - 1)
|
| 172 |
results.append({
|
|
|
|
| 177 |
"key_skills": item["skills"],
|
| 178 |
"starter_plan": item["starter"],
|
| 179 |
})
|
|
|
|
| 180 |
return results[:6]
|
| 181 |
|
| 182 |
# ------------------------------
|
|
|
|
| 185 |
def local_reply(user_msg, history, assessment_state, path_state):
|
| 186 |
text = (user_msg or "").lower()
|
| 187 |
|
|
|
|
| 188 |
if "score" in text or "ready" in text or "readiness" in text:
|
| 189 |
if isinstance(assessment_state, dict) and "score" in assessment_state:
|
| 190 |
s = assessment_state["score"]
|
| 191 |
t = assessment_state.get("tier", "?")
|
| 192 |
+
tip = (assessment_state.get("tips") or ["Run small demand tests first."])[0]
|
| 193 |
+
return f"Your readiness is **{s}/100 ({t})**. Top tip: {tip}"
|
| 194 |
else:
|
| 195 |
return "Run **Assess readiness** on the right, then ask me again 🙂"
|
| 196 |
|
|
|
|
| 197 |
if "path" in text or "idea" in text or "domain" in text:
|
| 198 |
if isinstance(path_state, dict) and path_state.get("suggestions"):
|
| 199 |
sug = path_state["suggestions"][0]
|
|
|
|
| 204 |
else:
|
| 205 |
return "Pick a few interests under **Path Explorer**, hit **Suggest paths**, then ask me which to start with."
|
| 206 |
|
|
|
|
| 207 |
for domain in PATHS.keys():
|
| 208 |
if domain in text:
|
| 209 |
first = PATHS[domain][0]
|
|
|
|
| 211 |
f"(difficulty {first['difficulty']}/5). "
|
| 212 |
f"Why it can pay off: {first['benefits']}. "
|
| 213 |
f"Starter plan: {', '.join(first['starter'][:3])}.")
|
|
|
|
| 214 |
return ("Start with a small **demand test**: a landing page and 5–10 user interviews. "
|
| 215 |
"Measure real intent (signups/preorders) before you build.")
|
| 216 |
|
|
|
|
| 218 |
# Gradio UI
|
| 219 |
# ------------------------------
|
| 220 |
with gr.Blocks(theme="soft", fill_height=True) as demo:
|
| 221 |
+
gr.Markdown("## Entrepreneurial Tutor (domains kept, assessment replaced)")
|
| 222 |
|
| 223 |
with gr.Row():
|
| 224 |
# Chat
|
|
|
|
| 230 |
|
| 231 |
# Tools
|
| 232 |
with gr.Column(scale=2):
|
| 233 |
+
gr.Markdown("### Quick Readiness Assessment (your factors)")
|
| 234 |
+
with gr.Row():
|
| 235 |
+
savings = gr.Number(value=2000, label="Savings (USD)", precision=0)
|
| 236 |
+
income = gr.Number(value=2500, label="Income/mo (USD)", precision=0)
|
| 237 |
+
with gr.Row():
|
| 238 |
+
bills = gr.Number(value=1500, label="Bills/mo (USD)", precision=0)
|
| 239 |
+
entertainment = gr.Number(value=200, label="Entertainment/mo (USD)", precision=0)
|
| 240 |
+
assets = gr.Number(value=0, label="Assets (USD)", precision=0)
|
| 241 |
+
|
| 242 |
+
sales_skills = gr.Slider(0,10, value=5, step=1, label="Sales skills (0–10)")
|
| 243 |
+
confidence = gr.Slider(0,10, value=5, step=1, label="Confidence (0–10)")
|
| 244 |
+
dependents = gr.Slider(0,6, value=0, step=1, label="Dependents (count)")
|
| 245 |
+
age = gr.Slider(15,80, value=25, step=1, label="Age")
|
| 246 |
+
idea_level = gr.Radio(
|
| 247 |
+
choices=list(IDEA_LEVEL_MAP.keys()),
|
| 248 |
+
value="exploring",
|
| 249 |
+
label="Idea level"
|
| 250 |
+
)
|
| 251 |
+
entrepreneurial_experience = gr.Slider(0,10, value=3, step=1, label="Entrepreneurial experience (0–10)")
|
| 252 |
+
risk = gr.Slider(0,10, value=5, step=1, label="Risk tolerance (0–10)")
|
| 253 |
|
| 254 |
assess_btn = gr.Button("Assess readiness")
|
| 255 |
assessment_state = gr.State({})
|
| 256 |
assess_out = gr.JSON(label="Assessment Result")
|
| 257 |
|
| 258 |
+
gr.Markdown("### Path Explorer (domains)")
|
| 259 |
interests = gr.CheckboxGroup(
|
| 260 |
choices=list(PATHS.keys()),
|
| 261 |
value=["tech"],
|
|
|
|
| 271 |
paths_out = gr.JSON(label="Suggested Paths")
|
| 272 |
|
| 273 |
# Wiring
|
| 274 |
+
def do_assess(savings, income, bills, entertainment, assets,
|
| 275 |
+
sales_skills, confidence, dependents, age,
|
| 276 |
+
idea_level, entrepreneurial_experience, risk):
|
| 277 |
payload = {
|
| 278 |
+
"savings": savings, "income": income, "bills": bills, "entertainment": entertainment, "assets": assets,
|
| 279 |
+
"sales_skills": sales_skills, "confidence": confidence, "dependents": dependents, "age": age,
|
| 280 |
+
"idea_level": idea_level, "entrepreneurial_experience": entrepreneurial_experience, "risk": risk
|
| 281 |
}
|
| 282 |
result = compute_readiness(payload)
|
| 283 |
return result, result # show + store
|
| 284 |
|
| 285 |
assess_btn.click(
|
| 286 |
do_assess,
|
| 287 |
+
inputs=[savings, income, bills, entertainment, assets,
|
| 288 |
+
sales_skills, confidence, dependents, age,
|
| 289 |
+
idea_level, entrepreneurial_experience, risk],
|
| 290 |
outputs=[assess_out, assessment_state]
|
| 291 |
)
|
| 292 |
|