каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений, таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет их через JSON Schema в generated.ts, тест ловит забытую регенерацию. Стенд: docker compose postgres + backend, health отвечает, база досягаема из контейнера бэкенда. Makefile не врёт — нереализованные цели падают и называют карточку, которая их закроет. frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.
This commit is contained in:
parent
4425c7d4ca
commit
7b7dd38e9f
36 changed files with 4997 additions and 0 deletions
15
.env.example
Normal file
15
.env.example
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Скопировать в .env. Файл в .gitignore: ключи в репозиторий не едут.
|
||||
DATABASE_URL=postgresql+asyncpg://lct:lct@localhost:5432/lct
|
||||
|
||||
# LLM: провайдер меняется значением, не кодом (docs/arch/STACK.md)
|
||||
LLM_PROVIDER=gigachat
|
||||
LLM_API_KEY=
|
||||
LLM_MODEL_CALLER=
|
||||
LLM_MODEL_JUDGE=
|
||||
JUDGE_TEMPERATURE=0
|
||||
|
||||
# Голосовой контур
|
||||
MODELS_DIR=models
|
||||
STT_MODEL=gigaam-v3-rnnt
|
||||
ENDPOINTING_MS=600
|
||||
OFFLINE=false
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -17,3 +17,6 @@ frontend/dist/
|
|||
# Env
|
||||
.env
|
||||
*.local
|
||||
|
||||
# Python
|
||||
.pytest_cache/
|
||||
|
|
|
|||
48
Makefile
Normal file
48
Makefile
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Подъём стенда и рутина. Цели, которых ещё нет, честно падают
|
||||
# и называют карточку, которая их закрывает, — README не должен врать.
|
||||
|
||||
UV ?= uv
|
||||
COMPOSE ?= docker compose
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
help: ## Список целей
|
||||
@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t22
|
||||
|
||||
dev: ## Поднять стенд: postgres + backend --reload + frontend
|
||||
$(COMPOSE) up --build
|
||||
|
||||
down: ## Погасить стенд
|
||||
$(COMPOSE) down
|
||||
|
||||
back: ## Только бэкенд, локально, без докера
|
||||
cd backend && $(UV) run uvicorn app.main:app --reload --port 8000
|
||||
|
||||
front: ## Только фронтенд, локально
|
||||
npm --prefix frontend install && npm --prefix frontend run dev
|
||||
|
||||
types: ## domain/events.py → frontend/src/shared/types/generated.ts
|
||||
cd backend && $(UV) run --no-project --with 'pydantic>=2.7' python scripts/export_types.py
|
||||
|
||||
test: ## Тесты бэкенда
|
||||
cd backend && $(UV) run --extra dev pytest -q
|
||||
|
||||
typecheck: ## Проверить фронтенд компилятором
|
||||
npm --prefix frontend run typecheck
|
||||
|
||||
models: ## Скачать веса GigaAM (STT) и Silero (TTS) в backend/models/
|
||||
@echo "не реализовано — карточка tasks/lct-02-latency-baseline.md"; exit 1
|
||||
|
||||
seed: ## Залить сценарии из /scenarios в БД
|
||||
@echo "не реализовано — карточка tasks/lct-04-scenario-loader.md"; exit 1
|
||||
|
||||
repl: ## Текстовый диалог со звонящим без голоса
|
||||
@echo "не реализовано — карточка tasks/lct-07-caller-slots.md"; exit 1
|
||||
|
||||
pregen: ## Дерево диалога и WAV первых реплик для офлайна
|
||||
@echo "не реализовано — карточка tasks/lct-19-reference-dialog.md"; exit 1
|
||||
|
||||
demo: ## Поднять всё в демо-режиме: офлайн, прогретые модели
|
||||
@echo "не реализовано — карточка tasks/lct-21-demo-readiness.md"; exit 1
|
||||
|
||||
.PHONY: help dev down back front types test typecheck models seed repl pregen demo
|
||||
6
backend/.dockerignore
Normal file
6
backend/.dockerignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
models/
|
||||
tests/
|
||||
14
backend/Dockerfile
Normal file
14
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
FROM python:3.11-slim
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||
|
||||
WORKDIR /app
|
||||
ENV UV_PROJECT_ENVIRONMENT=/usr/local PYTHONUNBUFFERED=1
|
||||
|
||||
COPY pyproject.toml ./
|
||||
RUN uv pip install --system fastapi "uvicorn[standard]" "pydantic>=2.7" pydantic-settings \
|
||||
"sqlalchemy[asyncio]" alembic asyncpg httpx pyyaml structlog
|
||||
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
40
backend/app/config.py
Normal file
40
backend/app/config.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""Настройки. Нормативы ГОСТ — в миллисекундах и из конфига, не из кода."""
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from app.domain.timers import NORMATIVES, TimerCode
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
# Данные
|
||||
database_url: str = "postgresql+asyncpg://lct:lct@localhost:5432/lct"
|
||||
|
||||
# Голосовой контур
|
||||
models_dir: str = "models"
|
||||
stt_model: str = "gigaam-v3-rnnt"
|
||||
endpointing_ms: int = 600
|
||||
mic_sample_rate: int = 16_000
|
||||
tts_sample_rate: int = 24_000
|
||||
offline: bool = False
|
||||
|
||||
# LLM. Провайдер меняется значением, не кодом (docs/arch/STACK.md).
|
||||
llm_provider: str = "gigachat"
|
||||
llm_api_key: str = ""
|
||||
llm_model_caller: str = ""
|
||||
llm_model_judge: str = ""
|
||||
judge_temperature: float = 0.0
|
||||
|
||||
# Нормативы: переопределяют значения по умолчанию из domain/timers.py
|
||||
timer_limits_ms: dict[TimerCode, int] = {}
|
||||
|
||||
def limit_ms(self, code: TimerCode) -> int:
|
||||
return self.timer_limits_ms.get(code, NORMATIVES[code].limit_ms)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
5
backend/app/domain/__init__.py
Normal file
5
backend/app/domain/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""Общий домен: контракт между бэкендом и фронтендом.
|
||||
|
||||
Правится только по уговору двоих — см. docs/arch/CONTRACT.md.
|
||||
Источник истины для frontend/src/shared/types/generated.ts (make types).
|
||||
"""
|
||||
64
backend/app/domain/classifiers.py
Normal file
64
backend/app/domain/classifiers.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""Классификаторы ДДС, типы происшествий, уровни сложности.
|
||||
|
||||
Норматив: docs/spec/NORMATIVES.md#классификаторы-ддс
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class DDSCode(StrEnum):
|
||||
"""Дежурно-диспетчерская служба, в которую уходит карточка."""
|
||||
|
||||
FIRE = "01"
|
||||
POLICE = "02"
|
||||
MEDICAL = "03"
|
||||
OTHER = "04"
|
||||
GKH = "gkh"
|
||||
|
||||
|
||||
class IncidentType(StrEnum):
|
||||
"""Тип происшествия. Совпадает с полем `type` сценария."""
|
||||
|
||||
FIRE = "fire"
|
||||
MEDICAL = "medical"
|
||||
POLICE = "police"
|
||||
OTHER = "other"
|
||||
GKH = "gkh"
|
||||
ERA_GLONASS = "era_glonass"
|
||||
|
||||
|
||||
class Level(StrEnum):
|
||||
"""Уровень сложности сценария."""
|
||||
|
||||
L1 = "L1"
|
||||
L2 = "L2"
|
||||
L3 = "L3"
|
||||
|
||||
|
||||
DDS_LABELS: dict[DDSCode, str] = {
|
||||
DDSCode.FIRE: "01 — МЧС",
|
||||
DDSCode.POLICE: "02 — полиция",
|
||||
DDSCode.MEDICAL: "03 — скорая помощь",
|
||||
DDSCode.OTHER: "04 — прочие службы",
|
||||
DDSCode.GKH: "ЖКХ",
|
||||
}
|
||||
|
||||
INCIDENT_LABELS: dict[IncidentType, str] = {
|
||||
IncidentType.FIRE: "Пожар",
|
||||
IncidentType.MEDICAL: "Медицинский вызов",
|
||||
IncidentType.POLICE: "Правонарушение",
|
||||
IncidentType.OTHER: "Прочее",
|
||||
IncidentType.GKH: "Авария ЖКХ",
|
||||
IncidentType.ERA_GLONASS: "ДТП, автовызов ЭРА-ГЛОНАСС",
|
||||
}
|
||||
|
||||
#: Штатное соответствие типа происшествия и службы. Используется как значение
|
||||
#: по умолчанию при выводе ground_truth сценария; сценарий вправе переопределить.
|
||||
DDS_BY_INCIDENT: dict[IncidentType, DDSCode] = {
|
||||
IncidentType.FIRE: DDSCode.FIRE,
|
||||
IncidentType.MEDICAL: DDSCode.MEDICAL,
|
||||
IncidentType.POLICE: DDSCode.POLICE,
|
||||
IncidentType.OTHER: DDSCode.OTHER,
|
||||
IncidentType.GKH: DDSCode.GKH,
|
||||
IncidentType.ERA_GLONASS: DDSCode.MEDICAL,
|
||||
}
|
||||
529
backend/app/domain/events.py
Normal file
529
backend/app/domain/events.py
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
"""Схемы событий WebSocket — источник истины для make types.
|
||||
|
||||
Контракт целиком: docs/arch/CONTRACT.md
|
||||
Каналы: /ws/call (курсант), /ws/station (ДДС), /ws/observe (монитор и пульт,
|
||||
только приём), /ws/control (только преподаватель, только передача).
|
||||
|
||||
Правила, зашитые в схемы:
|
||||
* курсанту уходят дельты, наблюдателям — состояние целиком;
|
||||
* все `at` — серверное UTC, фронт время не считает;
|
||||
* ошибки идут одним каналом `error`, без HTTP-кодов внутри WS.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Annotated, Any, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.domain.classifiers import DDSCode, IncidentType, Level
|
||||
from app.domain.kio import KIO
|
||||
from app.domain.taxonomy import Finding
|
||||
from app.domain.timers import TimerSnapshot
|
||||
|
||||
|
||||
class SessionMode(StrEnum):
|
||||
"""Режим сессии. Меняет доступность подсказок и протоколирование,
|
||||
но не поведение звонящего (docs/product/MODES.md)."""
|
||||
|
||||
TRAINING = "training"
|
||||
EXAM = "exam"
|
||||
SELF = "self"
|
||||
|
||||
|
||||
class Speaker(StrEnum):
|
||||
CALLER = "caller"
|
||||
OPERATOR = "operator"
|
||||
|
||||
|
||||
class Mood(StrEnum):
|
||||
"""Состояние звонящего. Фон подачи, не факты."""
|
||||
|
||||
CALM = "calm"
|
||||
WORRIED = "worried"
|
||||
PANIC = "panic"
|
||||
AGGRESSIVE = "aggressive"
|
||||
CONFUSED = "confused"
|
||||
|
||||
|
||||
class CallEndReason(StrEnum):
|
||||
HANGUP = "hangup"
|
||||
DROPPED = "dropped"
|
||||
INSTRUCTOR = "instructor"
|
||||
|
||||
|
||||
class PatchSource(StrEnum):
|
||||
"""`auto` — сервер распознал факт из речи, `operator` — эхо правки курсанта."""
|
||||
|
||||
AUTO = "auto"
|
||||
OPERATOR = "operator"
|
||||
|
||||
|
||||
class DirectiveMode(StrEnum):
|
||||
"""`immediate` — только для обрыва связи: рвёт TTS на полуслове."""
|
||||
|
||||
NEXT_TURN = "next_turn"
|
||||
IMMEDIATE = "immediate"
|
||||
|
||||
|
||||
class TranscriptEntry(BaseModel):
|
||||
"""Реплика в ленте. `ref` — якорь для пометок преподавателя и отметок разбора."""
|
||||
|
||||
ref: str
|
||||
speaker: Speaker
|
||||
text: str
|
||||
at: datetime
|
||||
mood: Mood | None = None
|
||||
|
||||
|
||||
class Coords(BaseModel):
|
||||
lat: float
|
||||
lon: float
|
||||
|
||||
|
||||
# ─────────────────────────── сервер → курсант ───────────────────────────
|
||||
|
||||
|
||||
class CallIncoming(BaseModel):
|
||||
type: Literal["call.incoming"] = "call.incoming"
|
||||
scenario_id: str
|
||||
caller_number: str
|
||||
level: Level
|
||||
mode: SessionMode
|
||||
|
||||
|
||||
class CallStarted(BaseModel):
|
||||
type: Literal["call.started"] = "call.started"
|
||||
started_at: datetime
|
||||
|
||||
|
||||
class SttPartial(BaseModel):
|
||||
type: Literal["stt.partial"] = "stt.partial"
|
||||
text: str
|
||||
|
||||
|
||||
class SttFinal(BaseModel):
|
||||
type: Literal["stt.final"] = "stt.final"
|
||||
text: str
|
||||
at: datetime
|
||||
|
||||
|
||||
class CallerUtterance(BaseModel):
|
||||
type: Literal["caller.utterance"] = "caller.utterance"
|
||||
utterance_id: UUID
|
||||
text: str
|
||||
at: datetime
|
||||
mood: Mood
|
||||
|
||||
|
||||
class TtsBegin(BaseModel):
|
||||
type: Literal["tts.begin"] = "tts.begin"
|
||||
utterance_id: UUID
|
||||
|
||||
|
||||
class TtsEnd(BaseModel):
|
||||
type: Literal["tts.end"] = "tts.end"
|
||||
utterance_id: UUID
|
||||
|
||||
|
||||
class TtsCancel(BaseModel):
|
||||
"""Оператор перебил. Фронт мгновенно чистит очередь воспроизведения."""
|
||||
|
||||
type: Literal["tts.cancel"] = "tts.cancel"
|
||||
utterance_id: UUID
|
||||
reason: Literal["barge_in", "director"] = "barge_in"
|
||||
|
||||
|
||||
class BgStart(BaseModel):
|
||||
"""Аудио-фон происшествия. Файл лежит в сборке фронта, трафика фон не создаёт."""
|
||||
|
||||
type: Literal["bg.start"] = "bg.start"
|
||||
loop: str
|
||||
gain_db: float
|
||||
|
||||
|
||||
class BgStop(BaseModel):
|
||||
type: Literal["bg.stop"] = "bg.stop"
|
||||
|
||||
|
||||
class EraData(BaseModel):
|
||||
"""Автоданные ЭРА-ГЛОНАСС до соединения с водителем."""
|
||||
|
||||
type: Literal["era.data"] = "era.data"
|
||||
vin: str
|
||||
coords: Coords
|
||||
passengers: int
|
||||
impact_force: str
|
||||
|
||||
|
||||
class KioPatchOut(BaseModel):
|
||||
"""Сервер распознал факт из речи либо шлёт эхо чужой правки."""
|
||||
|
||||
type: Literal["kio.patch"] = "kio.patch"
|
||||
fields: dict[str, Any]
|
||||
source: PatchSource
|
||||
|
||||
|
||||
class HintShown(BaseModel):
|
||||
type: Literal["hint.shown"] = "hint.shown"
|
||||
checklist_id: str
|
||||
question: str
|
||||
|
||||
|
||||
class TimerTick(BaseModel):
|
||||
"""Раз в секунду, не на каждое изменение: таймеров дюжина, UI рисует секунды."""
|
||||
|
||||
type: Literal["timer.tick"] = "timer.tick"
|
||||
timers: list[TimerSnapshot]
|
||||
|
||||
|
||||
class CallEnded(BaseModel):
|
||||
type: Literal["call.ended"] = "call.ended"
|
||||
reason: CallEndReason
|
||||
|
||||
|
||||
class ScoreReady(BaseModel):
|
||||
type: Literal["score.ready"] = "score.ready"
|
||||
session_id: UUID
|
||||
|
||||
|
||||
class ErrorEvent(BaseModel):
|
||||
type: Literal["error"] = "error"
|
||||
code: str
|
||||
message: str
|
||||
|
||||
|
||||
ServerToTrainee = Annotated[
|
||||
CallIncoming
|
||||
| CallStarted
|
||||
| SttPartial
|
||||
| SttFinal
|
||||
| CallerUtterance
|
||||
| TtsBegin
|
||||
| TtsEnd
|
||||
| TtsCancel
|
||||
| BgStart
|
||||
| BgStop
|
||||
| EraData
|
||||
| KioPatchOut
|
||||
| HintShown
|
||||
| TimerTick
|
||||
| CallEnded
|
||||
| ScoreReady
|
||||
| ErrorEvent,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# ─────────────────────────── курсант → сервер ───────────────────────────
|
||||
|
||||
|
||||
class CallAnswer(BaseModel):
|
||||
"""Снял гарнитуру. Останавливает норматив `answer`."""
|
||||
|
||||
type: Literal["call.answer"] = "call.answer"
|
||||
|
||||
|
||||
class KioPatchIn(BaseModel):
|
||||
"""Правка карточки. Дебаунс 300 мс, шлётся только дельта."""
|
||||
|
||||
type: Literal["kio.patch"] = "kio.patch"
|
||||
fields: dict[str, Any]
|
||||
|
||||
|
||||
class HintRequest(BaseModel):
|
||||
"""В режиме `exam` сервер отвечает событием `error`."""
|
||||
|
||||
type: Literal["hint.request"] = "hint.request"
|
||||
|
||||
|
||||
class SelfAssessmentSubmit(BaseModel):
|
||||
"""Самооценка до показа автооценки."""
|
||||
|
||||
type: Literal["self_assessment.submit"] = "self_assessment.submit"
|
||||
missed: list[str]
|
||||
comment: str = ""
|
||||
|
||||
|
||||
class DdsDispatch(BaseModel):
|
||||
"""Передача в ДДС. Замораживает карточку снимком и останавливает `dds_notify`."""
|
||||
|
||||
type: Literal["dds.dispatch"] = "dds.dispatch"
|
||||
service: DDSCode
|
||||
|
||||
|
||||
class CallHangup(BaseModel):
|
||||
type: Literal["call.hangup"] = "call.hangup"
|
||||
|
||||
|
||||
class CallbackDial(BaseModel):
|
||||
"""Обратный дозвон после обрыва: 3 попытки по 10 с."""
|
||||
|
||||
type: Literal["callback.dial"] = "callback.dial"
|
||||
|
||||
|
||||
TraineeToServer = Annotated[
|
||||
CallAnswer
|
||||
| KioPatchIn
|
||||
| HintRequest
|
||||
| SelfAssessmentSubmit
|
||||
| DdsDispatch
|
||||
| CallHangup
|
||||
| CallbackDial,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# ───────────────────────── сервер → наблюдателям ─────────────────────────
|
||||
|
||||
|
||||
class SessionSnapshot(BaseModel):
|
||||
"""Обязательно при подключении: монитор в классе включают посреди занятия."""
|
||||
|
||||
type: Literal["session.snapshot"] = "session.snapshot"
|
||||
session_id: UUID
|
||||
scenario_id: str
|
||||
scenario_title: str
|
||||
level: Level
|
||||
mode: SessionMode
|
||||
trainee_name: str | None = None
|
||||
started_at: datetime | None = None
|
||||
kio: KIO
|
||||
transcript: list[TranscriptEntry]
|
||||
timers: list[TimerSnapshot]
|
||||
hints_used: int = 0
|
||||
ended: bool = False
|
||||
|
||||
|
||||
class TranscriptAppend(BaseModel):
|
||||
type: Literal["transcript.append"] = "transcript.append"
|
||||
entry: TranscriptEntry
|
||||
|
||||
|
||||
class KioState(BaseModel):
|
||||
"""Полная карточка, не дельта: наблюдателю проще, рассинхрон дороже килобайт."""
|
||||
|
||||
type: Literal["kio.state"] = "kio.state"
|
||||
kio: KIO
|
||||
|
||||
|
||||
class ModeSet(BaseModel):
|
||||
type: Literal["mode.set"] = "mode.set"
|
||||
mode: SessionMode
|
||||
|
||||
|
||||
class SessionEnded(BaseModel):
|
||||
type: Literal["session.ended"] = "session.ended"
|
||||
reason: CallEndReason
|
||||
|
||||
|
||||
class InstructorNoteShown(BaseModel):
|
||||
"""Пометка преподавателя видна всем, кому виден транскрипт."""
|
||||
|
||||
type: Literal["instructor_note.shown"] = "instructor_note.shown"
|
||||
transcript_ref: str
|
||||
text: str
|
||||
author: str
|
||||
|
||||
|
||||
class ReferenceStarted(BaseModel):
|
||||
"""Автопроигрывание эталонного звонка на внешнем мониторе."""
|
||||
|
||||
type: Literal["reference.started"] = "reference.started"
|
||||
scenario_id: str
|
||||
|
||||
|
||||
ServerToObserver = Annotated[
|
||||
SessionSnapshot
|
||||
| TranscriptAppend
|
||||
| KioState
|
||||
| TimerTick
|
||||
| ModeSet
|
||||
| HintShown
|
||||
| BgStart
|
||||
| BgStop
|
||||
| CallerUtterance
|
||||
| SessionEnded
|
||||
| ScoreReady
|
||||
| InstructorNoteShown
|
||||
| ReferenceStarted
|
||||
| ErrorEvent,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# ──────────────────── преподаватель → сервер (control) ────────────────────
|
||||
|
||||
|
||||
class ScenarioStart(BaseModel):
|
||||
type: Literal["scenario.start"] = "scenario.start"
|
||||
scenario_id: str
|
||||
trainee: str
|
||||
group_id: str | None = None
|
||||
mode: SessionMode
|
||||
|
||||
|
||||
class DirectorInject(BaseModel):
|
||||
"""Директива звонящему. Карточку курсанта не трогает ни одна команда."""
|
||||
|
||||
type: Literal["director.inject"] = "director.inject"
|
||||
directive: str
|
||||
mode: DirectiveMode = DirectiveMode.NEXT_TURN
|
||||
|
||||
|
||||
class ReferencePlay(BaseModel):
|
||||
type: Literal["reference.play"] = "reference.play"
|
||||
|
||||
|
||||
class InstructorNoteAdd(BaseModel):
|
||||
type: Literal["instructor_note.add"] = "instructor_note.add"
|
||||
transcript_ref: str
|
||||
text: str
|
||||
|
||||
|
||||
class ScoreOverride(BaseModel):
|
||||
"""Коррекция оценки. Автооценка сохраняется рядом."""
|
||||
|
||||
type: Literal["score.override"] = "score.override"
|
||||
session_id: UUID
|
||||
verdict: str
|
||||
comment: str
|
||||
|
||||
|
||||
class ScenarioPublish(BaseModel):
|
||||
type: Literal["scenario.publish"] = "scenario.publish"
|
||||
scenario_id: str
|
||||
|
||||
|
||||
class SessionStop(BaseModel):
|
||||
type: Literal["session.stop"] = "session.stop"
|
||||
|
||||
|
||||
InstructorToServer = Annotated[
|
||||
ScenarioStart
|
||||
| DirectorInject
|
||||
| ReferencePlay
|
||||
| InstructorNoteAdd
|
||||
| ScoreOverride
|
||||
| ScenarioPublish
|
||||
| SessionStop,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# ───────────────────────────── станция ДДС ─────────────────────────────
|
||||
|
||||
|
||||
class CardReceived(BaseModel):
|
||||
"""Снимок КИО. После передачи не меняется — оператор не дописывает задним числом."""
|
||||
|
||||
type: Literal["card.received"] = "card.received"
|
||||
card: KIO
|
||||
from_operator: str
|
||||
at: datetime
|
||||
|
||||
|
||||
class CardAck(BaseModel):
|
||||
"""Останавливает норматив `dds_ack` (≤ 4 с)."""
|
||||
|
||||
type: Literal["card.ack"] = "card.ack"
|
||||
|
||||
|
||||
class CardBounce(BaseModel):
|
||||
"""Возврат на уточнение: неполнота КИО становится сорванным выездом."""
|
||||
|
||||
type: Literal["card.bounce"] = "card.bounce"
|
||||
missing_fields: list[str]
|
||||
comment: str = ""
|
||||
|
||||
|
||||
class ZoneDecision(BaseModel):
|
||||
type: Literal["zone.decision"] = "zone.decision"
|
||||
in_zone: bool
|
||||
|
||||
|
||||
class CrewDispatched(BaseModel):
|
||||
type: Literal["crew.dispatched"] = "crew.dispatched"
|
||||
at: datetime
|
||||
|
||||
|
||||
class CrewArrived(BaseModel):
|
||||
type: Literal["crew.arrived"] = "crew.arrived"
|
||||
at: datetime
|
||||
|
||||
|
||||
ServerToStation = Annotated[
|
||||
CardReceived | TimerTick | SessionEnded | ErrorEvent,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
StationToServer = Annotated[
|
||||
CardAck | CardBounce | ZoneDecision | CrewDispatched | CrewArrived,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# ─────────────────────────── отчёт по HTTP ───────────────────────────
|
||||
|
||||
|
||||
class Metric(BaseModel):
|
||||
"""Метрика оценки: факт против норматива со ссылкой. Не балл, а обоснование."""
|
||||
|
||||
key: str
|
||||
title: str
|
||||
fact: str
|
||||
norm: str
|
||||
ref: str | None = None
|
||||
passed: bool
|
||||
weight: float = 1.0
|
||||
|
||||
|
||||
class CompetencyScore(BaseModel):
|
||||
competency: str
|
||||
value: float
|
||||
|
||||
|
||||
class HintUsage(BaseModel):
|
||||
checklist_id: str
|
||||
question: str
|
||||
at: datetime
|
||||
|
||||
|
||||
class SelfAssessment(BaseModel):
|
||||
missed: list[str]
|
||||
comment: str = ""
|
||||
submitted_at: datetime
|
||||
|
||||
|
||||
class SessionReport(BaseModel):
|
||||
"""Единица истории: из отчётов складываются профиль, дельта попыток, аналитика."""
|
||||
|
||||
session_id: UUID
|
||||
scenario_id: str
|
||||
mode: SessionMode
|
||||
attempt: int = 1
|
||||
transcript: list[TranscriptEntry]
|
||||
findings: list[Finding]
|
||||
metrics: list[Metric]
|
||||
competencies: list[CompetencyScore]
|
||||
reference_questions: list[HintShown]
|
||||
missed_checklist: list[str]
|
||||
hints_used: list[HintUsage]
|
||||
self_assessment: SelfAssessment | None = None
|
||||
score_auto: float
|
||||
score_final: float
|
||||
overridden_by: str | None = None
|
||||
override_comment: str | None = None
|
||||
|
||||
|
||||
class EventCatalog(BaseModel):
|
||||
"""Единственное назначение — собрать все союзы в одну JSON Schema для make types."""
|
||||
|
||||
server_to_trainee: ServerToTrainee
|
||||
trainee_to_server: TraineeToServer
|
||||
server_to_observer: ServerToObserver
|
||||
instructor_to_server: InstructorToServer
|
||||
server_to_station: ServerToStation
|
||||
station_to_server: StationToServer
|
||||
session_report: SessionReport
|
||||
151
backend/app/domain/kio.py
Normal file
151
backend/app/domain/kio.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
"""Карточка информационного обмена (КИО).
|
||||
|
||||
Это не дизайн формы, это норматив: docs/spec/NORMATIVES.md#карточка-информационного-обмена-кио
|
||||
Обязательность полей задаётся сценарием (`required_fields`), а не моделью:
|
||||
в ложном вызове число пострадавших не требуется, в ЧС требуется.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.domain.classifiers import DDSCode, IncidentType
|
||||
|
||||
|
||||
class ResponseStatus(StrEnum):
|
||||
REGISTERED = "registered"
|
||||
TRANSFERRED = "transferred"
|
||||
IN_PROGRESS = "in_progress"
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
class FireDetails(BaseModel):
|
||||
"""Уточняющие поля 01 — МЧС."""
|
||||
|
||||
fire_nature: str | None = None
|
||||
object_kind: str | None = None
|
||||
floors: int | None = None
|
||||
gasified: bool | None = None
|
||||
people_inside: bool | None = None
|
||||
|
||||
|
||||
class PoliceDetails(BaseModel):
|
||||
"""Уточняющие поля 02 — полиция."""
|
||||
|
||||
offence_kind: str | None = None
|
||||
suspects: str | None = None
|
||||
vehicle: str | None = None
|
||||
|
||||
|
||||
class MedicalDetails(BaseModel):
|
||||
"""Уточняющие поля 03 — медицина."""
|
||||
|
||||
reason: str | None = None
|
||||
conscious: bool | None = None
|
||||
breathing: bool | None = None
|
||||
can_move: bool | None = None
|
||||
age: int | None = None
|
||||
|
||||
|
||||
class UtilityDetails(BaseModel):
|
||||
"""Уточняющие поля 04 / ЖКХ."""
|
||||
|
||||
failure_kind: str | None = None
|
||||
scale: str | None = None
|
||||
threat_to_residents: bool | None = None
|
||||
|
||||
|
||||
class KIO(BaseModel):
|
||||
"""Полная карточка. Наблюдателям уходит целиком (`kio.state`),
|
||||
курсанту — дельтой (`kio.patch`)."""
|
||||
|
||||
# Служебное — заполняется системой
|
||||
card_id: UUID = Field(default_factory=uuid4)
|
||||
registered_at: datetime | None = None
|
||||
response_status: ResponseStatus = ResponseStatus.REGISTERED
|
||||
|
||||
# Звонящий
|
||||
caller_number: str | None = None
|
||||
caller_name: str | None = None
|
||||
caller_contact: str | None = None
|
||||
language: str = "ru"
|
||||
|
||||
# Место
|
||||
okato: str | None = None
|
||||
address: str | None = None
|
||||
street: str | None = None
|
||||
building: str | None = None
|
||||
entrance: str | None = None
|
||||
floor: str | None = None
|
||||
coords: tuple[float, float] | None = None
|
||||
|
||||
# Происшествие
|
||||
incident_type: IncidentType | None = None
|
||||
description: str | None = None
|
||||
victims_count: int | None = None
|
||||
is_emergency: bool = False
|
||||
life_threat: bool = False
|
||||
evacuation_needed: bool = False
|
||||
|
||||
# ДДС
|
||||
dds: DDSCode | None = None
|
||||
dispatch_order_at: datetime | None = None
|
||||
arrival_at: datetime | None = None
|
||||
|
||||
# Уточняющие по службе
|
||||
fire: FireDetails | None = None
|
||||
police: PoliceDetails | None = None
|
||||
medical: MedicalDetails | None = None
|
||||
utility: UtilityDetails | None = None
|
||||
|
||||
|
||||
#: Поля, которые курсант не редактирует: их проставляет система.
|
||||
READ_ONLY_FIELDS: frozenset[str] = frozenset(
|
||||
{"card_id", "registered_at", "caller_number", "response_status"}
|
||||
)
|
||||
|
||||
|
||||
def get_field(card: KIO, path: str) -> Any:
|
||||
"""Значение поля по пути вида `floor` или `fire.floors`."""
|
||||
value: Any = card
|
||||
for part in path.split("."):
|
||||
if value is None:
|
||||
return None
|
||||
value = getattr(value, part, None)
|
||||
return value
|
||||
|
||||
|
||||
def missing_fields(card: KIO, required: list[str]) -> list[str]:
|
||||
"""Незаполненные обязательные поля — основание отметки E5.
|
||||
|
||||
Обязательность приходит из сценария, пустой список означает,
|
||||
что сценарий требований к карточке не предъявляет.
|
||||
"""
|
||||
empty: list[str] = []
|
||||
for path in required:
|
||||
value = get_field(card, path)
|
||||
if value is None or (isinstance(value, str) and not value.strip()):
|
||||
empty.append(path)
|
||||
return empty
|
||||
|
||||
|
||||
def apply_patch(card: KIO, fields: dict[str, Any]) -> KIO:
|
||||
"""Применить дельту `kio.patch`. Служебные поля игнорируются.
|
||||
|
||||
Вложенные поля приходят плоским путём: {"fire.floors": 5}.
|
||||
"""
|
||||
data = card.model_dump()
|
||||
for path, value in fields.items():
|
||||
if path in READ_ONLY_FIELDS:
|
||||
continue
|
||||
head, _, tail = path.partition(".")
|
||||
if not tail:
|
||||
data[head] = value
|
||||
continue
|
||||
nested = data.get(head) or {}
|
||||
nested[tail] = value
|
||||
data[head] = nested
|
||||
return KIO.model_validate(data)
|
||||
114
backend/app/domain/taxonomy.py
Normal file
114
backend/app/domain/taxonomy.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"""Таксономия ошибок E1–E6 и компетенции.
|
||||
|
||||
Методика: docs/product/METHODOLOGY.md#таксономия-ошибок
|
||||
Правило: каждая отметка несёт код и обоснование. Отметок без объяснения
|
||||
не появляется ни в одном интерфейсе.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ErrorCode(StrEnum):
|
||||
E1 = "E1"
|
||||
E2 = "E2"
|
||||
E3 = "E3"
|
||||
E4 = "E4"
|
||||
E5 = "E5"
|
||||
E6 = "E6"
|
||||
|
||||
|
||||
class FindingSource(StrEnum):
|
||||
"""Кто выставил отметку. `judge` — единственный недетерминированный источник."""
|
||||
|
||||
SLOTS = "slots"
|
||||
GROUND_TRUTH = "ground_truth"
|
||||
TIMERS = "timers"
|
||||
KIO = "kio"
|
||||
CHAIN = "chain"
|
||||
JUDGE = "judge"
|
||||
INSTRUCTOR = "instructor"
|
||||
|
||||
|
||||
class ErrorSpec(BaseModel):
|
||||
code: ErrorCode
|
||||
title: str
|
||||
detail: str
|
||||
source: FindingSource
|
||||
|
||||
|
||||
ERRORS: dict[ErrorCode, ErrorSpec] = {
|
||||
ErrorCode.E1: ErrorSpec(
|
||||
code=ErrorCode.E1,
|
||||
title="Пропущенный факт",
|
||||
detail="Обязательный вопрос не прозвучал, факт не добыт",
|
||||
source=FindingSource.SLOTS,
|
||||
),
|
||||
ErrorCode.E2: ErrorSpec(
|
||||
code=ErrorCode.E2,
|
||||
title="Неверная классификация или маршрутизация",
|
||||
detail="Тип происшествия или ДДС определены неверно",
|
||||
source=FindingSource.GROUND_TRUTH,
|
||||
),
|
||||
ErrorCode.E3: ErrorSpec(
|
||||
code=ErrorCode.E3,
|
||||
title="Нарушение временного норматива",
|
||||
detail="Опрос дольше 75 с, ДДС не оповещена за 60 с и т. д.",
|
||||
source=FindingSource.TIMERS,
|
||||
),
|
||||
ErrorCode.E4: ErrorSpec(
|
||||
code=ErrorCode.E4,
|
||||
title="Коммуникативная ошибка",
|
||||
detail="Тон, эмпатия, управление диалогом, лишние вопросы, игнорирование паники",
|
||||
source=FindingSource.JUDGE,
|
||||
),
|
||||
ErrorCode.E5: ErrorSpec(
|
||||
code=ErrorCode.E5,
|
||||
title="Неполнота карточки",
|
||||
detail="Обязательные поля КИО пусты или заполнены неверно",
|
||||
source=FindingSource.KIO,
|
||||
),
|
||||
ErrorCode.E6: ErrorSpec(
|
||||
code=ErrorCode.E6,
|
||||
title="Неверное завершение",
|
||||
detail="Карточка ушла в ДДС непригодной, снятие с контроля до подтверждения",
|
||||
source=FindingSource.CHAIN,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class Competency(StrEnum):
|
||||
"""Шесть осей радара. Проекция уже посчитанных метрик, без пересчёта весов."""
|
||||
|
||||
INTAKE = "intake"
|
||||
INTERVIEW = "interview"
|
||||
CARD = "card"
|
||||
ROUTING = "routing"
|
||||
NORMS = "norms"
|
||||
COMMUNICATION = "communication"
|
||||
|
||||
|
||||
COMPETENCY_LABELS: dict[Competency, str] = {
|
||||
Competency.INTAKE: "Приём вызова",
|
||||
Competency.INTERVIEW: "Опрос и добыча фактов",
|
||||
Competency.CARD: "Заполнение КИО",
|
||||
Competency.ROUTING: "Классификация и маршрутизация",
|
||||
Competency.NORMS: "Соблюдение нормативов",
|
||||
Competency.COMMUNICATION: "Коммуникация в стрессе",
|
||||
}
|
||||
|
||||
|
||||
class Finding(BaseModel):
|
||||
"""Отметка в разборе. `fact` и `norm` — то самое обоснование."""
|
||||
|
||||
code: ErrorCode
|
||||
source: FindingSource
|
||||
summary: str
|
||||
fact: str
|
||||
norm: str | None = None
|
||||
ref: str | None = None
|
||||
competency: Competency | None = None
|
||||
transcript_ref: str | None = None
|
||||
at: datetime | None = None
|
||||
95
backend/app/domain/timers.py
Normal file
95
backend/app/domain/timers.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
"""Таймеры ГОСТ Р 22.7.03-2021: коды, нормативы, состояние.
|
||||
|
||||
Норматив: docs/spec/NORMATIVES.md#временные-нормативы-приёма-вызова
|
||||
Правило: таймеры останавливаются событиями, а не таймаутами
|
||||
(docs/arch/CONTRACT.md) — иначе метрика недоказуема.
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
GOST_REF = "ГОСТ Р 22.7.03-2021"
|
||||
|
||||
|
||||
class TimerCode(StrEnum):
|
||||
"""Коды одинаковы на бэке, фронте и в отчёте."""
|
||||
|
||||
ANSWER = "answer"
|
||||
INTERVIEW = "interview"
|
||||
DDS_NOTIFY = "dds_notify"
|
||||
DDS_ACK = "dds_ack"
|
||||
ZONE_CHECK = "zone_check"
|
||||
CALLBACK = "callback"
|
||||
CLOSE = "close"
|
||||
|
||||
|
||||
class TimerState(StrEnum):
|
||||
"""Единственное место, где цвет несёт смысл (docs/arch/FRONTEND.md)."""
|
||||
|
||||
OK = "ok"
|
||||
WARN = "warn"
|
||||
VIOLATED = "violated"
|
||||
|
||||
|
||||
class Normative(BaseModel):
|
||||
"""Норматив одной операции приёма вызова."""
|
||||
|
||||
code: TimerCode
|
||||
title: str
|
||||
limit_ms: int
|
||||
attempts: int = 1
|
||||
ref: str = GOST_REF
|
||||
|
||||
|
||||
#: Значения по умолчанию. Рабочие лимиты берутся из конфига (app.config),
|
||||
#: не из кода — норматив может отличаться у заказчика.
|
||||
NORMATIVES: dict[TimerCode, Normative] = {
|
||||
TimerCode.ANSWER: Normative(
|
||||
code=TimerCode.ANSWER, title="Ответ на звонок", limit_ms=8_000
|
||||
),
|
||||
TimerCode.INTERVIEW: Normative(
|
||||
code=TimerCode.INTERVIEW, title="Опрос / идентификация ситуации", limit_ms=75_000
|
||||
),
|
||||
TimerCode.DDS_NOTIFY: Normative(
|
||||
code=TimerCode.DDS_NOTIFY, title="Оповещение ДДС", limit_ms=60_000
|
||||
),
|
||||
TimerCode.DDS_ACK: Normative(
|
||||
code=TimerCode.DDS_ACK, title="Подтверждение получения карточки", limit_ms=4_000
|
||||
),
|
||||
TimerCode.ZONE_CHECK: Normative(
|
||||
code=TimerCode.ZONE_CHECK, title="Проверка зоны ответственности", limit_ms=30_000
|
||||
),
|
||||
TimerCode.CALLBACK: Normative(
|
||||
code=TimerCode.CALLBACK,
|
||||
title="Обратный дозвон при обрыве",
|
||||
limit_ms=10_000,
|
||||
attempts=3,
|
||||
),
|
||||
TimerCode.CLOSE: Normative(
|
||||
code=TimerCode.CLOSE, title="Снятие с контроля", limit_ms=300_000
|
||||
),
|
||||
}
|
||||
|
||||
#: Доля норматива, после которой таймер жёлтый.
|
||||
WARN_RATIO = 0.8
|
||||
|
||||
|
||||
def state_for(elapsed_ms: int, limit_ms: int) -> TimerState:
|
||||
"""Состояние таймера по факту и нормативу."""
|
||||
if elapsed_ms > limit_ms:
|
||||
return TimerState.VIOLATED
|
||||
if elapsed_ms >= limit_ms * WARN_RATIO:
|
||||
return TimerState.WARN
|
||||
return TimerState.OK
|
||||
|
||||
|
||||
class TimerSnapshot(BaseModel):
|
||||
"""Один таймер в событии `timer.tick`."""
|
||||
|
||||
code: TimerCode
|
||||
elapsed_ms: int
|
||||
limit_ms: int
|
||||
state: TimerState
|
||||
attempt: int = Field(default=1, description="Номер попытки для callback")
|
||||
stopped: bool = Field(default=False, description="Остановлен событием, не тикает")
|
||||
28
backend/app/main.py
Normal file
28
backend/app/main.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""Сборка приложения. Роутеры подключаются по мере готовности — см. tasks/."""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Прогрев моделей и валидация сценариев — карточки lct-04 и lct-06.
|
||||
app.state.models_ready = False
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="Учебный симулятор занятия для системы 112", lifespan=lifespan)
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health() -> dict:
|
||||
"""Готовность стенда. Фронт показывает экран «модели прогреваются»."""
|
||||
settings = get_settings()
|
||||
return {
|
||||
"status": "ok",
|
||||
"models_ready": getattr(app.state, "models_ready", False),
|
||||
"offline": settings.offline,
|
||||
}
|
||||
4
backend/conftest.py
Normal file
4
backend/conftest.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
40
backend/pyproject.toml
Normal file
40
backend/pyproject.toml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[project]
|
||||
name = "lct-backend"
|
||||
version = "0.1.0"
|
||||
description = "Учебный симулятор занятия для системы 112 — бэкенд"
|
||||
requires-python = ">=3.11,<3.12"
|
||||
dependencies = [
|
||||
"fastapi>=0.115",
|
||||
"uvicorn[standard]>=0.30",
|
||||
"pydantic>=2.7",
|
||||
"pydantic-settings>=2.3",
|
||||
"sqlalchemy[asyncio]>=2.0",
|
||||
"alembic>=1.13",
|
||||
"asyncpg>=0.29",
|
||||
"httpx>=0.27",
|
||||
"pyyaml>=6.0",
|
||||
"structlog>=24.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Тяжёлый слой моделей — ставится отдельно: uv sync --extra voice
|
||||
voice = [
|
||||
"onnx-asr>=0.6",
|
||||
"onnxruntime>=1.18",
|
||||
"torch>=2.2",
|
||||
"numpy>=1.26",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=8.2",
|
||||
"pytest-asyncio>=0.23",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["app"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
0
backend/scripts/__init__.py
Normal file
0
backend/scripts/__init__.py
Normal file
159
backend/scripts/export_types.py
Normal file
159
backend/scripts/export_types.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
"""make types: app/domain/events.py → frontend/src/shared/types/generated.ts
|
||||
|
||||
Единственный шов между треками генерируется, а не переписывается руками
|
||||
(docs/arch/CONTRACT.md). Запускается без тяжёлых зависимостей — нужен только
|
||||
pydantic, поэтому годится и в pre-commit, и в CI.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from app.domain.events import EventCatalog # noqa: E402
|
||||
|
||||
OUT = ROOT.parent / "frontend" / "src" / "shared" / "types" / "generated.ts"
|
||||
|
||||
HEADER = """// Сгенерировано `make types` из backend/app/domain/events.py.
|
||||
// Руками не править: правка уедет при следующей генерации.
|
||||
// Контракт: docs/arch/CONTRACT.md
|
||||
"""
|
||||
|
||||
|
||||
def pascal(name: str) -> str:
|
||||
return "".join(part.capitalize() for part in name.split("_"))
|
||||
|
||||
|
||||
def literal(value: Any) -> str:
|
||||
if isinstance(value, str):
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
if value is None:
|
||||
return "null"
|
||||
if isinstance(value, bool):
|
||||
return "true" if value else "false"
|
||||
return str(value)
|
||||
|
||||
|
||||
def ts_type(node: Any) -> str:
|
||||
"""JSON Schema → выражение типа TypeScript."""
|
||||
if node is True or node is None:
|
||||
return "unknown"
|
||||
if node is False:
|
||||
return "never"
|
||||
if "$ref" in node:
|
||||
return node["$ref"].rsplit("/", 1)[-1]
|
||||
if "allOf" in node and len(node["allOf"]) == 1:
|
||||
return ts_type(node["allOf"][0])
|
||||
if "anyOf" in node:
|
||||
return " | ".join(dict.fromkeys(ts_type(item) for item in node["anyOf"]))
|
||||
if "oneOf" in node:
|
||||
return " | ".join(dict.fromkeys(ts_type(item) for item in node["oneOf"]))
|
||||
if "const" in node:
|
||||
return literal(node["const"])
|
||||
if "enum" in node:
|
||||
return " | ".join(literal(value) for value in node["enum"])
|
||||
|
||||
kind = node.get("type")
|
||||
if kind == "array":
|
||||
if "prefixItems" in node:
|
||||
return "[" + ", ".join(ts_type(item) for item in node["prefixItems"]) + "]"
|
||||
return f"Array<{ts_type(node.get('items'))}>"
|
||||
if kind == "object":
|
||||
extra = node.get("additionalProperties")
|
||||
if "properties" in node:
|
||||
return inline_object(node)
|
||||
return f"Record<string, {ts_type(extra)}>"
|
||||
if kind == "string":
|
||||
return "string"
|
||||
if kind in ("integer", "number"):
|
||||
return "number"
|
||||
if kind == "boolean":
|
||||
return "boolean"
|
||||
if kind == "null":
|
||||
return "null"
|
||||
if isinstance(kind, list):
|
||||
return " | ".join(ts_type({"type": item}) for item in kind)
|
||||
return "unknown"
|
||||
|
||||
|
||||
def fields(node: dict[str, Any], indent: str = " ") -> list[str]:
|
||||
required = set(node.get("required", []))
|
||||
lines: list[str] = []
|
||||
for name, prop in node.get("properties", {}).items():
|
||||
# Дискриминатор всегда присутствует в сериализованном событии,
|
||||
# иначе TypeScript не сузит союз по полю `type`.
|
||||
optional = "" if name in required or "const" in prop else "?"
|
||||
description = prop.get("description")
|
||||
if description:
|
||||
lines.append(f"{indent}/** {description} */")
|
||||
lines.append(f"{indent}{name}{optional}: {ts_type(prop)};")
|
||||
return lines
|
||||
|
||||
|
||||
def inline_object(node: dict[str, Any]) -> str:
|
||||
return "{ " + " ".join(line.strip() for line in fields(node, indent="")) + " }"
|
||||
|
||||
|
||||
def render_def(name: str, node: dict[str, Any]) -> str:
|
||||
doc = node.get("description", "").strip()
|
||||
comment = f"/** {doc} */\n" if doc else ""
|
||||
if "enum" in node and "properties" not in node:
|
||||
values = " | ".join(literal(value) for value in node["enum"])
|
||||
return f"{comment}export type {name} = {values};"
|
||||
if "properties" not in node:
|
||||
return f"{comment}export type {name} = {ts_type(node)};"
|
||||
body = "\n".join(fields(node))
|
||||
return f"{comment}export interface {name} {{\n{body}\n}}"
|
||||
|
||||
|
||||
def event_names(defs: dict[str, Any]) -> list[str]:
|
||||
names: list[str] = []
|
||||
for node in defs.values():
|
||||
prop = node.get("properties", {}).get("type", {})
|
||||
value = prop.get("const")
|
||||
if value is None and isinstance(prop.get("enum"), list) and len(prop["enum"]) == 1:
|
||||
value = prop["enum"][0]
|
||||
if isinstance(value, str) and value not in names:
|
||||
names.append(value)
|
||||
return sorted(names)
|
||||
|
||||
|
||||
def render() -> str:
|
||||
"""Готовый текст generated.ts. Вынесено отдельно, чтобы тест мог
|
||||
сверить файл в репозитории с моделями и поймать забытый make types."""
|
||||
schema = EventCatalog.model_json_schema(ref_template="#/$defs/{model}")
|
||||
defs: dict[str, Any] = schema.get("$defs", {})
|
||||
|
||||
chunks = [HEADER]
|
||||
for name in sorted(defs):
|
||||
chunks.append(render_def(name, defs[name]))
|
||||
|
||||
chunks.append("// Союзы по каналам — смотри docs/arch/CONTRACT.md#каналы")
|
||||
for key, prop in schema.get("properties", {}).items():
|
||||
alias, expr = pascal(key), ts_type(prop)
|
||||
if alias == expr:
|
||||
continue # союз из одного типа: интерфейс уже объявлен выше
|
||||
chunks.append(f"export type {alias} = {expr};")
|
||||
|
||||
names = event_names(defs)
|
||||
chunks.append(
|
||||
"export type EventName =\n " + "\n | ".join(literal(name) for name in names) + ";"
|
||||
)
|
||||
|
||||
return "\n\n".join(chunks) + "\n"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
text = render()
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(text, encoding="utf-8")
|
||||
print(f"{OUT.relative_to(ROOT.parent)}: {text.count('export ')} объявлений")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
89
backend/tests/test_domain.py
Normal file
89
backend/tests/test_domain.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""Тесты контракта. Домен — общий шов, ломать его молча нельзя."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
||||
from app.domain.events import (
|
||||
EventCatalog,
|
||||
KioPatchIn,
|
||||
ServerToTrainee,
|
||||
SessionMode,
|
||||
TraineeToServer,
|
||||
)
|
||||
from app.domain.kio import KIO, apply_patch, missing_fields
|
||||
from app.domain.timers import NORMATIVES, TimerCode, TimerState, state_for
|
||||
from scripts.export_types import OUT, render
|
||||
|
||||
|
||||
def test_interview_normative_is_75_seconds():
|
||||
"""Центральная метрика продукта. Значение меняется только вместе с ГОСТ."""
|
||||
assert NORMATIVES[TimerCode.INTERVIEW].limit_ms == 75_000
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"elapsed_ms,expected",
|
||||
[(0, TimerState.OK), (59_000, TimerState.OK), (70_000, TimerState.WARN), (94_000, TimerState.VIOLATED)],
|
||||
)
|
||||
def test_timer_state(elapsed_ms, expected):
|
||||
assert state_for(elapsed_ms, 75_000) is expected
|
||||
|
||||
|
||||
def test_patch_applies_nested_and_ignores_service_fields():
|
||||
card = KIO(caller_number="+7 999 000-00-00")
|
||||
patched = apply_patch(card, {"floor": "5", "fire.floors": 9, "caller_number": "подмена"})
|
||||
|
||||
assert patched.floor == "5"
|
||||
assert patched.fire is not None and patched.fire.floors == 9
|
||||
# Номер определяется автоматически и курсантом не редактируется.
|
||||
assert patched.caller_number == "+7 999 000-00-00"
|
||||
|
||||
|
||||
def test_missing_fields_reports_required_only():
|
||||
card = KIO(address="улица Ленина, 14")
|
||||
assert missing_fields(card, ["address", "floor", "victims_count"]) == ["floor", "victims_count"]
|
||||
|
||||
|
||||
def test_trainee_events_are_discriminated_by_type():
|
||||
adapter = TypeAdapter(TraineeToServer)
|
||||
event = adapter.validate_python({"type": "kio.patch", "fields": {"floor": "5"}})
|
||||
assert isinstance(event, KioPatchIn)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
adapter.validate_python({"type": "kio.state", "kio": {}})
|
||||
|
||||
|
||||
def test_server_events_round_trip_through_json():
|
||||
adapter = TypeAdapter(ServerToTrainee)
|
||||
payload = {
|
||||
"type": "caller.utterance",
|
||||
"utterance_id": str(uuid4()),
|
||||
"text": "Алло! Помогите! Горим!",
|
||||
"at": datetime.now(timezone.utc).isoformat(),
|
||||
"mood": "panic",
|
||||
}
|
||||
assert adapter.validate_python(payload).text.startswith("Алло")
|
||||
|
||||
|
||||
def test_session_modes_are_the_three_from_docs():
|
||||
assert {mode.value for mode in SessionMode} == {"training", "exam", "self"}
|
||||
|
||||
|
||||
def test_event_catalog_covers_every_channel():
|
||||
assert set(EventCatalog.model_fields) == {
|
||||
"server_to_trainee",
|
||||
"trainee_to_server",
|
||||
"server_to_observer",
|
||||
"instructor_to_server",
|
||||
"server_to_station",
|
||||
"station_to_server",
|
||||
"session_report",
|
||||
}
|
||||
|
||||
|
||||
def test_generated_types_match_models():
|
||||
"""Забытый `make types` ловится здесь, а не на фронте в последнюю ночь."""
|
||||
assert OUT.exists(), "нет frontend/src/shared/types/generated.ts — запусти make types"
|
||||
assert OUT.read_text(encoding="utf-8") == render(), "generated.ts устарел — запусти make types"
|
||||
999
backend/uv.lock
generated
Normal file
999
backend/uv.lock
generated
Normal file
|
|
@ -0,0 +1,999 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
requires-python = "==3.11.*"
|
||||
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mako" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/aa/02910bdb8e2f1444f6654d5b296cd827d126f82209050ee7b1000f92ac4b/alembic-1.20.0.tar.gz", hash = "sha256:db505480647bc60386c5369402f4a57a506b7539c9e9ef5e270d45cbbe4939bf", size = 2093272, upload-time = "2026-09-11T19:09:11.126Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/27/78a89b55b0904d222183164e079b4ca56208e94eff1d35ad1f1ad5be9b06/alembic-1.20.0-py3-none-any.whl", hash = "sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d", size = 268719, upload-time = "2026-09-11T19:09:12.88Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.15.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncpg"
|
||||
version = "0.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/17/cc02bc49bc350623d050fa139e34ea512cd6e020562f2a7312a7bcae4bc9/asyncpg-0.31.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eee690960e8ab85063ba93af2ce128c0f52fd655fdff9fdb1a28df01329f031d", size = 643159, upload-time = "2025-11-24T23:25:36.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/62/4ded7d400a7b651adf06f49ea8f73100cca07c6df012119594d1e3447aa6/asyncpg-0.31.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2657204552b75f8288de08ca60faf4a99a65deef3a71d1467454123205a88fab", size = 638157, upload-time = "2025-11-24T23:25:37.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/5b/4179538a9a72166a0bf60ad783b1ef16efb7960e4d7b9afe9f77a5551680/asyncpg-0.31.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a429e842a3a4b4ea240ea52d7fe3f82d5149853249306f7ff166cb9948faa46c", size = 2918051, upload-time = "2025-11-24T23:25:39.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/35/c27719ae0536c5b6e61e4701391ffe435ef59539e9360959240d6e47c8c8/asyncpg-0.31.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0807be46c32c963ae40d329b3a686356e417f674c976c07fa49f1b30303f109", size = 2972640, upload-time = "2025-11-24T23:25:41.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/f4/01ebb9207f29e645a64699b9ce0eefeff8e7a33494e1d29bb53736f7766b/asyncpg-0.31.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e5d5098f63beeae93512ee513d4c0c53dc12e9aa2b7a1af5a81cddf93fe4e4da", size = 2851050, upload-time = "2025-11-24T23:25:43.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/f4/03ff1426acc87be0f4e8d40fa2bff5c3952bef0080062af9efc2212e3be8/asyncpg-0.31.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37fc6c00a814e18eef51833545d1891cac9aa69140598bb076b4cd29b3e010b9", size = 2962574, upload-time = "2025-11-24T23:25:44.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/39/cc788dfca3d4060f9d93e67be396ceec458dfc429e26139059e58c2c244d/asyncpg-0.31.0-cp311-cp311-win32.whl", hash = "sha256:5a4af56edf82a701aece93190cc4e094d2df7d33f6e915c222fb09efbb5afc24", size = 521076, upload-time = "2025-11-24T23:25:46.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/fc/735af5384c029eb7f1ca60ccb8fa95521dbdaeef788edf4cecfc604c3cab/asyncpg-0.31.0-cp311-cp311-win_amd64.whl", hash = "sha256:480c4befbdf079c14c9ca43c8c5e1fe8b6296c96f1f927158d4f1e750aacc047", size = 584980, upload-time = "2025-11-24T23:25:47.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.7.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuda-bindings"
|
||||
version = "13.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cuda-pathfinder" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/45/57fb37fea9200d854960e36c2181675f92d16bfd7012cc5a95b153e782f0/cuda_bindings-13.4.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56c1552e207b291c321cef9952fbfaf8591c2a14d9c6e5215071020f541a04e5", size = 6484208, upload-time = "2026-09-10T01:16:46.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/00/15dc1b8f2e5e98975107c366ec5671256d32b8e5c5ed6c1bb3a90a64f035/cuda_bindings-13.4.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5dc0f13cfd14cd62206fede462f91c497d0c484b8236f4158531d20377066cd", size = 7159324, upload-time = "2026-09-10T01:16:48.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuda-pathfinder"
|
||||
version = "1.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/e6/22df83f82f9bc26cb1c42265cf14d34d4908dba2a0f261bd7b28244acb00/cuda_pathfinder-1.8.1-py3-none-any.whl", hash = "sha256:ae0137ff9e56ea97499bcbf54f5f2778ec25f3266715ac86da192a795af982a8", size = 62552, upload-time = "2026-09-02T16:55:28.64Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuda-toolkit"
|
||||
version = "13.0.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
cublas = [
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
cudart = [
|
||||
{ name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
cufft = [
|
||||
{ name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
cufile = [
|
||||
{ name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
|
||||
]
|
||||
cupti = [
|
||||
{ name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
curand = [
|
||||
{ name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
cusolver = [
|
||||
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
cusparse = [
|
||||
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
nvjitlink = [
|
||||
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
nvrtc = [
|
||||
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
nvtx = [
|
||||
{ name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.141.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.32.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "25.12.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsspec"
|
||||
version = "2026.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.5.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/6e/0091f175ccd02b02bc8811bbcbcc6ac2e980be116e3b2f7a736ca322bf84/greenlet-3.5.6.tar.gz", hash = "sha256:8e67c43bdfc88d5fee6db0d3e40175b362fc95fb85f0412d233b9b203c53a575", size = 207653, upload-time = "2026-09-14T15:42:51.806Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/d7/41511ee2696f14be4200b524d9553dc4295e2bdeb20aa8962c3cb25e71c6/greenlet-3.5.6-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a6a4b98a9132e0f45c9fc245a63894cfd8c45fb7a0d6bffc5eab3ec327cf7324", size = 294075, upload-time = "2026-09-14T14:25:16.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/7b/b509624970909294cd064ff7346148ca9941c21bec9026d7873dd254e9fa/greenlet-3.5.6-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45bfd2b51e38aaa5f9849f114d9c7c1d75f69187c849b3549cd64c465283abfa", size = 613429, upload-time = "2026-09-14T15:12:00.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/5c/d2eb503067f9ba20875ef8c87681f29a64f53bbbbe4059a5d7c53179d442/greenlet-3.5.6-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c6dede9133e1da41d561bc3fb14e92b47e2ce39ae60edefaad145658ea7c5e2", size = 625405, upload-time = "2026-09-14T15:20:41.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/24/9b071d11c8bb9f5f38cccacc38fcc234d91997a4c395cc2bf43ecae89642/greenlet-3.5.6-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4fb8e59f68845d56c23c031dcd79c329f345e4a9d2ffac91c3d1ab366bdc457b", size = 633270, upload-time = "2026-09-14T15:25:04.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/d3/63d4477ce31dff2fd802a9a20240f6606aac85977e0fb18443aae33de3f6/greenlet-3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c20ea32a73d17b9b60e3371240e17b0068120c98a5ec01a224a7dd8c89733ba", size = 624428, upload-time = "2026-09-14T14:35:56.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/17/ac11883ecc9da19c681c8b763ee39e6f7dca2aa81874eb11a075d3cbeb00/greenlet-3.5.6-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:d701eab36200c36224833d07dbdb709adb7fd4253429548ddb5e547b8ed40586", size = 428068, upload-time = "2026-09-14T15:28:35.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/aa/9cde4e00688eaa2a03b91d12e4681439a87e6aad860399e0847af6a014ca/greenlet-3.5.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a0b2791239c99992a86c1b635b787fe2a877d9eaaa26f8891ce943832b585ae", size = 1588385, upload-time = "2026-09-14T15:10:05.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/01/24632b5ec186b64e21e07a8f53ce5e15a7e9cb33eddee99a5fe16379afa5/greenlet-3.5.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:188bf333769b7145e2b0b4a7f09615ec550ed44d3a2a8395fb7b36f0e9901e13", size = 1653119, upload-time = "2026-09-14T14:35:48.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/6c/019d2ef898f4b9ac845167f1c6f73229e9a4e2439362a5e2ce50205a19b0/greenlet-3.5.6-cp311-cp311-win_amd64.whl", hash = "sha256:a6b4ff33f7e011bbaa148238d131c4fd4f8afbab3c104ddfbdb2b12b74ff7016", size = 323317, upload-time = "2026-09-14T14:22:38.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/7a/439df999455e3bdf02b1c68f3848d4020385ef0a01f89f706b07bf148a65/greenlet-3.5.6-cp311-cp311-win_arm64.whl", hash = "sha256:59deccd347735a7774223b05a93773fddbb298aba3cea21be4337fb4752dbe32", size = 307739, upload-time = "2026-09-14T14:23:40.469Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lct-backend"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "sqlalchemy", extra = ["asyncio"] },
|
||||
{ name = "structlog" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
]
|
||||
voice = [
|
||||
{ name = "numpy" },
|
||||
{ name = "onnx-asr" },
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "torch" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "alembic", specifier = ">=1.13" },
|
||||
{ name = "asyncpg", specifier = ">=0.29" },
|
||||
{ name = "fastapi", specifier = ">=0.115" },
|
||||
{ name = "httpx", specifier = ">=0.27" },
|
||||
{ name = "numpy", marker = "extra == 'voice'", specifier = ">=1.26" },
|
||||
{ name = "onnx-asr", marker = "extra == 'voice'", specifier = ">=0.6" },
|
||||
{ name = "onnxruntime", marker = "extra == 'voice'", specifier = ">=1.18" },
|
||||
{ name = "pydantic", specifier = ">=2.7" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.3" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.2" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0" },
|
||||
{ name = "structlog", specifier = ">=24.1" },
|
||||
{ name = "torch", marker = "extra == 'voice'", specifier = ">=2.2" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.30" },
|
||||
]
|
||||
provides-extras = ["voice", "dev"]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/12/b5fa2353e2754cd67fb9f83793fa48ff42c213a5da7e719869d2301f6ab8/mako-1.4.1.tar.gz", hash = "sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27", size = 410165, upload-time = "2026-08-05T06:10:56.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/54/12ed58d458474aaab5c3d180173e745a4fe131bb330370596876d19ff60f/mako-1.4.1-py3-none-any.whl", hash = "sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617", size = 80010, upload-time = "2026-08-05T06:10:58.248Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpmath"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "networkx"
|
||||
version = "3.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cublas"
|
||||
version = "13.1.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cuda-nvrtc" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cuda-cupti"
|
||||
version = "13.0.85"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cuda-nvrtc"
|
||||
version = "13.0.88"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cuda-runtime"
|
||||
version = "13.0.96"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cudnn-cu13"
|
||||
version = "9.24.0.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863, upload-time = "2026-07-02T16:23:39.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/ba/791cffd048fe5b044e620df55267e3e95c0e6e07d50b41e377c03dfc910f/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:71f181cd810e90f9b6023b01186fe82d13d65f0ec098581ee201d39fad769e4b", size = 553099438, upload-time = "2026-07-02T16:27:42.58Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cufft"
|
||||
version = "12.0.0.61"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cufile"
|
||||
version = "1.15.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-curand"
|
||||
version = "10.4.0.35"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cusolver"
|
||||
version = "12.0.4.66"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-cublas" },
|
||||
{ name = "nvidia-cusparse" },
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cusparse"
|
||||
version = "12.6.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nvidia-nvjitlink" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-cusparselt-cu13"
|
||||
version = "0.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-nccl-cu13"
|
||||
version = "2.30.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/21/a73174c6157101bdf1ffc22b517f76ff0082613989dd9bc8f43e8034caac/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:ca786ffa5a647c75d4d1f5cc72a6c4f537947e2ba8823d7c8aaf768e7a7b9f77", size = 215983881, upload-time = "2026-06-09T03:23:15.633Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/34/c500f90c7ae641b8e0f98965b36b8a7ac79cc8b296e8d251fe3eb592ee54/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:cefa7fdb9710efd0f39c5f1be1d61ff6fc9a996c451265bd7fbdcf9455ed4b50", size = 215965170, upload-time = "2026-06-09T03:23:39.73Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-nvjitlink"
|
||||
version = "13.4.52"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/c1/091f198d7f87e31d67fa9680eec8f8e4c6f889881f729b759db36ff01612/nvidia_nvjitlink-13.4.52-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:90401db7e5a580a5067a468b3086e0b65f3b96ab3c42524afd741efc8a0e150a", size = 42452221, upload-time = "2026-09-09T18:01:32.895Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/a9/225ff51e80de170be880cb88e992193bc8134a51059cc0a3952f967f62c5/nvidia_nvjitlink-13.4.52-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a589e3732140839349545efd0db67426523459b12e6952c3c73b6d55900f200", size = 40419746, upload-time = "2026-09-09T18:01:25.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-nvshmem-cu13"
|
||||
version = "3.4.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvidia-nvtx"
|
||||
version = "13.0.85"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onnx-asr"
|
||||
version = "0.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/11/136f556cda02ecc172674dcea79232e9e84e74b951bbcd2e2722117f633b/onnx_asr-0.12.0.tar.gz", hash = "sha256:c1fcacddbced392f9f769ed5c6223f4ce739a6e4549346de33b9b520d4ead713", size = 45991, upload-time = "2026-07-15T00:09:58.852Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/60/2fa469a2ee674c35ab48821a1039762ae7b9d0b88188ac1012e779477f76/onnx_asr-0.12.0-py3-none-any.whl", hash = "sha256:5e7ceca454609819ea7833f61e2302e0c8f6ece4f8a78b66c5daba53cb51de4a", size = 3980006, upload-time = "2026-07-15T00:09:57.322Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onnxruntime"
|
||||
version = "1.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flatbuffers" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/b6/e2fa6dfc8c3cd19639d452c3120fb0e02f5176b4e24ecabb24b5362be725/onnxruntime-1.30.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f36c87ec504f4f328e968ee322e4ba9682abe66280b3425625afcbed5333fed7", size = 21533037, upload-time = "2026-09-10T16:31:03.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/1a/4d197d2676a866f6239cd2d06a95b6db40d47dc29d5bec9fb1493a13dd94/onnxruntime-1.30.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bac3aee7b40acd805659fd81a2acd6096312d5b0f014c97ddfe75fb4c1644627", size = 21332697, upload-time = "2026-09-10T16:31:08.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/2e/4c96278a99140d0307ccda6fd51e6e9d1ca7c9f6fdd1fa96ff375acfc713/onnxruntime-1.30.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd54b314ea385bcecac69ab431f020ba503e3878dad4ebb645fec5a24b041242", size = 23561046, upload-time = "2026-09-10T16:31:11.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/68/b8aaade8c1883f569228f4b57201cce922bb7d4af6f977d619520a733430/onnxruntime-1.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:0edd0145a6e3fce8a1276491dc784d615e3c58bcb952c9b4e5c876d5c6a12ad7", size = 14309136, upload-time = "2026-09-10T16:31:14.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/f1/fbda6fb08ac4db6c5c6f7c3e128e4d8fb398b52fe2fc3282c45cadedd18b/onnxruntime-1.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:16506cfa5d218850f2b46e0edce1b952121f2f84f8e0074dafe15f4787f0c8fb", size = 14167408, upload-time = "2026-09-10T16:31:16.505Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "7.36.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/73/f66c748df06e7fe24e658eddd600d19c4b40bad836c97ce2d0ad9851fb6b/protobuf-7.36.1.tar.gz", hash = "sha256:d0f6470f0ce2b84e3feaea2d4b816378b37ba4d4aa08a274305373de93e2d524", size = 512499, upload-time = "2026-08-31T22:40:04.667Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/6c/3a54a58f2948b0f485df9ecdd06590f15d0a7abf46a89d50c3de709ff4ff/protobuf-7.36.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:3cf2ee25d006cee57294a1196ea43b37feb78e0dcd1e8af5c1aeddb777655aca", size = 456046, upload-time = "2026-08-31T22:39:56.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/08/9f9548793c771095245c0eeaf0c84b76b16a5ef26158043d456f551344a0/protobuf-7.36.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:43d3d37b1eb24c113b9b7d02008cac44e423f00b611b7781ae998d7623972969", size = 344226, upload-time = "2026-08-31T22:39:58.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/51/1bdbd612fa3c51e42ea6f45d05e84dc748ddcd2663e1aa1e89a00a33facd/protobuf-7.36.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:39c518c05586c016d7874ff6079ee115bcec1ea5fbb1d177fbf7867ef4c67e44", size = 357229, upload-time = "2026-08-31T22:39:59.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/df/c799fe7a05ef16ba853a59db01f3a2c5f7d0676469589ccc4874f76a2a88/protobuf-7.36.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:97198b77e369a0abd8e262b8f6c7266c55ddb796a3a12c76d7b8881188ed83aa", size = 343228, upload-time = "2026-08-31T22:40:00.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/33/d4724ec5d86d496fe4108e220618aa0837ad3423a7af7ccdd9684ccc77c8/protobuf-7.36.1-cp310-abi3-win32.whl", hash = "sha256:0b53ce95272aad50ad25d7ff03373743209822e8ba42ea7fad27d2bee1547d00", size = 443002, upload-time = "2026-08-31T22:40:01.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/37/155788a0d8daded960375af604202805308169f9b859419ea0aa370946e2/protobuf-7.36.1-cp310-abi3-win_amd64.whl", hash = "sha256:51139351435d9b43d88a55eaa49fb6f737fbb478fb0cbf2cf694d1a04a9d3363", size = 456518, upload-time = "2026-08-31T22:40:02.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/ca/c47f91d3cab175b01fd8c4f0d80fdf8613be876cc616e66ad281a59c5ddf/protobuf-7.36.1-py3-none-any.whl", hash = "sha256:7d951e46b3f963d6c264c367c437921de9d5aedd9c3f9612b9077736b4e3ad5c", size = 179813, upload-time = "2026-08-31T22:40:03.54Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "1.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "84.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.53"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/de/59fc3d2e63dfd367e803417c40e70e35b6c0a0997420cb082d351cdd052b/sqlalchemy-2.0.53.tar.gz", hash = "sha256:b568a8ac0778988c2710476017ff74fb5dcb9b9322d757189c3d7fc4cc8026fa", size = 9966200, upload-time = "2026-09-14T20:37:31.731Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/58/68f32c4428e01aa784d045d74fdd9ceacca1f27b7030d468a7c90dcb0839/sqlalchemy-2.0.53-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b0a3481fdea08ce9f93c318e40c446c91bc63260a5eae1cf06d85b4d4d5651d4", size = 2187761, upload-time = "2026-09-14T22:39:48.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/ad/57b82748084536340703d42b23694a259bef66db17f21cf37a0475480cfa/sqlalchemy-2.0.53-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29e7eb8bb305411f26ead6854e7591cfc6a5b3b6b924a31bd8b843ba243a3ebd", size = 3428938, upload-time = "2026-09-14T22:46:44.295Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/f9/9c719de2e6628b8d0884082590da324674a10ec3bb177face202a8bb439b/sqlalchemy-2.0.53-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:255c1ca3810da6994b755c80b3abc27a5fb1d45b6703985092a2f6663f9daa9e", size = 3428551, upload-time = "2026-09-14T22:50:14.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/0c/93586869f7c852b4138736d9ea6121f4b2f967878bec616a36caa9cb2502/sqlalchemy-2.0.53-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764eebce5bf2046172f6f32d51ee47b0acc0dbdb05262a6382875267bc5b97d4", size = 3377684, upload-time = "2026-09-14T22:46:45.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/4d/078b5badf416692545382a8cc803230f27dab29ab2dc970840566a221b7a/sqlalchemy-2.0.53-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab1489a2a417f5d8f03d2824771e168ac2da8db9c34e05de8a404182390d67e7", size = 3402953, upload-time = "2026-09-14T22:50:16.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/24/31d22eee205f611286cfc172dcc71f502454895a469e66ef5ecb32998241/sqlalchemy-2.0.53-cp311-cp311-win32.whl", hash = "sha256:90242b1ad2bdad5fcebf32fb2971465edd4034e0381c1d6126bcb5a5b1851f18", size = 2142283, upload-time = "2026-09-14T22:55:58.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/bd/6ac820a60ca104a20977b267381ebe78d84fdf3aaaa44fd98a952cdcc962/sqlalchemy-2.0.53-cp311-cp311-win_amd64.whl", hash = "sha256:05faf168e324b5c609df10530d25620fcce7a815635c653903d3f459617b85bd", size = 2169208, upload-time = "2026-09-14T22:56:00.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/3c/6ee5fe746b0820335d30c5172527e9a2c7409ac632376d15fded38ff715c/sqlalchemy-2.0.53-py3-none-any.whl", hash = "sha256:87eb05fc61a0835dfe1cbf135d0f99cb2764ad09350d92fdd3fe1b827a557d65", size = 1958002, upload-time = "2026-09-14T22:40:04.431Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
asyncio = [
|
||||
{ name = "greenlet" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structlog"
|
||||
version = "26.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/89/b4a0bcfdf4f71a3dea31379f095929613d7e4528a0996bca6aa964cd0dca/structlog-26.1.0.tar.gz", hash = "sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7", size = 1459881, upload-time = "2026-06-06T07:33:39.348Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/18/489c97b834dfff9cf2fc2507cede4bcd4b11e67f84bc462acd1992496f86/structlog-26.1.0-py3-none-any.whl", hash = "sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e", size = 73764, upload-time = "2026-06-06T07:33:38.046Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mpmath" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "torch"
|
||||
version = "2.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cuda-bindings", marker = "sys_platform == 'linux'" },
|
||||
{ name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" },
|
||||
{ name = "filelock" },
|
||||
{ name = "fsspec" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "networkx" },
|
||||
{ name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "sympy" },
|
||||
{ name = "triton", marker = "sys_platform == 'linux'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/2c/0c9757a1ea1fff8ccc4faff1d1c87e98a69542b5d4b606e50f7518f72e58/torch-2.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ae530ddd3f3b94248b77f1fd3313c4f3405bd0d17283d505b81574816767133a", size = 127277244, upload-time = "2026-09-02T13:42:57.855Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/30/eb2f6cf77cb8f2d5e7ce4343882a856783521f51b557673d78d1d7bb8661/torch-2.14.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4dbea8a10d80b10dff2be1ed467d2617b704fe6bf76bbfad4194774c8f6d886b", size = 453993896, upload-time = "2026-09-02T13:43:29.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/be/c0c8a845bf00552960d2f1f6a00b6ac9750fd312f55429c3e8c6c0e3aac4/torch-2.14.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d9e232b6376c62f3090237889fb1cb6887c0ece9f73e6c1052e80fc1481f999", size = 554583496, upload-time = "2026-09-02T13:44:10.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/5b/ed9d177f6a133389d5fa89e298f3e2a879b62952aa928ac6ddd04467f225/torch-2.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:6981872df75eb5409c439050d36b67cb57d88b4e11080516e2aa7410f6730705", size = 124096356, upload-time = "2026-09-02T13:43:02.941Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triton"
|
||||
version = "3.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/cf/d21c9b1a4d1df9ba3aed218069f24f762a4b9565c5a362bef6f110c08e72/triton-3.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:372285307d4c44ee74cee32de0b4f04bd157e071427e38c6f4ee3e3beb2194f4", size = 226467015, upload-time = "2026-08-28T16:08:05.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/2c3d3823726d5ad5359f7065b02b984925158b74611c1e3987f6a37461fb/triton-3.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68988ac85d5e7086baeda0ddc175af9667db7529b3c5e11a5c0601b8bef2200a", size = 247945226, upload-time = "2026-08-28T15:55:43.795Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.53.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5d/ad/04bbb797c84fc1f26cb171f7394716f4865ffb8d8c5e1eef42565c2dfa6b/uvicorn-0.53.0.tar.gz", hash = "sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e", size = 110881, upload-time = "2026-09-14T07:44:23.815Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/18/0eea75741ee812e9f598b687619ce2454f6c3a1c5cd21ea990ec6bd26f45/uvicorn-0.53.0-py3-none-any.whl", hash = "sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e", size = 87081, upload-time = "2026-09-14T07:44:22.179Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
standard = [
|
||||
{ name = "httptools" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.22.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ad/66a74d42fb537bd44056483eae6cbb7ebb10b742c300a0bf8cee427556d4/websockets-17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:88b882764ef65147a7a5ae13168dedbe225a04e2ff4858fe543f2c402f093e9c", size = 216984, upload-time = "2026-08-26T14:55:20.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/1b/344ab22cea729e872f759b926441f7b822ab6cd106db527736afc066927f/websockets-17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98a5b2589a56a4b4f098b0a958099a4356ab904a7844f1da3841efca469af7e9", size = 214667, upload-time = "2026-08-26T14:55:22.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/42/bace574b6ae80e1a8d6935b8c5f03fb67236233ec572e976fe826ff719cf/websockets-17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:020e271205f8ab3406d7a59cd00de6dec722315924411c421bd00642f18bad86", size = 214944, upload-time = "2026-08-26T14:55:23.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/87/08e35ca4a0ffafb500a16ff461bf9561ad2b755362adb5d077d4dba9affc/websockets-17.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:65be6bda2b537fefa4b3a5ccd6ab386533ce39dd8fe62433ec90901fdc81752d", size = 224004, upload-time = "2026-08-26T14:55:24.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/9a/00ae2e147eaa086fe8bdddd36f57216ce72b9a9dfc0b17c717005ebdacaf/websockets-17.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c84bdef916556cbe1d5a43b423398be4dd3cba6522b463e53d848578b920695", size = 224278, upload-time = "2026-08-26T14:55:26.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/e2/7aeb4e00defa68826f449392922a382ce7fdf542fe52190558dc1714e284/websockets-17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47a62d6045c6eaa0d8f97bc2fb68b8cf90077a0cbfd4e83d6f2d2145611ee134", size = 225511, upload-time = "2026-08-26T14:55:27.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/e4/655be3d93c3edbe1a51606073b5454ea6b1b32d87aa26253a6df952417b7/websockets-17.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34879e19bb0a3c44f9317679435aea5327fac993933a704cbf353bf1234b10c7", size = 228802, upload-time = "2026-08-26T14:55:28.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/33/98549a2afa9d68fe1b5a8e0a61cd461a43ea1ab7209bce675eea67c79190/websockets-17.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d72879819f5145a342d0030c418702496c65a4b913ef81f5ae944dd91dd50f6", size = 226075, upload-time = "2026-08-26T14:55:29.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/6b/cbc27e014d6c292b9b2709cfd32781a2b61eb30cd4c9130e7c57e41a204a/websockets-17.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f25e099fdfe3b09f953d84698f729a1f7d1e99101b2787d7a28ed77b323750", size = 224846, upload-time = "2026-08-26T14:55:30.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/a6/e57925f7a423d90f24559e85bac21a7f0b44c0cf4a5c0babc0759ca54bab/websockets-17.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469355ab1af100b9380f1afb09985019f4a4b94fa1dd0e9396db4361626d7ab8", size = 222136, upload-time = "2026-08-26T14:55:32.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/07/b9de0400addb542ba7c819022abc3afa46cd7e518068881bceadac69d995/websockets-17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00679b7468b4c2b12b0757118174e8eabac56bb2f579a928a104d9554a56e098", size = 225000, upload-time = "2026-08-26T14:55:33.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/bb/af2828a1d7f2beb792af6ba56d7b02d56262070266b95d2af9ef391fbfb0/websockets-17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a9fe648abd1d9b89aebfa30407bfdd08a0271ec5dc7d44a4c6ccd1ce22cf562a", size = 223592, upload-time = "2026-08-26T14:55:34.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/51/7379f254730c1dc7d8e4dd8d686868d8f7be55bdc94c6d3a44538840f639/websockets-17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f47aafd92aa28b941180e6da8a42b0f711851b14b81a5b6bb28dbbb1fa35152c", size = 224360, upload-time = "2026-08-26T14:55:35.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/c6/fcd91320dd71dda7046df9bc60f60c70ee15c052dee21e31ed6221dc8b5d/websockets-17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c89406fa3dcd4aa8662c6406cc5c0de1790e9614d2c3aaf03ca53a8a8ccf3405", size = 225404, upload-time = "2026-08-26T14:55:36.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/b2/655a4f939388079f80f1b3f8a1b9d40783e70a376e7423988cc9a590a09f/websockets-17.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b3b451fd2723ad3191a209afe6f3f4bc86c83e9a85bdc255353b91803ee6aa66", size = 222982, upload-time = "2026-08-26T14:55:38.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/75/37c84c4371c6aa668910d7841036c4397ea10554c07030603ccd5e44b02a/websockets-17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:054c28db2dcec0e857e3b705d8c28012613e555b38c765d6a4f75340a4fc06a0", size = 224017, upload-time = "2026-08-26T14:55:39.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/20/8a9a94323bfcfe03bde3f9d98926bea4855856359702fe3ca0d07051ef5d/websockets-17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f8e822efd54137d8cc8310eb64635ab827a4a6c72ff08691f38aa624776d8ecb", size = 224252, upload-time = "2026-08-26T14:55:40.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/dd/aa66e6500188cd40306abeb92c9a738ca6dd7029d8d8532c538055ab5daf/websockets-17.1-cp311-cp311-win32.whl", hash = "sha256:dcb8d5f7edef7a399d322cf28d4c4e6f98dab64d301c8f50581a1080e5198142", size = 217484, upload-time = "2026-08-26T14:55:41.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/a2/cdf3b551f0b9177023afd3a45d3b431a0d4064951008c4321a8b42ac2288/websockets-17.1-cp311-cp311-win_amd64.whl", hash = "sha256:b1bc819c6db90e8f91a38250a1ab4c058261871aa52d2fe36382eddedf146dee", size = 217779, upload-time = "2026-08-26T14:55:42.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/13/51253dbed7d16a4bb87b05110ad3bf12165f410e915f6da1edd4186d8dc1/websockets-17.1-cp311-cp311-win_arm64.whl", hash = "sha256:edadce7a22052056fd4384543019856b34850363c9d387929f677ae01d79709c", size = 217710, upload-time = "2026-08-26T14:55:44.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/e4/af4abbcf07eac6a725ec6f865611526b2b0c23d482723de551bec667880d/websockets-17.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10ecb38ffc05e1841b619d99c725307a223ef9ad58e7b1ed33311d472dc43918", size = 214602, upload-time = "2026-08-26T14:58:25.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/fe/819fba7ba35f92b639333da7355041c07dd50048f9c76fba0b8e292a6483/websockets-17.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17aa424ab61620aad21b36b2240efc87b500cc496e7d0e999a5c2ae99395e886", size = 214874, upload-time = "2026-08-26T14:58:26.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/a7/d370ab794f47fbeea648d17ad08caf0bb50131d6c04b7ad83e6af63c405a/websockets-17.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:764cf7bfa149365f32b7a0fd9fed32debdac29dd06295d5635cde1745b446cd8", size = 215821, upload-time = "2026-08-26T17:25:23.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/6b/251b00fe634e2a9c2cb5d6390e0e97cec55e3d18dd09b4b976620eed5d7b/websockets-17.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d1b108bd8f5f6a8b90801f6db3b3858d5deca889acfdb8ac497bbb24e4b0edf", size = 215714, upload-time = "2026-08-26T17:25:26.295Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/b1/37fe0c96c206b4208a072c3a74add6a72af4b8228be3f5435163c5a6d099/websockets-17.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a62d8c424383c9dc769ff3672018df822603117e32686e567d452ed035b6fb2e", size = 216608, upload-time = "2026-08-26T17:25:28.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/7e/75a0a491b512412e08333b9f8412757af6186fe1c598186261002de1a793/websockets-17.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8196d217eeca52b9235ee1f8a684a09885a5f953d5a31e80ef915bf2c5c94f9d", size = 217870, upload-time = "2026-08-26T17:25:29.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" },
|
||||
]
|
||||
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: lct
|
||||
POSTGRES_PASSWORD: lct
|
||||
POSTGRES_DB: lct
|
||||
ports: ["5432:5432"]
|
||||
volumes: ["pgdata:/var/lib/postgresql/data"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U lct"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
# Один воркер принципиально: состояние живой сессии и реестр наблюдателей
|
||||
# живут в памяти процесса (docs/arch/STACK.md).
|
||||
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --workers 1
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://lct:lct@postgres:5432/lct
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- ./scenarios:/scenarios:ro
|
||||
ports: ["8000:8000"]
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
command: npm run dev -- --host 0.0.0.0
|
||||
volumes:
|
||||
- ./frontend:/app
|
||||
- /app/node_modules
|
||||
ports: ["5173:5173"]
|
||||
depends_on: [backend]
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
2
frontend/.dockerignore
Normal file
2
frontend/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
||||
8
frontend/.npmrc
Normal file
8
frontend/.npmrc
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# При дефолтных 15 параллельных сокетах npm install зависает под WSL2:
|
||||
# процесс стоит в poll, тарболы не доезжают. С тремя сокетами ставится за минуты.
|
||||
# Проверено 15.09.2026: один caniuse-lite качался 120 с при 15 сокетах.
|
||||
maxsockets=3
|
||||
fetch-retries=5
|
||||
fetch-timeout=60000
|
||||
audit=false
|
||||
fund=false
|
||||
9
frontend/Dockerfile
Normal file
9
frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
FROM node:20-slim
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* .npmrc ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
EXPOSE 5173
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
12
frontend/index.html
Normal file
12
frontend/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Система 112 — учебный симулятор</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
1792
frontend/package-lock.json
generated
Normal file
1792
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
frontend/package.json
Normal file
24
frontend/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "lct-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.26.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.3"
|
||||
}
|
||||
}
|
||||
13
frontend/src/app/router.tsx
Normal file
13
frontend/src/app/router.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { createBrowserRouter, Navigate } from "react-router-dom";
|
||||
|
||||
import { Stub } from "@/shared/ui/Stub";
|
||||
|
||||
// Четыре интерфейса — одна SPA (docs/arch/FRONTEND.md).
|
||||
// session_id живёт в URL: /instructor?session=..., монитор открывают ссылкой.
|
||||
export const router = createBrowserRouter([
|
||||
{ path: "/", element: <Navigate to="/trainee" replace /> },
|
||||
{ path: "/trainee", element: <Stub title="АРМ курсанта" card="lct-08" /> },
|
||||
{ path: "/instructor", element: <Stub title="Пульт преподавателя" card="lct-17" /> },
|
||||
{ path: "/wall", element: <Stub title="Внешний монитор" card="lct-16" /> },
|
||||
{ path: "/dds", element: <Stub title="АРМ диспетчера ДДС" card="lct-20" /> },
|
||||
]);
|
||||
12
frontend/src/main.tsx
Normal file
12
frontend/src/main.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
|
||||
import { router } from "./app/router";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
527
frontend/src/shared/types/generated.ts
Normal file
527
frontend/src/shared/types/generated.ts
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
// Сгенерировано `make types` из backend/app/domain/events.py.
|
||||
// Руками не править: правка уедет при следующей генерации.
|
||||
// Контракт: docs/arch/CONTRACT.md
|
||||
|
||||
|
||||
/** Аудио-фон происшествия. Файл лежит в сборке фронта, трафика фон не создаёт. */
|
||||
export interface BgStart {
|
||||
type: "bg.start";
|
||||
loop: string;
|
||||
gain_db: number;
|
||||
}
|
||||
|
||||
export interface BgStop {
|
||||
type: "bg.stop";
|
||||
}
|
||||
|
||||
/** Снял гарнитуру. Останавливает норматив `answer`. */
|
||||
export interface CallAnswer {
|
||||
type: "call.answer";
|
||||
}
|
||||
|
||||
export type CallEndReason = "hangup" | "dropped" | "instructor";
|
||||
|
||||
export interface CallEnded {
|
||||
type: "call.ended";
|
||||
reason: CallEndReason;
|
||||
}
|
||||
|
||||
export interface CallHangup {
|
||||
type: "call.hangup";
|
||||
}
|
||||
|
||||
export interface CallIncoming {
|
||||
type: "call.incoming";
|
||||
scenario_id: string;
|
||||
caller_number: string;
|
||||
level: Level;
|
||||
mode: SessionMode;
|
||||
}
|
||||
|
||||
export interface CallStarted {
|
||||
type: "call.started";
|
||||
started_at: string;
|
||||
}
|
||||
|
||||
/** Обратный дозвон после обрыва: 3 попытки по 10 с. */
|
||||
export interface CallbackDial {
|
||||
type: "callback.dial";
|
||||
}
|
||||
|
||||
export interface CallerUtterance {
|
||||
type: "caller.utterance";
|
||||
utterance_id: string;
|
||||
text: string;
|
||||
at: string;
|
||||
mood: Mood;
|
||||
}
|
||||
|
||||
/** Останавливает норматив `dds_ack` (≤ 4 с). */
|
||||
export interface CardAck {
|
||||
type: "card.ack";
|
||||
}
|
||||
|
||||
/** Возврат на уточнение: неполнота КИО становится сорванным выездом. */
|
||||
export interface CardBounce {
|
||||
type: "card.bounce";
|
||||
missing_fields: Array<string>;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
/** Снимок КИО. После передачи не меняется — оператор не дописывает задним числом. */
|
||||
export interface CardReceived {
|
||||
type: "card.received";
|
||||
card: KIO;
|
||||
from_operator: string;
|
||||
at: string;
|
||||
}
|
||||
|
||||
/** Шесть осей радара. Проекция уже посчитанных метрик, без пересчёта весов. */
|
||||
export type Competency = "intake" | "interview" | "card" | "routing" | "norms" | "communication";
|
||||
|
||||
export interface CompetencyScore {
|
||||
competency: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface Coords {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export interface CrewArrived {
|
||||
type: "crew.arrived";
|
||||
at: string;
|
||||
}
|
||||
|
||||
export interface CrewDispatched {
|
||||
type: "crew.dispatched";
|
||||
at: string;
|
||||
}
|
||||
|
||||
/** Дежурно-диспетчерская служба, в которую уходит карточка. */
|
||||
export type DDSCode = "01" | "02" | "03" | "04" | "gkh";
|
||||
|
||||
/** Передача в ДДС. Замораживает карточку снимком и останавливает `dds_notify`. */
|
||||
export interface DdsDispatch {
|
||||
type: "dds.dispatch";
|
||||
service: DDSCode;
|
||||
}
|
||||
|
||||
/** `immediate` — только для обрыва связи: рвёт TTS на полуслове. */
|
||||
export type DirectiveMode = "next_turn" | "immediate";
|
||||
|
||||
/** Директива звонящему. Карточку курсанта не трогает ни одна команда. */
|
||||
export interface DirectorInject {
|
||||
type: "director.inject";
|
||||
directive: string;
|
||||
mode?: DirectiveMode;
|
||||
}
|
||||
|
||||
/** Автоданные ЭРА-ГЛОНАСС до соединения с водителем. */
|
||||
export interface EraData {
|
||||
type: "era.data";
|
||||
vin: string;
|
||||
coords: Coords;
|
||||
passengers: number;
|
||||
impact_force: string;
|
||||
}
|
||||
|
||||
export type ErrorCode = "E1" | "E2" | "E3" | "E4" | "E5" | "E6";
|
||||
|
||||
export interface ErrorEvent {
|
||||
type: "error";
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** Отметка в разборе. `fact` и `norm` — то самое обоснование. */
|
||||
export interface Finding {
|
||||
code: ErrorCode;
|
||||
source: FindingSource;
|
||||
summary: string;
|
||||
fact: string;
|
||||
norm?: string | null;
|
||||
ref?: string | null;
|
||||
competency?: Competency | null;
|
||||
transcript_ref?: string | null;
|
||||
at?: string | null;
|
||||
}
|
||||
|
||||
/** Кто выставил отметку. `judge` — единственный недетерминированный источник. */
|
||||
export type FindingSource = "slots" | "ground_truth" | "timers" | "kio" | "chain" | "judge" | "instructor";
|
||||
|
||||
/** Уточняющие поля 01 — МЧС. */
|
||||
export interface FireDetails {
|
||||
fire_nature?: string | null;
|
||||
object_kind?: string | null;
|
||||
floors?: number | null;
|
||||
gasified?: boolean | null;
|
||||
people_inside?: boolean | null;
|
||||
}
|
||||
|
||||
/** В режиме `exam` сервер отвечает событием `error`. */
|
||||
export interface HintRequest {
|
||||
type: "hint.request";
|
||||
}
|
||||
|
||||
export interface HintShown {
|
||||
type: "hint.shown";
|
||||
checklist_id: string;
|
||||
question: string;
|
||||
}
|
||||
|
||||
export interface HintUsage {
|
||||
checklist_id: string;
|
||||
question: string;
|
||||
at: string;
|
||||
}
|
||||
|
||||
/** Тип происшествия. Совпадает с полем `type` сценария. */
|
||||
export type IncidentType = "fire" | "medical" | "police" | "other" | "gkh" | "era_glonass";
|
||||
|
||||
export interface InstructorNoteAdd {
|
||||
type: "instructor_note.add";
|
||||
transcript_ref: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/** Пометка преподавателя видна всем, кому виден транскрипт. */
|
||||
export interface InstructorNoteShown {
|
||||
type: "instructor_note.shown";
|
||||
transcript_ref: string;
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
/** Полная карточка. Наблюдателям уходит целиком (`kio.state`),
|
||||
курсанту — дельтой (`kio.patch`). */
|
||||
export interface KIO {
|
||||
card_id?: string;
|
||||
registered_at?: string | null;
|
||||
response_status?: ResponseStatus;
|
||||
caller_number?: string | null;
|
||||
caller_name?: string | null;
|
||||
caller_contact?: string | null;
|
||||
language?: string;
|
||||
okato?: string | null;
|
||||
address?: string | null;
|
||||
street?: string | null;
|
||||
building?: string | null;
|
||||
entrance?: string | null;
|
||||
floor?: string | null;
|
||||
coords?: [number, number] | null;
|
||||
incident_type?: IncidentType | null;
|
||||
description?: string | null;
|
||||
victims_count?: number | null;
|
||||
is_emergency?: boolean;
|
||||
life_threat?: boolean;
|
||||
evacuation_needed?: boolean;
|
||||
dds?: DDSCode | null;
|
||||
dispatch_order_at?: string | null;
|
||||
arrival_at?: string | null;
|
||||
fire?: FireDetails | null;
|
||||
police?: PoliceDetails | null;
|
||||
medical?: MedicalDetails | null;
|
||||
utility?: UtilityDetails | null;
|
||||
}
|
||||
|
||||
/** Правка карточки. Дебаунс 300 мс, шлётся только дельта. */
|
||||
export interface KioPatchIn {
|
||||
type: "kio.patch";
|
||||
fields: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Сервер распознал факт из речи либо шлёт эхо чужой правки. */
|
||||
export interface KioPatchOut {
|
||||
type: "kio.patch";
|
||||
fields: Record<string, unknown>;
|
||||
source: PatchSource;
|
||||
}
|
||||
|
||||
/** Полная карточка, не дельта: наблюдателю проще, рассинхрон дороже килобайт. */
|
||||
export interface KioState {
|
||||
type: "kio.state";
|
||||
kio: KIO;
|
||||
}
|
||||
|
||||
/** Уровень сложности сценария. */
|
||||
export type Level = "L1" | "L2" | "L3";
|
||||
|
||||
/** Уточняющие поля 03 — медицина. */
|
||||
export interface MedicalDetails {
|
||||
reason?: string | null;
|
||||
conscious?: boolean | null;
|
||||
breathing?: boolean | null;
|
||||
can_move?: boolean | null;
|
||||
age?: number | null;
|
||||
}
|
||||
|
||||
/** Метрика оценки: факт против норматива со ссылкой. Не балл, а обоснование. */
|
||||
export interface Metric {
|
||||
key: string;
|
||||
title: string;
|
||||
fact: string;
|
||||
norm: string;
|
||||
ref?: string | null;
|
||||
passed: boolean;
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
export interface ModeSet {
|
||||
type: "mode.set";
|
||||
mode: SessionMode;
|
||||
}
|
||||
|
||||
/** Состояние звонящего. Фон подачи, не факты. */
|
||||
export type Mood = "calm" | "worried" | "panic" | "aggressive" | "confused";
|
||||
|
||||
/** `auto` — сервер распознал факт из речи, `operator` — эхо правки курсанта. */
|
||||
export type PatchSource = "auto" | "operator";
|
||||
|
||||
/** Уточняющие поля 02 — полиция. */
|
||||
export interface PoliceDetails {
|
||||
offence_kind?: string | null;
|
||||
suspects?: string | null;
|
||||
vehicle?: string | null;
|
||||
}
|
||||
|
||||
export interface ReferencePlay {
|
||||
type: "reference.play";
|
||||
}
|
||||
|
||||
/** Автопроигрывание эталонного звонка на внешнем мониторе. */
|
||||
export interface ReferenceStarted {
|
||||
type: "reference.started";
|
||||
scenario_id: string;
|
||||
}
|
||||
|
||||
export type ResponseStatus = "registered" | "transferred" | "in_progress" | "closed";
|
||||
|
||||
export interface ScenarioPublish {
|
||||
type: "scenario.publish";
|
||||
scenario_id: string;
|
||||
}
|
||||
|
||||
export interface ScenarioStart {
|
||||
type: "scenario.start";
|
||||
scenario_id: string;
|
||||
trainee: string;
|
||||
group_id?: string | null;
|
||||
mode: SessionMode;
|
||||
}
|
||||
|
||||
/** Коррекция оценки. Автооценка сохраняется рядом. */
|
||||
export interface ScoreOverride {
|
||||
type: "score.override";
|
||||
session_id: string;
|
||||
verdict: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export interface ScoreReady {
|
||||
type: "score.ready";
|
||||
session_id: string;
|
||||
}
|
||||
|
||||
export interface SelfAssessment {
|
||||
missed: Array<string>;
|
||||
comment?: string;
|
||||
submitted_at: string;
|
||||
}
|
||||
|
||||
/** Самооценка до показа автооценки. */
|
||||
export interface SelfAssessmentSubmit {
|
||||
type: "self_assessment.submit";
|
||||
missed: Array<string>;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface SessionEnded {
|
||||
type: "session.ended";
|
||||
reason: CallEndReason;
|
||||
}
|
||||
|
||||
/** Режим сессии. Меняет доступность подсказок и протоколирование,
|
||||
но не поведение звонящего (docs/product/MODES.md). */
|
||||
export type SessionMode = "training" | "exam" | "self";
|
||||
|
||||
/** Единица истории: из отчётов складываются профиль, дельта попыток, аналитика. */
|
||||
export interface SessionReport {
|
||||
session_id: string;
|
||||
scenario_id: string;
|
||||
mode: SessionMode;
|
||||
attempt?: number;
|
||||
transcript: Array<TranscriptEntry>;
|
||||
findings: Array<Finding>;
|
||||
metrics: Array<Metric>;
|
||||
competencies: Array<CompetencyScore>;
|
||||
reference_questions: Array<HintShown>;
|
||||
missed_checklist: Array<string>;
|
||||
hints_used: Array<HintUsage>;
|
||||
self_assessment?: SelfAssessment | null;
|
||||
score_auto: number;
|
||||
score_final: number;
|
||||
overridden_by?: string | null;
|
||||
override_comment?: string | null;
|
||||
}
|
||||
|
||||
/** Обязательно при подключении: монитор в классе включают посреди занятия. */
|
||||
export interface SessionSnapshot {
|
||||
type: "session.snapshot";
|
||||
session_id: string;
|
||||
scenario_id: string;
|
||||
scenario_title: string;
|
||||
level: Level;
|
||||
mode: SessionMode;
|
||||
trainee_name?: string | null;
|
||||
started_at?: string | null;
|
||||
kio: KIO;
|
||||
transcript: Array<TranscriptEntry>;
|
||||
timers: Array<TimerSnapshot>;
|
||||
hints_used?: number;
|
||||
ended?: boolean;
|
||||
}
|
||||
|
||||
export interface SessionStop {
|
||||
type: "session.stop";
|
||||
}
|
||||
|
||||
export type Speaker = "caller" | "operator";
|
||||
|
||||
export interface SttFinal {
|
||||
type: "stt.final";
|
||||
text: string;
|
||||
at: string;
|
||||
}
|
||||
|
||||
export interface SttPartial {
|
||||
type: "stt.partial";
|
||||
text: string;
|
||||
}
|
||||
|
||||
/** Коды одинаковы на бэке, фронте и в отчёте. */
|
||||
export type TimerCode = "answer" | "interview" | "dds_notify" | "dds_ack" | "zone_check" | "callback" | "close";
|
||||
|
||||
/** Один таймер в событии `timer.tick`. */
|
||||
export interface TimerSnapshot {
|
||||
code: TimerCode;
|
||||
elapsed_ms: number;
|
||||
limit_ms: number;
|
||||
state: TimerState;
|
||||
/** Номер попытки для callback */
|
||||
attempt?: number;
|
||||
/** Остановлен событием, не тикает */
|
||||
stopped?: boolean;
|
||||
}
|
||||
|
||||
/** Единственное место, где цвет несёт смысл (docs/arch/FRONTEND.md). */
|
||||
export type TimerState = "ok" | "warn" | "violated";
|
||||
|
||||
/** Раз в секунду, не на каждое изменение: таймеров дюжина, UI рисует секунды. */
|
||||
export interface TimerTick {
|
||||
type: "timer.tick";
|
||||
timers: Array<TimerSnapshot>;
|
||||
}
|
||||
|
||||
export interface TranscriptAppend {
|
||||
type: "transcript.append";
|
||||
entry: TranscriptEntry;
|
||||
}
|
||||
|
||||
/** Реплика в ленте. `ref` — якорь для пометок преподавателя и отметок разбора. */
|
||||
export interface TranscriptEntry {
|
||||
ref: string;
|
||||
speaker: Speaker;
|
||||
text: string;
|
||||
at: string;
|
||||
mood?: Mood | null;
|
||||
}
|
||||
|
||||
export interface TtsBegin {
|
||||
type: "tts.begin";
|
||||
utterance_id: string;
|
||||
}
|
||||
|
||||
/** Оператор перебил. Фронт мгновенно чистит очередь воспроизведения. */
|
||||
export interface TtsCancel {
|
||||
type: "tts.cancel";
|
||||
utterance_id: string;
|
||||
reason?: "barge_in" | "director";
|
||||
}
|
||||
|
||||
export interface TtsEnd {
|
||||
type: "tts.end";
|
||||
utterance_id: string;
|
||||
}
|
||||
|
||||
/** Уточняющие поля 04 / ЖКХ. */
|
||||
export interface UtilityDetails {
|
||||
failure_kind?: string | null;
|
||||
scale?: string | null;
|
||||
threat_to_residents?: boolean | null;
|
||||
}
|
||||
|
||||
export interface ZoneDecision {
|
||||
type: "zone.decision";
|
||||
in_zone: boolean;
|
||||
}
|
||||
|
||||
// Союзы по каналам — смотри docs/arch/CONTRACT.md#каналы
|
||||
|
||||
export type ServerToTrainee = CallIncoming | CallStarted | SttPartial | SttFinal | CallerUtterance | TtsBegin | TtsEnd | TtsCancel | BgStart | BgStop | EraData | KioPatchOut | HintShown | TimerTick | CallEnded | ScoreReady | ErrorEvent;
|
||||
|
||||
export type TraineeToServer = CallAnswer | KioPatchIn | HintRequest | SelfAssessmentSubmit | DdsDispatch | CallHangup | CallbackDial;
|
||||
|
||||
export type ServerToObserver = SessionSnapshot | TranscriptAppend | KioState | TimerTick | ModeSet | HintShown | BgStart | BgStop | CallerUtterance | SessionEnded | ScoreReady | InstructorNoteShown | ReferenceStarted | ErrorEvent;
|
||||
|
||||
export type InstructorToServer = ScenarioStart | DirectorInject | ReferencePlay | InstructorNoteAdd | ScoreOverride | ScenarioPublish | SessionStop;
|
||||
|
||||
export type ServerToStation = CardReceived | TimerTick | SessionEnded | ErrorEvent;
|
||||
|
||||
export type StationToServer = CardAck | CardBounce | ZoneDecision | CrewDispatched | CrewArrived;
|
||||
|
||||
export type EventName =
|
||||
"bg.start"
|
||||
| "bg.stop"
|
||||
| "call.answer"
|
||||
| "call.ended"
|
||||
| "call.hangup"
|
||||
| "call.incoming"
|
||||
| "call.started"
|
||||
| "callback.dial"
|
||||
| "caller.utterance"
|
||||
| "card.ack"
|
||||
| "card.bounce"
|
||||
| "card.received"
|
||||
| "crew.arrived"
|
||||
| "crew.dispatched"
|
||||
| "dds.dispatch"
|
||||
| "director.inject"
|
||||
| "era.data"
|
||||
| "error"
|
||||
| "hint.request"
|
||||
| "hint.shown"
|
||||
| "instructor_note.add"
|
||||
| "instructor_note.shown"
|
||||
| "kio.patch"
|
||||
| "kio.state"
|
||||
| "mode.set"
|
||||
| "reference.play"
|
||||
| "reference.started"
|
||||
| "scenario.publish"
|
||||
| "scenario.start"
|
||||
| "score.override"
|
||||
| "score.ready"
|
||||
| "self_assessment.submit"
|
||||
| "session.ended"
|
||||
| "session.snapshot"
|
||||
| "session.stop"
|
||||
| "stt.final"
|
||||
| "stt.partial"
|
||||
| "timer.tick"
|
||||
| "transcript.append"
|
||||
| "tts.begin"
|
||||
| "tts.cancel"
|
||||
| "tts.end"
|
||||
| "zone.decision";
|
||||
11
frontend/src/shared/ui/Stub.tsx
Normal file
11
frontend/src/shared/ui/Stub.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Заглушка экрана. Каждая ссылается на карточку, которая её закрывает.
|
||||
export function Stub({ title, card }: { title: string; card: string }) {
|
||||
return (
|
||||
<main className="stub">
|
||||
<h1>{title}</h1>
|
||||
<p>
|
||||
Экран не реализован. Карточка: <code>tasks/{card}.md</code>
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
29
frontend/src/styles.css
Normal file
29
frontend/src/styles.css
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* Казённая база: системный шрифт, плотная сетка, три состояния норматива.
|
||||
Продуктовой эстетики здесь быть не должно (docs/arch/FRONTEND.md). */
|
||||
:root {
|
||||
--bg: #f2f3f5;
|
||||
--panel: #ffffff;
|
||||
--line: #b9bec6;
|
||||
--text: #14171a;
|
||||
--ok: #1f6f3d;
|
||||
--warn: #9a6b00;
|
||||
--violated: #a51d1d;
|
||||
font-family: system-ui, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.stub {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.stub h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
19
frontend/tsconfig.json
Normal file
19
frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "src",
|
||||
"paths": { "@/*": ["*"] }
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
15
frontend/vite.config.ts
Normal file
15
frontend/vite.config.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// Бэкенд один, порт один: фронт ходит на /api и /ws через прокси,
|
||||
// чтобы в классе не было разъезда адресов между машинами.
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
"/api": { target: "http://localhost:8000", changeOrigin: true },
|
||||
"/ws": { target: "ws://localhost:8000", ws: true },
|
||||
},
|
||||
},
|
||||
resolve: { alias: { "@": "/src" } },
|
||||
});
|
||||
12
scenarios/checklists/fire.yaml
Normal file
12
scenarios/checklists/fire.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Общий чек-лист по классификатору 01 — МЧС.
|
||||
# Наследуется сценариями через `extends`, дополняется локально.
|
||||
# Методика: docs/product/METHODOLOGY.md#чек-листы-опроса
|
||||
id: fire
|
||||
dds: "01"
|
||||
checklist:
|
||||
- { id: q_address, question: "Назовите адрес: улица, дом, квартира, этаж?" }
|
||||
- { id: q_what_burns, question: "Что именно горит?" }
|
||||
- { id: q_people, question: "Есть ли люди в помещении?" }
|
||||
- { id: q_smoke, question: "Куда идёт дым, есть ли задымление подъезда?" }
|
||||
- { id: q_gas, question: "Есть ли газ, можно ли перекрыть?" }
|
||||
- { id: q_caller, question: "Представьтесь и оставьте контактный телефон" }
|
||||
68
scenarios/fire-apartment-l2.yaml
Normal file
68
scenarios/fire-apartment-l2.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Первый сценарий — тот самый, на котором замыкается вертикальный срез
|
||||
# (docs/hackathon/CASE-DECISION.md: в первый день один сценарий целиком).
|
||||
# Формат: docs/spec/SCENARIO-FORMAT.md
|
||||
id: fire-apartment-l2
|
||||
title: "Пожар в квартире, паникующий заявитель"
|
||||
type: fire
|
||||
level: L2
|
||||
topics: [fires]
|
||||
modes: [training, exam, self]
|
||||
|
||||
extends: checklists/fire.yaml
|
||||
|
||||
persona:
|
||||
base: panic
|
||||
arc:
|
||||
- { stage: registration, mood: panic }
|
||||
- { stage: interview, mood: panic }
|
||||
- { stage: dispatch, mood: worried }
|
||||
|
||||
background:
|
||||
loop: fire_crackle.mp3
|
||||
gain_db: -18
|
||||
|
||||
first_line: "Алло! Алло! Помогите! Горим! А-а-а!"
|
||||
|
||||
facts:
|
||||
- id: f_address
|
||||
value: "улица Ленина, 14, квартира 47, 5-й этаж"
|
||||
reveal_on: { question: q_address }
|
||||
- id: f_what_burns
|
||||
value: "горит балкон, дым пошёл в квартиру"
|
||||
reveal_on: { question: q_what_burns }
|
||||
- id: f_people
|
||||
value: "в квартире жена и ребёнок, не выходят"
|
||||
reveal_on: { question: q_people }
|
||||
- id: f_smoke
|
||||
value: "дым идёт в подъезд, на площадке ничего не видно"
|
||||
reveal_on: { question: q_smoke }
|
||||
- id: f_gas
|
||||
value: "плита газовая, вентиль в кухне, перекрыть не успел"
|
||||
reveal_on: { question: q_gas }
|
||||
hidden: true
|
||||
|
||||
checklist:
|
||||
- { id: q_address, fact: f_address }
|
||||
- { id: q_what_burns, fact: f_what_burns }
|
||||
- { id: q_people, fact: f_people }
|
||||
- { id: q_smoke, fact: f_smoke }
|
||||
- { id: q_gas, fact: f_gas }
|
||||
|
||||
required_fields:
|
||||
- address
|
||||
- floor
|
||||
- incident_type
|
||||
- dds
|
||||
- victims_count
|
||||
- description
|
||||
|
||||
ground_truth:
|
||||
# Выводится из фактов кодом при загрузке, здесь — для наглядности примера.
|
||||
incident_type: fire
|
||||
dds: "01"
|
||||
required_facts: [f_address, f_what_burns, f_people]
|
||||
address: "улица Ленина, 14"
|
||||
victims: 2
|
||||
|
||||
tree:
|
||||
pregenerated: false
|
||||
Loading…
Reference in a new issue