192 lines
6.4 KiB
Python
192 lines
6.4 KiB
Python
"""Trainee routes: win/lose board, weak-areas, generate own persona."""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
from ..llm import LLMError
|
|
from ..services.trainee import MyPersonaStore, analyze_weak_areas
|
|
from .helpers import (
|
|
ApiError,
|
|
current_user,
|
|
internal_error,
|
|
is_valid_tenant_id,
|
|
request_json_object,
|
|
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"],
|
|
}
|
|
|
|
|
|
def _authorized_finished_trainee_sessions(s, actor) -> list[dict]:
|
|
"""Return only current, actor-authorized sessions usable as evidence."""
|
|
org_id = actor.get("org_id")
|
|
user_id = actor.get("id")
|
|
if not is_valid_tenant_id(org_id) or not isinstance(user_id, str) or not user_id:
|
|
raise ApiError("permission denied", 403)
|
|
sessions = s["sessions"].list_for_user(user_id, org_id=org_id)
|
|
from .chat_routes import _authorize_session_context
|
|
|
|
authorized = []
|
|
for session in sessions:
|
|
if (
|
|
not isinstance(session, dict)
|
|
or (session.get("mode") or "trainee") != "trainee"
|
|
or session.get("status") != "finished"
|
|
or session.get("outcome") not in ("won", "lost")
|
|
):
|
|
continue
|
|
try:
|
|
_authorize_session_context(s, session, required_status="finished")
|
|
except ApiError:
|
|
continue
|
|
authorized.append(session)
|
|
return authorized
|
|
|
|
|
|
@me_bp.get("/board")
|
|
@require_auth
|
|
def win_lose_board():
|
|
"""Per-persona won/lost/not-tried across all groups the user sees (any authenticated user)."""
|
|
s = _stores()
|
|
actor = current_user()
|
|
uid = actor["id"]
|
|
org_id = actor.get("org_id")
|
|
if not is_valid_tenant_id(org_id):
|
|
raise ApiError("permission denied", 403)
|
|
my_sessions = s["sessions"].list_for_user(uid, org_id=org_id)
|
|
outcome_by = {
|
|
(x.get("group_id"), x.get("persona_id")): (
|
|
"active" if x.get("status") == "active" else x.get("outcome")
|
|
)
|
|
for x in my_sessions
|
|
if isinstance(x, dict)
|
|
}
|
|
|
|
groups = s["groups"].list_visible_to(
|
|
role="user", org_id=org_id, user_id=uid
|
|
)
|
|
items = []
|
|
for g in groups:
|
|
raw_personas = g.get("personas")
|
|
personas = raw_personas if isinstance(raw_personas, list) else []
|
|
for p in personas:
|
|
if not isinstance(p, dict):
|
|
continue
|
|
group_id = g.get("id")
|
|
persona_id = p.get("id")
|
|
if not isinstance(group_id, str) or not group_id or not isinstance(persona_id, str) or not persona_id:
|
|
continue
|
|
key = (group_id, persona_id)
|
|
items.append({
|
|
"group_id": group_id,
|
|
"group_title": g.get("title"),
|
|
"persona_id": persona_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
|
|
def weak_areas():
|
|
s = _stores()
|
|
actor = current_user()
|
|
uid = actor["id"]
|
|
org_id = actor.get("org_id")
|
|
if not is_valid_tenant_id(org_id):
|
|
raise ApiError("permission denied", 403)
|
|
sessions = _authorized_finished_trainee_sessions(s, actor)
|
|
insight = analyze_weak_areas(sessions, user_id=uid, org_id=org_id)
|
|
return jsonify({"insight": insight})
|
|
|
|
|
|
def _personal_group(s, actor) -> dict:
|
|
"""Return (or create) the user's private group holding their own personas."""
|
|
org_id = actor.get("org_id")
|
|
if not is_valid_tenant_id(org_id):
|
|
raise ApiError("permission denied", 403)
|
|
try:
|
|
return s["groups"].get_or_create_private_group(
|
|
org_id=org_id,
|
|
owner_user_id=actor["id"],
|
|
owner_name=actor.get("name", "User"),
|
|
)
|
|
except ValueError as exc:
|
|
raise internal_error("private group unavailable", exc, 409)
|
|
|
|
|
|
@me_bp.get("/personas")
|
|
@require_auth
|
|
def my_personas():
|
|
s = _stores()
|
|
actor = current_user()
|
|
group = _personal_group(s, actor)
|
|
from .group_routes import serialize_group, serialize_persona
|
|
|
|
raw_personas = group.get("personas")
|
|
personas = raw_personas if isinstance(raw_personas, list) else []
|
|
return jsonify({
|
|
"group": serialize_group(group, actor),
|
|
"personas": [serialize_persona(p, actor) for p in personas if isinstance(p, dict)],
|
|
})
|
|
|
|
|
|
@me_bp.post("/personas/generate")
|
|
@require_auth
|
|
def generate_persona():
|
|
s = _stores()
|
|
actor = current_user()
|
|
data = request_json_object()
|
|
mode = data.get("mode", "manual") # "weak-area" | "manual"
|
|
spec = data.get("spec") or {}
|
|
if not isinstance(mode, str) or not isinstance(spec, dict):
|
|
raise ApiError("invalid request", 400)
|
|
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
|
|
org_id = actor.get("org_id")
|
|
if not is_valid_tenant_id(org_id):
|
|
raise ApiError("permission denied", 403)
|
|
sessions = _authorized_finished_trainee_sessions(s, actor)
|
|
spec = analyze_weak_areas(sessions, user_id=actor["id"], org_id=org_id)
|
|
from ..services.own_persona import generate_own_persona
|
|
|
|
try:
|
|
persona = generate_own_persona(llm, mode=mode, spec=spec)
|
|
except (LLMError, ValueError) as exc:
|
|
raise internal_error("persona generation failed", exc)
|
|
|
|
group = _personal_group(s, actor)
|
|
with s["groups"].record_lock(group["id"]):
|
|
group = s["groups"].get(group["id"])
|
|
raw_personas = group.get("personas")
|
|
existing = [p for p in raw_personas if isinstance(p, dict)] if isinstance(raw_personas, list) else []
|
|
persona["id"] = f"myp-{uuid.uuid4().hex[:10]}"
|
|
existing.append(persona)
|
|
s["groups"].set_personas(group["id"], existing)
|
|
from .group_routes import serialize_group, serialize_persona
|
|
|
|
fresh_group = s["groups"].get(group["id"])
|
|
return jsonify({
|
|
"persona": serialize_persona(persona, actor),
|
|
"group": serialize_group(fresh_group, actor),
|
|
}), 201
|