Sales Trainer v0.1: corporate sales-training simulator (Flask+Vue, 15 personas, chat simulator, judge, analytics)
- Auth/roles (no self-reg), admin user provision, JWT - Analyze: sales kit + initial pain-fit from form/upload - Persona generator: 15 personas (5/tier) w/ pain variety, negotiation, init mode, channel, latent/revealable, wrong_text special - Chat simulator: per-mode initiation, one-shot, hidden signals, judge-LLM debrief+coaching - Trainee loop: win/lose board, weak-areas, user-generated personas - Admin analytics; EN+TH Vue SPA served by Flask - Deploy: Dockerfile, docker-compose, README, eng-log + HANDOFF - Tests (mock LLM): m0/m1/routes/e2e all pass
This commit is contained in:
92
backend/app/services/report.py
Normal file
92
backend/app/services/report.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""Report builder: assemble a human-readable analysis report from sales kit + personas."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
TIER_NAMES = {
|
||||
"A": ("Ready to buy", "ตั้งใจซื้อ"),
|
||||
"B": ("Unsure / educating", "ไม่แน่ใจ"),
|
||||
"C": ("Not interested but has pain", "ไม่สนใจแต่มี pain"),
|
||||
}
|
||||
|
||||
|
||||
def build_report(*, sales_kit: dict[str, Any], personas: list[dict[str, Any]], language: str = "th") -> dict[str, Any]:
|
||||
thai = language == "th"
|
||||
tiers: dict[str, list[dict[str, Any]]] = {"A": [], "B": [], "C": []}
|
||||
for p in personas:
|
||||
tiers.get(p.get("tier", "B"), []).append(p)
|
||||
|
||||
sections = []
|
||||
sections.append({
|
||||
"title": "Sales Kit / ข้อมูลสินค้า" if thai else "Sales Kit",
|
||||
"content": _render_sales_kit(sales_kit, thai),
|
||||
})
|
||||
for tier, personas_list in tiers.items():
|
||||
label = TIER_NAMES[tier][1 if thai else 0]
|
||||
sections.append({
|
||||
"title": f"Tier {tier} — {label}",
|
||||
"content": _render_tier(personas_list, thai),
|
||||
})
|
||||
|
||||
return {
|
||||
"title": f"{sales_kit.get('productName', 'Product')} — Sales Training Analysis",
|
||||
"summary": "Customer personas + pain analysis for sales training.",
|
||||
"language": language,
|
||||
"sections": sections,
|
||||
"raw_personas": personas,
|
||||
}
|
||||
|
||||
|
||||
def _render_sales_kit(kit: dict[str, Any], thai: bool) -> str:
|
||||
lines = []
|
||||
lines.append(f"**{'สินค้า' if thai else 'Product'}:** {kit.get('productName', '-')}")
|
||||
if kit.get("category"):
|
||||
lines.append(f"**{'หมวดหมู่' if thai else 'Category'}:** {kit['category']}")
|
||||
if kit.get("valueProps"):
|
||||
lines.append(f"**{'คุณค่า' if thai else 'Value props'}:** " + "; ".join(kit["valueProps"]))
|
||||
if kit.get("features"):
|
||||
lines.append(f"**{'ฟีเจอร์' if thai else 'Features'}:** " + "; ".join(kit["features"]))
|
||||
if kit.get("pricingAnchors"):
|
||||
lines.append(f"**{'ราคา' if thai else 'Pricing'}:** " + "; ".join(kit["pricingAnchors"]))
|
||||
ta = kit.get("targetAudience") or {}
|
||||
if ta.get("segment"):
|
||||
lines.append(f"**{'กลุ่มเป้าหมาย' if thai else 'Target segment'}:** {ta['segment']}")
|
||||
if kit.get("initialPainFit"):
|
||||
lines.append(f"**{'Pain ที่สินค้าแก้ได้เบื้องต้น' if thai else 'Initial pain-fit'}:**")
|
||||
for p in kit["initialPainFit"]:
|
||||
lines.append(f"- ({p.get('fit', '?')}) {p.get('pain', '')}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _render_tier(personas: list[dict[str, Any]], thai: bool) -> str:
|
||||
if not personas:
|
||||
return "_" + ("ไม่มี" if thai else "none") + "_"
|
||||
blocks = []
|
||||
for p in personas:
|
||||
blocks.append(_render_persona(p, thai))
|
||||
return "\n\n---\n\n".join(blocks)
|
||||
|
||||
|
||||
def _render_persona(p: dict[str, Any], thai: bool) -> str:
|
||||
lines = [f"### {p.get('name', '-')} (difficulty {p.get('difficulty', 1)})"]
|
||||
lines.append(f"- {'อาชีพ' if thai else 'Profession'}: {p.get('profession', '-')} | "
|
||||
f"{'อายุ' if thai else 'Age'}: {p.get('age_group', '-')} | "
|
||||
f"{'ช่องทาง' if thai else 'Channel'}: {p.get('channel', 'facebook')} | "
|
||||
f"{'เปิดบท' if thai else 'Initiation'}: {p.get('initiation_mode', 'customer')}")
|
||||
if p.get("special"):
|
||||
lines.append(f"- SPECIAL: {p['special']}")
|
||||
lines.append(f"- {'พื้นหลัง' if thai else 'Background'}: {p.get('background', '-')}")
|
||||
lines.append(f"- {'รายได้' if thai else 'Income'}: {p.get('income', '-')} | "
|
||||
f"{'ไลฟ์สไตล์' if thai else 'Lifestyle'}: {p.get('lifestyle', '-')}")
|
||||
lines.append(f"- {'นิสัย' if thai else 'Personality'}: {p.get('personality', '-')}")
|
||||
if p.get("pains"):
|
||||
lines.append(f"- {'Pain points (latent)' if thai else 'Pains (latent)'}:")
|
||||
for pain in p.get("pains", []):
|
||||
conds = "; ".join(pain.get("resolutionConditions", [])) if isinstance(pain, dict) else ""
|
||||
lines.append(f" - [{pain.get('fit', '?') if isinstance(pain, dict) else '?'}] "
|
||||
f"{pain.get('name', pain) if isinstance(pain, dict) else pain}"
|
||||
f"{' — resolve: ' + conds if conds else ''}")
|
||||
if p.get("negotiation_levers"):
|
||||
levers = p.get("negotiation_levers") or []
|
||||
lines.append(f"- {'ต่อรอง' if thai else 'Negotiation levers'}: " + ", ".join(str(x) for x in levers))
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user