"""Trainee routes: win/lose board, weak-areas, generate own persona.""" from __future__ import annotations from flask import Blueprint, jsonify, request from ..llm import LLMError from ..services.trainee import MyPersonaStore, analyze_weak_areas from .helpers import ApiError, current_user, require_auth, require_roles me_bp = Blueprint("me", __name__) def _stores(): from flask import current_app return { "groups": current_app.extensions["group_store"], "sessions": current_app.extensions["session_store"], "my_personas": current_app.extensions.get("my_persona_store"), "llm": current_app.extensions["llm"], } @me_bp.get("/board") @require_auth @require_roles("user", "admin", "super_admin") def win_lose_board(): """Per-persona won/lost/not-tried across all groups the user sees. Available to trainees AND admins (admins may also train/practice). """ s = _stores() actor = current_user() uid = actor["id"] role = actor.get("role", "user") my_sessions = s["sessions"].list_for_user(uid) outcome_by = {(x.get("group_id"), x.get("persona_id")): x.get("outcome") for x in my_sessions} groups = s["groups"].list_visible_to(role=role, org_id=actor.get("org_id")) # Only the owner sees their personal groups (IDOR defense). groups = [g for g in groups if not g.get("owner_user_id") or g.get("owner_user_id") == uid] items = [] for g in groups: for p in g.get("personas", []): key = (g["id"], p["id"]) items.append({ "group_id": g["id"], "group_title": g.get("title"), "persona_id": p["id"], "persona_name": p.get("name"), "tier": p.get("tier"), "my_outcome": outcome_by.get(key, "not_tried"), }) return jsonify({"board": items}) @me_bp.get("/weak-areas") @require_auth @require_roles("user", "admin", "super_admin") def weak_areas(): s = _stores() uid = current_user()["id"] sessions = s["sessions"].list_for_user(uid) insight = analyze_weak_areas(sessions) return jsonify({"insight": insight}) def _personal_group(s, actor) -> dict: """Return (or create) the user's private group holding their own personas.""" groups = s["groups"].list_for_org(org_id=actor.get("org_id")) for g in groups: if g.get("owner_user_id") == actor["id"]: return g g = s["groups"].create( org_id=actor.get("org_id") or "org-default", creator_id=actor["id"], title=f"{actor.get('name','User')}'s private personas", ) s["groups"].update( g["id"], status="ready", owner_user_id=actor["id"], input={"channel": "facebook", "language": "th"}, sales_kit={"productName": "personal practice", "valueProps": [], "features": []}, ) return s["groups"].get(g["id"]) @me_bp.get("/personas") @require_auth @require_roles("user", "admin", "super_admin") def my_personas(): s = _stores() uid = current_user()["id"] group = _personal_group(s, current_user()) return jsonify({"group": group, "personas": group.get("personas", [])}) @me_bp.post("/personas/generate") @require_auth @require_roles("user", "admin", "super_admin") def generate_persona(): s = _stores() actor = current_user() data = request.get_json(silent=True) or {} mode = data.get("mode", "manual") # "weak-area" | "manual" spec = data.get("spec") or {} llm = s["llm"] if not llm: raise ApiError("LLM not configured", 500) if mode == "weak-area" and not spec: # auto-detect weak areas from this user's losses if no spec given sessions = s["sessions"].list_for_user(actor["id"]) spec = analyze_weak_areas(sessions) from ..services.own_persona import generate_own_persona try: persona = generate_own_persona(llm, mode=mode, spec=spec) except (LLMError, ValueError) as exc: raise ApiError(f"generation failed: {exc}", 500) group = _personal_group(s, actor) group = s["groups"].get(group["id"]) existing = group.get("personas", []) persona["id"] = f"myp-{len(existing)+1:02d}" existing.append(persona) s["groups"].set_personas(group["id"], existing) return jsonify({"persona": persona, "group": s["groups"].get(group["id"])}), 201