/
dmitry.kovalenkov
/
webcc
Обзор
Документация
Войти
/
dmitry.kovalenkov
/
webcc
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
openapi_contracts.html
702 строки
45 KB
Дмитрий Коваленков
create: openapi_contracts.html, event_schema.html, microservices_architecture.html, erd_sessions_scores.html, erd_users_content.html, cc_trainer_architecture.svg, user_story_map (1).docx, ВебТренажер__Архитектура.docx
08 апр 2026, 12:31
Верифицирован
08 апр 2026, 12:31
223ff20
Код
Авторство
О чём код?
<style> *{box-sizing:border-box;margin:0;padding:0} .root{font-family:var(--font-sans);color:var(--color-text-primary);padding:12px 0} .tabs{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px} .tab{font-size:11px;padding:4px 10px;border-radius:20px;border:0.5px solid var(--color-border-secondary);cursor:pointer;color:var(--color-text-secondary);background:transparent;transition:all .15s} .tab:hover{border-color:var(--color-border-primary);color:var(--color-text-primary)} .tab.active{background:var(--color-text-primary);color:var(--color-background-primary);border-color:transparent} .svc{display:none} .svc.show{display:block} .svc-header{margin-bottom:10px} .svc-title{font-size:15px;font-weight:500} .svc-meta{font-size:11px;color:var(--color-text-tertiary);margin-top:3px} .endpoint{border:0.5px solid var(--color-border-secondary);border-radius:10px;margin-bottom:8px;overflow:hidden} .ep-head{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;background:var(--color-background-secondary)} .ep-head:hover{background:var(--color-background-tertiary)} .method{font-size:10px;font-weight:500;padding:2px 7px;border-radius:4px;min-width:40px;text-align:center;font-family:var(--font-mono)} .m-get{background:#e1f5ee;color:#0f6e56}.m-post{background:#e6f1fb;color:#185fa5} .m-put{background:#faeeda;color:#854f0b}.m-delete{background:#fcebeb;color:#a32d2d} .m-ws{background:#eeedfe;color:#534ab7} @media(prefers-color-scheme:dark){ .m-get{background:#085041;color:#9fe1cb}.m-post{background:#0c447c;color:#b5d4f4} .m-put{background:#633806;color:#fac775}.m-delete{background:#791f1f;color:#f7c1c1} .m-ws{background:#3c3489;color:#cecbf6} } .ep-path{font-size:12px;font-family:var(--font-mono);color:var(--color-text-primary)} .ep-summary{font-size:11px;color:var(--color-text-secondary);margin-left:auto;text-align:right} .ep-body{padding:10px 12px;border-top:0.5px solid var(--color-border-tertiary);display:none} .ep-body.open{display:block} .section{margin-bottom:8px} .sec-label{font-size:10px;font-weight:500;color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px} .field-row{display:grid;grid-template-columns:160px 90px 1fr;gap:6px;font-size:11px;padding:3px 0;border-bottom:0.5px solid var(--color-border-tertiary)} .field-row:last-child{border-bottom:none} .fname{font-family:var(--font-mono);color:var(--color-text-primary)} .ftype{color:var(--color-text-tertiary);font-family:var(--font-mono)} .fdesc{color:var(--color-text-secondary);line-height:1.4} .req-badge{font-size:9px;color:var(--color-text-danger);vertical-align:super} .resp-row{display:grid;grid-template-columns:50px 1fr;gap:8px;font-size:11px;padding:3px 0;border-bottom:0.5px solid var(--color-border-tertiary)} .resp-row:last-child{border-bottom:none} .rcode{font-family:var(--font-mono);font-weight:500} .r200{color:var(--color-text-success)}.r201{color:var(--color-text-success)} .r400{color:var(--color-text-warning)}.r401{color:var(--color-text-danger)} .r403{color:var(--color-text-danger)}.r404{color:var(--color-text-warning)} .r422{color:var(--color-text-warning)} .rdesc{color:var(--color-text-secondary)} .auth-note{font-size:10px;padding:2px 8px;border-radius:3px;margin-left:6px} .auth-jwt{background:var(--color-background-success);color:var(--color-text-success)} .auth-none{background:var(--color-background-secondary);color:var(--color-text-tertiary);border:0.5px solid var(--color-border-tertiary)} .note{font-size:11px;color:var(--color-text-tertiary);padding:4px 0;line-height:1.5} </style> <div class="root"> <div class="tabs"> <button class="tab active" onclick="show('auth')">Auth :8001</button> <button class="tab" onclick="show('admin')">Admin :8002</button> <button class="tab" onclick="show('content')">Content :8003</button> <button class="tab" onclick="show('simulator')">Simulator :8004</button> <button class="tab" onclick="show('evaluator')">Evaluator :8005</button> <button class="tab" onclick="show('stats')">Stats :8006</button> <button class="tab" onclick="show('llmproxy')">LLM proxy :8007</button> <button class="tab" onclick="show('notify')">Notify :8008</button> </div> <!-- AUTH --> <div class="svc show" id="svc-auth"> <div class="svc-header"><div class="svc-title">Auth service</div><div class="svc-meta">Аутентификация, JWT, заявки на доступ · Oracle: USERS, ACCESS_REQUESTS</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/auth/login</span> <span class="auth-note auth-none">public</span> <span class="ep-summary">Вход, получение JWT</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">login<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Логин пользователя</span></div> <div class="field-row"><span class="fname">password<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Пароль</span></div> </div> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">access_token</span><span class="ftype">string</span><span class="fdesc">JWT RS256, exp 8h</span></div> <div class="field-row"><span class="fname">token_type</span><span class="ftype">string</span><span class="fdesc">"bearer"</span></div> <div class="field-row"><span class="fname">user</span><span class="ftype">object</span><span class="fdesc">id, full_name, role, group_ids[]</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r200">200</span><span class="rdesc">Успешный вход</span></div> <div class="resp-row"><span class="rcode r401">401</span><span class="rdesc">Неверный логин или пароль</span></div> <div class="resp-row"><span class="rcode r403">403</span><span class="rdesc">Аккаунт неактивен / заявка не одобрена</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/auth/refresh</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Обновить токен</span> </div> <div class="ep-body"> <div class="note">Принимает текущий действующий токен в заголовке, возвращает новый с обновлённым exp.</div> <div class="section" style="margin-top:6px"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">access_token</span><span class="ftype">string</span><span class="fdesc">Новый JWT</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/auth/access-requests</span> <span class="auth-note auth-none">public</span> <span class="ep-summary">Подать заявку на доступ</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">full_name<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Имя заявителя</span></div> <div class="field-row"><span class="fname">email<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Email для связи</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r201">201</span><span class="rdesc">Заявка создана, письмо отправлено админу</span></div> <div class="resp-row"><span class="rcode r422">422</span><span class="rdesc">Невалидный email</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/auth/me</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Профиль текущего пользователя</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">id</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">login</span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">full_name</span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">email</span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">role</span><span class="ftype">string</span><span class="fdesc">admin | content_manager | supervisor | operator_in | operator_out</span></div> <div class="field-row"><span class="fname">group_ids</span><span class="ftype">number[]</span><span class="fdesc">ID групп пользователя</span></div> </div> </div> </div> </div> <!-- ADMIN --> <div class="svc" id="svc-admin"> <div class="svc-header"><div class="svc-title">Admin service</div><div class="svc-meta">Управление пользователями и группами · Oracle: GROUPS, USER_GROUPS · роль: admin</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/admin/users</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Список пользователей</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">role</span><span class="ftype">string?</span><span class="fdesc">Фильтр по роли</span></div> <div class="field-row"><span class="fname">group_id</span><span class="ftype">number?</span><span class="fdesc">Фильтр по группе</span></div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">active | inactive</span></div> <div class="field-row"><span class="fname">page / page_size</span><span class="ftype">number?</span><span class="fdesc">Пагинация, default 1 / 50</span></div> </div> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">items</span><span class="ftype">User[]</span><span class="fdesc">id, login, full_name, role, status, group_ids[]</span></div> <div class="field-row"><span class="fname">total</span><span class="ftype">number</span><span class="fdesc">Всего записей</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/admin/users</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Создать пользователя</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">login<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Уникальный логин</span></div> <div class="field-row"><span class="fname">password<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Временный пароль</span></div> <div class="field-row"><span class="fname">full_name<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">email<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">role<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">admin | content_manager | supervisor | operator_in | operator_out</span></div> <div class="field-row"><span class="fname">group_ids</span><span class="ftype">number[]?</span><span class="fdesc">Сразу назначить группы</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r201">201</span><span class="rdesc">Пользователь создан</span></div> <div class="resp-row"><span class="rcode r400">400</span><span class="rdesc">Логин занят</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-put">PUT</span> <span class="ep-path">/admin/users/{user_id}</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Обновить пользователя</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body (все поля опциональны)</div> <div class="field-row"><span class="fname">full_name</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">email</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">role</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">active | inactive</span></div> <div class="field-row"><span class="fname">group_ids</span><span class="ftype">number[]?</span><span class="fdesc">Полная замена списка групп</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/admin/groups</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Создать группу</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">name<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Название группы</span></div> <div class="field-row"><span class="fname">description</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">scenario_ids</span><span class="ftype">number[]?</span><span class="fdesc">Доступные сценарии</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/admin/access-requests</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Список заявок на доступ</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">pending | approved | rejected</span></div> </div> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">items</span><span class="ftype">Request[]</span><span class="fdesc">id, full_name, email, status, created_at</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-put">PUT</span> <span class="ep-path">/admin/access-requests/{id}</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Одобрить / отклонить заявку</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">status<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">approved | rejected</span></div> <div class="field-row"><span class="fname">role</span><span class="ftype">string?</span><span class="fdesc">Роль при одобрении</span></div> </div> </div> </div> </div> <!-- CONTENT --> <div class="svc" id="svc-content"> <div class="svc-header"><div class="svc-title">Content service</div><div class="svc-meta">CRUD контента · Oracle: SCENARIOS, CHARACTERS, CHECKLISTS, CHECKLIST_ITEMS · роли: admin, content_manager (запись); все (чтение)</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/content/scenarios</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Список сценариев</span> </div> <div class="ep-body"> <div class="note">Для оператора — только сценарии групп, в которые он входит (фильтрация по group_ids из JWT). Для admin/content_manager — все.</div> <div class="section" style="margin-top:6px"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">active | draft | archived</span></div> </div> <div class="section"><div class="sec-label">Response 200 — каждый элемент</div> <div class="field-row"><span class="fname">id, title, status</span><span class="ftype"></span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">params_dict</span><span class="ftype">object?</span><span class="fdesc">Словарь параметров для оператора</span></div> <div class="field-row"><span class="fname">has_checklist</span><span class="ftype">bool</span><span class="fdesc"></span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/content/scenarios/{id}</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Сценарий с промптом</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">id, title, status</span><span class="ftype"></span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">system_prompt</span><span class="ftype">string</span><span class="fdesc">Полный текст промпта</span></div> <div class="field-row"><span class="fname">params_dict</span><span class="ftype">object?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">checklist</span><span class="ftype">Checklist?</span><span class="fdesc">Вложенный объект с items[]</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/content/scenarios</span> <span class="auth-note auth-jwt">JWT content_manager+</span> <span class="ep-summary">Создать сценарий</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">title<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">system_prompt<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">params_dict</span><span class="ftype">object?</span><span class="fdesc">Произвольный JSON-словарь</span></div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">draft (default) | active</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/content/characters/{id}</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Персонаж</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">id, name, status</span><span class="ftype"></span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">system_prompt</span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">params_dict</span><span class="ftype">object?</span><span class="fdesc">Известные оператору параметры</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/content/checklists/{id}</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Чек-лист с пунктами</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">id, title</span><span class="ftype"></span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">items</span><span class="ftype">Item[]</span><span class="fdesc">id, title, weight, sort_order, children[] — двухуровневая структура</span></div> </div> </div> </div> </div> <!-- SIMULATOR --> <div class="svc" id="svc-simulator"> <div class="svc-header"><div class="svc-title">Simulator service</div><div class="svc-meta">Диалог, WebSocket, лог сессии · Oracle: SESSIONS, DIALOG_MESSAGES · :8004</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/simulator/sessions</span> <span class="auth-note auth-jwt">JWT operator</span> <span class="ep-summary">Создать сессию</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">scenario_id<span class="req-badge">*</span></span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">character_id</span><span class="ftype">number?</span><span class="fdesc">null если lucky=true</span></div> <div class="field-row"><span class="fname">lucky</span><span class="ftype">bool?</span><span class="fdesc">false (default). Если true — персонаж и опционально сценарий генерируется случайно</span></div> <div class="field-row"><span class="fname">lucky_random_scenario</span><span class="ftype">bool?</span><span class="fdesc">Случайный сценарий тоже (только при lucky=true)</span></div> <div class="field-row"><span class="fname">input_mode<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">text | voice</span></div> </div> <div class="section"><div class="sec-label">Response 201</div> <div class="field-row"><span class="fname">session_id</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">session_uuid</span><span class="ftype">string</span><span class="fdesc">UUID для WebSocket URL</span></div> <div class="field-row"><span class="fname">ws_url</span><span class="ftype">string</span><span class="fdesc">wss://host/ws/{session_uuid}</span></div> <div class="field-row"><span class="fname">character_snapshot</span><span class="ftype">object</span><span class="fdesc">Имя и params_dict персонажа (даже если сгенерирован)</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-ws">WS</span> <span class="ep-path">/ws/{session_uuid}</span> <span class="auth-note auth-jwt">JWT via query ?token=</span> <span class="ep-summary">Диалог в реальном времени</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Client → Server (входящие сообщения)</div> <div class="field-row"><span class="fname">type: operator_message</span><span class="ftype"></span><span class="fdesc">text: string — реплика оператора</span></div> <div class="field-row"><span class="fname">type: audio_chunk</span><span class="ftype"></span><span class="fdesc">data: base64 — фрагмент аудио для STT</span></div> <div class="field-row"><span class="fname">type: ping</span><span class="ftype"></span><span class="fdesc">Keepalive</span></div> </div> <div class="section"><div class="sec-label">Server → Client (исходящие сообщения)</div> <div class="field-row"><span class="fname">type: client_chunk</span><span class="ftype"></span><span class="fdesc">text: string — стриминг ответа LLM</span></div> <div class="field-row"><span class="fname">type: client_done</span><span class="ftype"></span><span class="fdesc">Конец реплики клиента</span></div> <div class="field-row"><span class="fname">type: transcript</span><span class="ftype"></span><span class="fdesc">text: string — результат STT</span></div> <div class="field-row"><span class="fname">type: evaluation_ready</span><span class="ftype"></span><span class="fdesc">score_id: number — оценка готова</span></div> <div class="field-row"><span class="fname">type: error</span><span class="ftype"></span><span class="fdesc">code, message</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/simulator/sessions/{id}/finish</span> <span class="auth-note auth-jwt">JWT operator</span> <span class="ep-summary">Завершить сессию</span> </div> <div class="ep-body"> <div class="note">Закрывает сессию, публикует событие session.finished в EVENTS_QUEUE. Возвращает 200 немедленно, оценка придёт через WS.</div> <div class="section" style="margin-top:6px"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">session_id</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">duration_sec</span><span class="ftype">number</span><span class="fdesc">Длительность сессии</span></div> <div class="field-row"><span class="fname">evaluation_status</span><span class="ftype">string</span><span class="fdesc">"pending" — оценка в очереди</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/simulator/sessions/{id}/report</span> <span class="auth-note auth-jwt">JWT operator</span> <span class="ep-summary">Сообщить о проблеме</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">problem_text<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">Описание проблемы</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r201">201</span><span class="rdesc">Репорт сохранён, уведомление отправлено</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/simulator/sessions/{id}/dialog</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Лог диалога сессии</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">messages</span><span class="ftype">Message[]</span><span class="fdesc">role (operator|client), content, transcript?, created_at</span></div> </div> </div> </div> </div> <!-- EVALUATOR --> <div class="svc" id="svc-evaluator"> <div class="svc-header"><div class="svc-title">Evaluator service</div><div class="svc-meta">LLM-оценка диалога · Oracle: SCORES, SCORE_ITEMS · вызывается асинхронно через EVENTS_QUEUE</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/evaluator/scores/{session_id}</span> <span class="auth-note auth-jwt">JWT</span> <span class="ep-summary">Результат оценки сессии</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">session_id</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">status</span><span class="ftype">string</span><span class="fdesc">pending | processing | done | failed</span></div> <div class="field-row"><span class="fname">total_score</span><span class="ftype">number?</span><span class="fdesc">Итоговый балл (null пока pending)</span></div> <div class="field-row"><span class="fname">max_score</span><span class="ftype">number?</span><span class="fdesc">Максимально возможный</span></div> <div class="field-row"><span class="fname">percent</span><span class="ftype">number?</span><span class="fdesc">total/max × 100</span></div> <div class="field-row"><span class="fname">llm_feedback</span><span class="ftype">string?</span><span class="fdesc">Текстовый фидбэк от LLM</span></div> <div class="field-row"><span class="fname">items</span><span class="ftype">ScoreItem[]</span><span class="fdesc">checklist_item_id, title, is_passed, score_earned, comment</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r200">200</span><span class="rdesc">Оценка найдена (может быть pending)</span></div> <div class="resp-row"><span class="rcode r404">404</span><span class="rdesc">Оценка ещё не создана</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/evaluator/scores/retry/{session_id}</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Повторить оценку</span> </div> <div class="ep-body"> <div class="note">Для случаев когда оценка упала со статусом failed. Перезапускает LLM-workflow для данной сессии.</div> <div class="section" style="margin-top:6px"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r202">202</span><span class="rdesc">Поставлена в очередь повторно</span></div> <div class="resp-row"><span class="rcode r400">400</span><span class="rdesc">Оценка уже done или processing</span></div> </div> </div> </div> </div> <!-- STATS --> <div class="svc" id="svc-stats"> <div class="svc-header"><div class="svc-title">Stats service</div><div class="svc-meta">Аналитика и метрики · только чтение · фильтрация по ролям из JWT</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/stats/overview</span> <span class="auth-note auth-jwt">JWT supervisor+</span> <span class="ep-summary">Сводка: DAU / WAU / MAU, сессии, оценки</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">group_id</span><span class="ftype">number?</span><span class="fdesc">Supervisor видит только свои группы</span></div> <div class="field-row"><span class="fname">date_from / date_to</span><span class="ftype">date?</span><span class="fdesc">ISO 8601, default последние 30 дней</span></div> </div> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">dau / wau / mau</span><span class="ftype">number</span><span class="fdesc">Уникальных активных пользователей</span></div> <div class="field-row"><span class="fname">sessions_total</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">avg_score_percent</span><span class="ftype">number</span><span class="fdesc">Средний % выполнения чек-листа</span></div> <div class="field-row"><span class="fname">avg_duration_sec</span><span class="ftype">number</span><span class="fdesc">Среднее время сессии</span></div> <div class="field-row"><span class="fname">lucky_ratio</span><span class="ftype">number</span><span class="fdesc">Доля сессий с режимом «Мне повезёт»</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/stats/operators</span> <span class="auth-note auth-jwt">JWT supervisor+</span> <span class="ep-summary">Статистика по операторам</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">group_id</span><span class="ftype">number?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">date_from / date_to</span><span class="ftype">date?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">sort_by</span><span class="ftype">string?</span><span class="fdesc">avg_score | sessions_count | last_active</span></div> </div> <div class="section"><div class="sec-label">Response 200 — каждый элемент</div> <div class="field-row"><span class="fname">user_id, full_name</span><span class="ftype"></span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">sessions_count</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">avg_score_percent</span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">score_trend</span><span class="ftype">number</span><span class="fdesc">Δ% vs предыдущий период</span></div> <div class="field-row"><span class="fname">retained</span><span class="ftype">bool</span><span class="fdesc">Был ли активен на прошлой неделе</span></div> <div class="field-row"><span class="fname">last_active</span><span class="ftype">datetime</span><span class="fdesc"></span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/stats/checklist-failures</span> <span class="auth-note auth-jwt">JWT supervisor+</span> <span class="ep-summary">Топ проваленных пунктов чек-листа</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">items</span><span class="ftype">Item[]</span><span class="fdesc">checklist_item_id, title, fail_rate (0–1), fail_count — отсортировано по fail_rate desc</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/stats/me</span> <span class="auth-note auth-jwt">JWT operator</span> <span class="ep-summary">Личная статистика оператора</span> </div> <div class="ep-body"> <div class="note">Оператор видит только свои данные. Те же поля что в /operators, плюс история последних 10 сессий с оценками.</div> </div> </div> </div> <!-- LLM PROXY --> <div class="svc" id="svc-llmproxy"> <div class="svc-header"><div class="svc-title">LLM proxy</div><div class="svc-meta">Единый шлюз к LLM API · вызывается только внутренними сервисами · Oracle: LLM_CONFIG</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/llm/generate</span> <span class="auth-note auth-none">internal only</span> <span class="ep-summary">Генерация ответа LLM</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">config_key<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc">simulator | evaluator | stt — выбирает конфиг из LLM_CONFIG</span></div> <div class="field-row"><span class="fname">messages<span class="req-badge">*</span></span><span class="ftype">Message[]</span><span class="fdesc">role: system|user|assistant, content: string</span></div> <div class="field-row"><span class="fname">stream</span><span class="ftype">bool?</span><span class="fdesc">false (default). true — Server-Sent Events</span></div> <div class="field-row"><span class="fname">max_tokens</span><span class="ftype">number?</span><span class="fdesc">default из конфига</span></div> <div class="field-row"><span class="fname">temperature</span><span class="ftype">number?</span><span class="fdesc">0.0–2.0, default из конфига</span></div> </div> <div class="section"><div class="sec-label">Response 200 (stream=false)</div> <div class="field-row"><span class="fname">content</span><span class="ftype">string</span><span class="fdesc">Ответ модели</span></div> <div class="field-row"><span class="fname">usage</span><span class="ftype">object</span><span class="fdesc">prompt_tokens, completion_tokens</span></div> <div class="field-row"><span class="fname">config_key</span><span class="ftype">string</span><span class="fdesc">Какой конфиг использовался</span></div> </div> <div class="note" style="margin-top:4px">При stream=true — SSE с delta-чанками в формате OpenAI.</div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/llm/configs</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Список конфигураций LLM</span> </div> <div class="ep-body"> <div class="note">API-ключи не возвращаются — только маскированная версия (последние 4 символа). Редактирование через PUT.</div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-put">PUT</span> <span class="ep-path">/llm/configs/{config_key}</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Обновить конфигурацию LLM</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">base_url</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">api_key</span><span class="ftype">string?</span><span class="fdesc">Шифруется перед сохранением</span></div> <div class="field-row"><span class="fname">model_name</span><span class="ftype">string?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">default_max_tokens</span><span class="ftype">number?</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">default_temperature</span><span class="ftype">number?</span><span class="fdesc"></span></div> </div> </div> </div> </div> <!-- NOTIFY --> <div class="svc" id="svc-notify"> <div class="svc-header"><div class="svc-title">Notification service</div><div class="svc-meta">Email-уведомления · Oracle: PROBLEM_REPORTS · вызывается другими сервисами</div></div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/notify/access-request</span> <span class="auth-note auth-none">internal only</span> <span class="ep-summary">Уведомить об új заявке</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">full_name<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">email<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">request_id<span class="req-badge">*</span></span><span class="ftype">number</span><span class="fdesc">ID из ACCESS_REQUESTS</span></div> </div> <div class="section"><div class="sec-label">Статусы</div> <div class="resp-row"><span class="rcode r200">200</span><span class="rdesc">Письмо отправлено</span></div> <div class="resp-row"><span class="rcode r500">500</span><span class="rdesc">Ошибка SMTP — вызывающий сервис логирует и не падает</span></div> </div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-post">POST</span> <span class="ep-path">/notify/problem-report</span> <span class="auth-note auth-none">internal only</span> <span class="ep-summary">Уведомить о репорте проблемы</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Request body</div> <div class="field-row"><span class="fname">session_id<span class="req-badge">*</span></span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">user_id<span class="req-badge">*</span></span><span class="ftype">number</span><span class="fdesc"></span></div> <div class="field-row"><span class="fname">problem_text<span class="req-badge">*</span></span><span class="ftype">string</span><span class="fdesc"></span></div> </div> <div class="note" style="margin-top:4px">Сервис сохраняет запись в PROBLEM_REPORTS и отправляет письмо. Вызов fire-and-forget — Simulator не ждёт ответа.</div> </div> </div> <div class="endpoint"> <div class="ep-head" onclick="tog(this)"> <span class="method m-get">GET</span> <span class="ep-path">/notify/problem-reports</span> <span class="auth-note auth-jwt">JWT admin</span> <span class="ep-summary">Список репортов проблем</span> </div> <div class="ep-body"> <div class="section"><div class="sec-label">Query params</div> <div class="field-row"><span class="fname">status</span><span class="ftype">string?</span><span class="fdesc">new | reviewed | resolved</span></div> <div class="field-row"><span class="fname">date_from / date_to</span><span class="ftype">date?</span><span class="fdesc"></span></div> </div> <div class="section"><div class="sec-label">Response 200</div> <div class="field-row"><span class="fname">items</span><span class="ftype">Report[]</span><span class="fdesc">id, session_id, user_id, problem_text, status, created_at</span></div> </div> </div> </div> </div> </div> <script> function show(id) { document.querySelectorAll('.svc').forEach(s => s.classList.remove('show')); document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.getElementById('svc-' + id).classList.add('show'); event.target.classList.add('active'); } function tog(head) { head.nextElementSibling.classList.toggle('open'); } </script>