|
|
|
|
|
import gradio as gr |
|
|
import json |
|
|
|
|
|
SYSTEM_PROMPT = ( |
|
|
"You are a friendly tutor for Entrepreneurial Readiness and Paths. " |
|
|
"Be practical and concise. If the user ran the assessment or path explorer, use that info." |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PATHS = { |
|
|
"tech": [ |
|
|
{"path":"Micro-SaaS","difficulty":3,"benefits":"High margins, scalable, global reach", |
|
|
"skills":["customer interviews","MVP scoping","pricing","analytics"], |
|
|
"starter":["Define niche pain","5–10 interviews","Landing page test","Iterate"]}, |
|
|
{"path":"No-code automation agency","difficulty":2,"benefits":"Service revenue funds experiments", |
|
|
"skills":["workflow mapping","Zapier/Make","sales","onboarding"], |
|
|
"starter":["Pick niche","3 pilot clients","SOPs","Retainers"]}, |
|
|
{"path":"Educational apps","difficulty":3,"benefits":"Recurring revenue via schools/parents", |
|
|
"skills":["curriculum","UX","distribution partnerships"], |
|
|
"starter":["Niche curriculum","Prototype","Parent/teacher tests"]} |
|
|
], |
|
|
"cooking": [ |
|
|
{"path":"Meal-prep delivery","difficulty":4,"benefits":"Tangible product; recurring subs", |
|
|
"skills":["food safety","menu costing","logistics","support"], |
|
|
"starter":["Permits","Menu tests","Delivery loop","Subscriptions"]}, |
|
|
{"path":"Cooking classes","difficulty":2,"benefits":"Low capex; community", |
|
|
"skills":["lesson design","facilitation","marketing"], |
|
|
"starter":["Pilot class","Testimonials","Cohorts","Bundles"]} |
|
|
], |
|
|
"education": [ |
|
|
{"path":"Tutoring niche","difficulty":2,"benefits":"Fast validation; referrals", |
|
|
"skills":["subject mastery","scheduling","sales"], |
|
|
"starter":["ICP","5 trial students","Packages","Referrals"]} |
|
|
], |
|
|
"fitness": [ |
|
|
{"path":"Online coaching","difficulty":2,"benefits":"Low overhead; trust", |
|
|
"skills":["programming","accountability","content"], |
|
|
"starter":["Niche offer","Clients 1–3","Testimonials","Packages"]} |
|
|
], |
|
|
"content": [ |
|
|
{"path":"Newsletter + paid tiers","difficulty":2,"benefits":"Audience compounds", |
|
|
"skills":["editorial","conversion","email ops"], |
|
|
"starter":["Niche POV","Weekly cadence","Lead magnet","Feedback loop"]} |
|
|
], |
|
|
"games": [ |
|
|
{"path":"Indie premium","difficulty":4,"benefits":"Creative control; wishlists matter", |
|
|
"skills":["scope control","playtesting","marketing"], |
|
|
"starter":["Core loop","Vertical slice","Store page","Playtests"]} |
|
|
], |
|
|
"fashion": [ |
|
|
{"path":"Print-on-demand niche","difficulty":2,"benefits":"Low inventory risk", |
|
|
"skills":["design","niche research","ads"], |
|
|
"starter":["Mockups","POD store","Ad test","Iterate"]} |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WEIGHTS = { |
|
|
"savings": 0.10, |
|
|
"income": 0.08, |
|
|
"bills": 0.08, |
|
|
"entertainment": 0.04, |
|
|
"assets": 0.06, |
|
|
"sales_skills": 0.14, |
|
|
"confidence": 0.10, |
|
|
"dependents": 0.06, |
|
|
"age": 0.04, |
|
|
"idea_level": 0.10, |
|
|
"entrepreneurial_experience": 0.10, |
|
|
"risk": 0.10, |
|
|
} |
|
|
|
|
|
CAP = { |
|
|
"savings": 20000, |
|
|
"income": 8000, |
|
|
"bills": 5000, |
|
|
"entertainment": 1000, |
|
|
"assets": 100000, |
|
|
"dependents_max": 3, |
|
|
} |
|
|
|
|
|
IDEA_LEVEL_MAP = { |
|
|
"none": 0.0, |
|
|
"exploring": 0.25, |
|
|
"validated_problem": 0.5, |
|
|
"prototype": 0.75, |
|
|
"mvp_with_users": 1.0, |
|
|
} |
|
|
|
|
|
def _nz(x, default=0.0): |
|
|
try: |
|
|
return float(x) if x is not None else float(default) |
|
|
except Exception: |
|
|
return float(default) |
|
|
|
|
|
def _clamp01(v): return max(0.0, min(1.0, float(v))) |
|
|
def _direct_norm(v, cap): return _clamp01(_nz(v) / float(cap)) |
|
|
def _inverse_norm(v, cap): return 1.0 - _direct_norm(v, cap) |
|
|
|
|
|
def _age_norm(age): |
|
|
"""Mild symmetric curve centered ~35; tiny effect (≤0.2).""" |
|
|
a = _nz(age, 35) |
|
|
penalty = min(abs(a - 35.0) / 35.0, 1.0) * 0.2 |
|
|
return _clamp01(1.0 - penalty) |
|
|
|
|
|
def _dependents_norm(dep): |
|
|
d = max(0, int(_nz(dep, 0))) |
|
|
base = 1.0 - min(d / float(CAP["dependents_max"]), 1.0) |
|
|
return _clamp01(base) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compute_readiness(payload): |
|
|
n = { |
|
|
"savings": _direct_norm(payload["savings"], CAP["savings"]), |
|
|
"income": _direct_norm(payload["income"], CAP["income"]), |
|
|
"bills": _inverse_norm(payload["bills"], CAP["bills"]), |
|
|
"entertainment": _inverse_norm(payload["entertainment"], CAP["entertainment"]), |
|
|
"assets": _direct_norm(payload["assets"], CAP["assets"]), |
|
|
"sales_skills": _clamp01(_nz(payload["sales_skills"]) / 10.0), |
|
|
"confidence": _clamp01(_nz(payload["confidence"]) / 10.0), |
|
|
"dependents": _dependents_norm(payload["dependents"]), |
|
|
"age": _age_norm(payload["age"]), |
|
|
"idea_level": IDEA_LEVEL_MAP.get(payload["idea_level"], 0.0), |
|
|
"entrepreneurial_experience": _clamp01(_nz(payload["entrepreneurial_experience"]) / 10.0), |
|
|
"risk": _clamp01(_nz(payload["risk"]) / 10.0), |
|
|
} |
|
|
|
|
|
total = sum(WEIGHTS[k] * n[k] for k in WEIGHTS.keys()) |
|
|
score = int(round(100 * total)) |
|
|
tier = "Starter" if score < 55 else ("Building" if score < 75 else "Launch-ready") |
|
|
|
|
|
tips = [] |
|
|
|
|
|
if n["savings"] < 0.5 or n["assets"] < 0.4: |
|
|
tips.append("Run a 60–90 day runway sprint: reduce burn and build a buffer.") |
|
|
if n["bills"] < 0.6 or n["entertainment"] < 0.6: |
|
|
tips.append("Do a weekly expense audit and a 30-day no-spend on non-essentials.") |
|
|
if n["income"] < 0.5: |
|
|
tips.append("Spin up a simple service for cash: 3 outreach/day → 2 pilot clients.") |
|
|
|
|
|
if n["sales_skills"] < 0.6: |
|
|
tips.append("Do 20 cold DMs/day for 5 days; refine your offer script.") |
|
|
if n["confidence"] < 0.6: |
|
|
tips.append("Post build-in-public for 10 days to desensitize and attract allies.") |
|
|
if n["entrepreneurial_experience"] < 0.5: |
|
|
tips.append("Run a 2-week micro-project: interviews → small paid pilot.") |
|
|
if n["idea_level"] < 0.5: |
|
|
tips.append("Advance one notch: exploring → validated problem → prototype → MVP.") |
|
|
|
|
|
if n["risk"] < 0.5: |
|
|
tips.append("Set a ‘risk budget’: small, reversible tests with clear stop-lines.") |
|
|
|
|
|
return {"score": score, "tier": tier, "tips": tips[:5], "normalized": n} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def suggest_paths(interests, experience): |
|
|
results = [] |
|
|
for domain in interests or []: |
|
|
for item in PATHS.get(domain, []): |
|
|
diff = item["difficulty"] |
|
|
if experience == "beginner": diff = min(5, diff + 1) |
|
|
if experience == "advanced": diff = max(1, diff - 1) |
|
|
results.append({ |
|
|
"domain": domain, |
|
|
"path": item["path"], |
|
|
"difficulty_1to5": diff, |
|
|
"benefits": item["benefits"], |
|
|
"key_skills": item["skills"], |
|
|
"starter_plan": item["starter"], |
|
|
}) |
|
|
return results[:6] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def local_reply(user_msg, history, assessment_state, path_state): |
|
|
text = (user_msg or "").lower() |
|
|
|
|
|
if "score" in text or "ready" in text or "readiness" in text: |
|
|
if isinstance(assessment_state, dict) and "score" in assessment_state: |
|
|
s = assessment_state["score"] |
|
|
t = assessment_state.get("tier", "?") |
|
|
tip = (assessment_state.get("tips") or ["Run small demand tests first."])[0] |
|
|
return f"Your readiness is **{s}/100 ({t})**. Top tip: {tip}" |
|
|
else: |
|
|
return "Run **Assess readiness** on the right, then ask me again 🙂" |
|
|
|
|
|
if "path" in text or "idea" in text or "domain" in text: |
|
|
if isinstance(path_state, dict) and path_state.get("suggestions"): |
|
|
sug = path_state["suggestions"][0] |
|
|
return (f"Try **{sug['domain']} → {sug['path']}**. " |
|
|
f"Difficulty ~{sug['difficulty_1to5']}/5. " |
|
|
f"Benefits: {sug['benefits']}. " |
|
|
f"First steps: {', '.join(sug['starter_plan'][:3])}. Want more options?") |
|
|
else: |
|
|
return "Pick a few interests under **Path Explorer**, hit **Suggest paths**, then ask me which to start with." |
|
|
|
|
|
for domain in PATHS.keys(): |
|
|
if domain in text: |
|
|
first = PATHS[domain][0] |
|
|
return (f"In **{domain}**, consider **{first['path']}** " |
|
|
f"(difficulty {first['difficulty']}/5). " |
|
|
f"Why it can pay off: {first['benefits']}. " |
|
|
f"Starter plan: {', '.join(first['starter'][:3])}.") |
|
|
return ("Start with a small **demand test**: a landing page and 5–10 user interviews. " |
|
|
"Measure real intent (signups/preorders) before you build.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(theme="soft", fill_height=True) as demo: |
|
|
gr.Markdown("## Entrepreneurial Tutor") |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=3): |
|
|
chat = gr.Chatbot(type="tuples", height=460) |
|
|
with gr.Row(): |
|
|
msg = gr.Textbox(placeholder="Ask about Entrepreneurial paths or readiness…", scale=4) |
|
|
send = gr.Button("Send", variant="primary") |
|
|
|
|
|
|
|
|
with gr.Column(scale=2): |
|
|
gr.Markdown("### Quick Readiness Assessment (your factors)") |
|
|
with gr.Row(): |
|
|
savings = gr.Number(value=2000, label="Savings (USD)", precision=0) |
|
|
income = gr.Number(value=2500, label="Income/mo (USD)", precision=0) |
|
|
with gr.Row(): |
|
|
bills = gr.Number(value=1500, label="Bills/mo (USD)", precision=0) |
|
|
entertainment = gr.Number(value=200, label="Entertainment/mo (USD)", precision=0) |
|
|
assets = gr.Number(value=0, label="Assets (USD)", precision=0) |
|
|
|
|
|
sales_skills = gr.Slider(0,10, value=5, step=1, label="Sales skills (0–10)") |
|
|
confidence = gr.Slider(0,10, value=5, step=1, label="Confidence (0–10)") |
|
|
dependents = gr.Slider(0,6, value=0, step=1, label="Dependents (count)") |
|
|
age = gr.Slider(15,80, value=25, step=1, label="Age") |
|
|
idea_level = gr.Radio( |
|
|
choices=list(IDEA_LEVEL_MAP.keys()), |
|
|
value="exploring", |
|
|
label="Idea level" |
|
|
) |
|
|
entrepreneurial_experience = gr.Slider(0,10, value=3, step=1, label="Entrepreneurial experience (0–10)") |
|
|
risk = gr.Slider(0,10, value=5, step=1, label="Risk tolerance (0–10)") |
|
|
|
|
|
assess_btn = gr.Button("Assess readiness") |
|
|
assessment_state = gr.State({}) |
|
|
assess_out = gr.JSON(label="Assessment Result") |
|
|
|
|
|
gr.Markdown("### Path Explorer (domains)") |
|
|
interests = gr.CheckboxGroup( |
|
|
choices=list(PATHS.keys()), |
|
|
value=["tech"], |
|
|
label="Interests / domains" |
|
|
) |
|
|
experience = gr.Radio( |
|
|
choices=["beginner","intermediate","advanced"], |
|
|
value="beginner", |
|
|
label="Experience level" |
|
|
) |
|
|
suggest_btn = gr.Button("Suggest paths") |
|
|
path_state = gr.State({}) |
|
|
paths_out = gr.JSON(label="Suggested Paths") |
|
|
|
|
|
|
|
|
def do_assess(savings, income, bills, entertainment, assets, |
|
|
sales_skills, confidence, dependents, age, |
|
|
idea_level, entrepreneurial_experience, risk): |
|
|
payload = { |
|
|
"savings": savings, "income": income, "bills": bills, "entertainment": entertainment, "assets": assets, |
|
|
"sales_skills": sales_skills, "confidence": confidence, "dependents": dependents, "age": age, |
|
|
"idea_level": idea_level, "entrepreneurial_experience": entrepreneurial_experience, "risk": risk |
|
|
} |
|
|
result = compute_readiness(payload) |
|
|
return result, result |
|
|
|
|
|
assess_btn.click( |
|
|
do_assess, |
|
|
inputs=[savings, income, bills, entertainment, assets, |
|
|
sales_skills, confidence, dependents, age, |
|
|
idea_level, entrepreneurial_experience, risk], |
|
|
outputs=[assess_out, assessment_state] |
|
|
) |
|
|
|
|
|
def do_paths(interests, experience): |
|
|
res = suggest_paths(interests, experience) |
|
|
state = {"interests": interests, "experience": experience, "suggestions": res} |
|
|
return res, state |
|
|
|
|
|
suggest_btn.click( |
|
|
do_paths, |
|
|
inputs=[interests, experience], |
|
|
outputs=[paths_out, path_state] |
|
|
) |
|
|
|
|
|
def on_send(user_message, history, assessment_state, path_state): |
|
|
if not user_message: |
|
|
return gr.update(), history |
|
|
reply = local_reply(user_message, history, assessment_state, path_state) |
|
|
return "", (history or []) + [[user_message, reply]] |
|
|
|
|
|
send.click( |
|
|
on_send, |
|
|
inputs=[msg, chat, assessment_state, path_state], |
|
|
outputs=[msg, chat] |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|