diff --git a/backend/elena_app.py b/backend/elena_app.py index 68bab37..bf83c0d 100644 --- a/backend/elena_app.py +++ b/backend/elena_app.py @@ -464,6 +464,89 @@ def ask(): con.commit() return jsonify({"reply": reply, "deviation_recorded": recorded}) +# ── Организационный слой: оргструктура + должностные инструкции (между IDEF0 и ТЗ) ── +ORGCHART_TOOL = { + "name": "build_orgchart", + "description": "Строит целевую оргструктуру (TO-BE) из функциональной модели и интервью.", + "input_schema": { + "type": "object", + "properties": { + "units": {"type": "array", "items": {"type": "object", "properties": { + "role": {"type": "string", "description": "Должность/роль"}, + "reports_to": {"type": "string", "description": "Кому подчиняется (роль) или '—' для верхнего уровня"}, + "headcount": {"type": "integer", "description": "Сколько человек на этой роли"}, + "owns_functions": {"type": "array", "items": {"type": "string"}, "description": "Функции IDEF0 (node_id или название), за которые отвечает"}, + "note": {"type": "string", "description": "Комментарий: совмещения, особенности, конфликт интересов"} + }, "required": ["role", "reports_to", "headcount"]}}, + "insight": {"type": "string", "description": "Ключевой вывод: узкие места, перегруз, конфликты интересов в структуре"} + }, + "required": ["units", "insight"] + } +} +ORG_INSTRUCTION = """Построй целевую оргструктуру (TO-BE) бизнеса клиента. +Опирайся на функциональную модель (mechanisms — кто выполняет функции) и интервью. +Учитывай реальный масштаб — не раздувай штат. Где есть совмещения ролей или конфликт интересов (один человек и исполняет, и контролирует) — отметь в note соответствующей роли и в insight. +Если зафиксированы отклонения клиента — учитывай их (например, совмещение склада и пошива оставлено по требованию клиента). +Вызови build_orgchart.""" + +@app.route("/api/build-orgchart", methods=["POST"]) +def build_orgchart_route(): + data = request.get_json(force=True) or {} + proj = get_project(data.get("token")) + if not proj: + return jsonify({"error": "not found"}), 404 + extra = stage_artifact_context(proj["id"], "idef0") # canvas + idef0 + отклонения + result, usage = run_tool(proj["id"], ORGCHART_TOOL, "build_orgchart", ORG_INSTRUCTION, extra_context=extra, max_tokens=2500) + if result is None: + return jsonify({"error": usage}), 500 + save_artifact(proj["id"], "orgchart", result) + return jsonify({"orgchart": result, "usage": usage}) + +JOBS_TOOL = { + "name": "build_job_descriptions", + "description": "Должностные инструкции по ролям из оргструктуры и функций, с учётом отклонений клиента.", + "input_schema": { + "type": "object", + "properties": { + "roles": {"type": "array", "items": {"type": "object", "properties": { + "role": {"type": "string"}, + "purpose": {"type": "string", "description": "Цель должности одним предложением"}, + "responsibilities": {"type": "array", "items": {"type": "string"}, "description": "Зоны ответственности"}, + "kpis": {"type": "array", "items": {"type": "string"}, "description": "Показатели эффективности (измеримые)"}, + "reports_to": {"type": "string", "description": "Кому подчиняется"}, + "authority": {"type": "array", "items": {"type": "string"}, "description": "Права и полномочия"}, + "deviation_note": {"type": "string", "description": "Если роль затронута отклонением клиента — как именно (совмещение и риск)"} + }, "required": ["role", "purpose", "responsibilities", "kpis"]}} + }, + "required": ["roles"] + } +} +JOBS_INSTRUCTION = """Составь должностные инструкции по ролям из целевой оргструктуры и функциональной модели. +Для каждой роли: цель должности, зоны ответственности, измеримые KPI, кому подчиняется, права/полномочия. +ВАЖНО: если роль затронута отклонением клиента (совмещение функций и т.п.) — отрази это честно в deviation_note и в обязанностях, с оговоркой про риск (например, совмещение склада и пошива — риск «плавающего» учёта остатков). +Вызови build_job_descriptions.""" + +@app.route("/api/build-jobs", methods=["POST"]) +def build_jobs_route(): + data = request.get_json(force=True) or {} + proj = get_project(data.get("token")) + if not proj: + return jsonify({"error": "not found"}), 404 + pid = proj["id"] + parts = [] + org = latest_artifact(pid, "orgchart") + if org: + parts.append("ЦЕЛЕВАЯ ОРГСТРУКТУРА:\n" + json.dumps(org, ensure_ascii=False)[:3000]) + art = stage_artifact_context(pid, "idef0") # idef0 + canvas + отклонения + if art: + parts.append(art) + extra = "\n\n".join(parts) if parts else None + result, usage = run_tool(pid, JOBS_TOOL, "build_job_descriptions", JOBS_INSTRUCTION, extra_context=extra, max_tokens=3500) + if result is None: + return jsonify({"error": usage}), 500 + save_artifact(pid, "jobs", result) + return jsonify({"jobs": result, "usage": usage}) + # ── Tool schema: строгий IDEF0 (ICOM + декомпозиция) ── ARROW = { "type": "object", @@ -1347,6 +1430,8 @@ def get_project_state(token): "model": json.loads(model_row["blocks_json"]) if model_row else None, "selection": latest_artifact(proj["id"], "selection"), "canvas": latest_artifact(proj["id"], "canvas"), + "orgchart": latest_artifact(proj["id"], "orgchart"), + "jobs": latest_artifact(proj["id"], "jobs"), "spec": latest_artifact(proj["id"], "spec"), "approvals": latest_artifact(proj["id"], "approvals") or {}, "crm": crm, diff --git a/docs/cabinet.html b/docs/cabinet.html index 2b42eca..3140cf4 100644 --- a/docs/cabinet.html +++ b/docs/cabinet.html @@ -598,7 +598,7 @@ async function sendMsg(){ let anTab="canvas"; function renderAnalysis(){ const pad=document.getElementById("anPad"); - pad.innerHTML=`