diff --git a/.env.example b/.env.example index a20f76d..233345c 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,24 @@ # ---- 应用 ---- +# 运行环境: development | production +# production 启动时会强制校验:SECRET_KEY 非空且非弱值、鉴权已配置、DB 弱密码阻断。 APP_ENV=development LOG_LEVEL=INFO API_PORT=8000 FRONTEND_PORT=3000 +# ---- 生产环境启动必填(缺少则拒绝启动) ---- +# 1. SECRET_KEY: 加密主密钥与会话签名根密钥。 +# 生成: openssl rand -base64 32 +# 严禁使用 changeme/secret/123456 等弱值;变更后已加密配置无法解密。 +SECRET_KEY= + +# 2. 管理鉴权(至少一项): +# - ADMIN_PASSWORD: 后台登录初始密码(启动后自动 scrypt 哈希入库,之后在「系统配置」页修改) +# - ADMIN_API_KEY: 脚本直连接口用的密钥(请求头 X-API-Key) +# 两者都留空 = 不启用鉴权(仅本地开发)。 +ADMIN_PASSWORD= +ADMIN_API_KEY= + # ---- 数据库 ---- POSTGRES_USER=football POSTGRES_PASSWORD=football diff --git a/Dockerfile b/Dockerfile index b404224..f592023 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,11 +8,11 @@ ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple # P2-5: 创建非 root 用户(容器安全最佳实践) RUN groupadd --system profeto && useradd --system --gid profeto profeto -RUN pip install --no-cache-dir hatchling -# Fix 1: 不再复制 README.md(.dockerignore 排除了 *.md) -COPY pyproject.toml ./ -COPY src ./src +# 可复现构建:先安装锁定版本的依赖(含哈希校验),再安装本项目 +COPY pyproject.toml requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt +COPY src ./src RUN pip install --no-cache-dir . # 将工作目录所有权移交给非 root 用户 @@ -23,5 +23,5 @@ EXPOSE 8000 # 以非 root 用户运行 USER profeto -# Fix 3: 启动时先跑迁移,再启 uvicorn +# 启动时先跑迁移,再启 uvicorn CMD ["sh", "-c", "alembic upgrade head && uvicorn src.api.app:app --host 0.0.0.0 --port 8000"] diff --git a/docker-compose.yml b/docker-compose.yml index cec9594..695181e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,12 @@ services: # 必须覆盖 .env 中的 DATABASE_URL,因为 Settings 不读 DB_HOST/DB_PORT DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:?POSTGRES_USER 未设置}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD 未设置}@postgres:5432/${POSTGRES_DB:-football} env_file: .env + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8000/health/ready || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s depends_on: postgres: condition: service_healthy diff --git a/docs/04-agents.md b/docs/04-agents.md index e9e4942..ff952dc 100644 --- a/docs/04-agents.md +++ b/docs/04-agents.md @@ -129,6 +129,19 @@ agents/ 迭代 prompt 时:复制 `h2h_v1.md` → `h2h_v2.md`,改内容,传 `prompt_version: "v2"`。 `predictions.prompt_version` 存的是 `multi_v2`,与 single 模式的 `v1`/`v2` 天然分组,可在 eval summary 中 A/B 对比。 +### 版本纪律(强制) + +> **改 prompt 内容必须 bump 版本号(v2→v3),禁止默默修改 `*_v1.md` 内容却不改版本。** + +原因: +1. **可复现性**:`predictions.prompt_version` 决定哪份 prompt 产生了历史预测;篡改 v1 会让历史预测的 prompt 来源失真,eval 对比失效。 +2. **A/B 可信度**:`get_eval_summary` 按 `(provider, model, prompt_version)` 分组。若 v1 内容在不同时间指向不同 prompt,则 v1 桶内数据不可比。 +3. **缓存一致性**:模板内容 hash 写入缓存键(`_prompt_template_hash`),版本不变则 hash 不变,命中旧缓存。bump 版本自动让旧缓存失效。 + +**流程**:改 prompt → 新建 `*_v{N+1}.md` → 新请求传 `prompt_version=v{N+1}` → 旧版本文件保持不变(供历史复现)。 + +代码保证:写入 DB 的 `prompt_version` 与 `_load_prompt_template(version)` / `load_agent_prompt(name, version)` 加载的文件**严格一致**,不会漂移。 + ## 如何新增一个专家 Agent 三步: diff --git a/docs/06-deployment.md b/docs/06-deployment.md index 10073cf..622ba43 100644 --- a/docs/06-deployment.md +++ b/docs/06-deployment.md @@ -162,9 +162,60 @@ cat backup.sql | docker exec -i profeto-postgres psql -U football football ## 监控 -- `/health`: 存活检查 -- 日志:容器 stdout(`docker compose logs -f api`) -- 评估汇总:`GET /api/v1/eval/summary`(准确率/RMSAE/校准度) +### 健康检查 + +| 端点 | 含义 | HTTP 状态码 | +|---|---|---| +| `/health` | 存活检查(liveness) | 始终 200(进程在跑即活) | +| `/health/ready` | 就绪检查(readiness) | DB 可达 200,不可达 **503** | + +### 探针配置 + +#### Docker Compose + +`docker-compose.yml` 已为 `api` 服务配置 readiness: + +```yaml +healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8000/health/ready || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s +``` + +**要点**:必须指向 `/health/ready` 而非 `/health`——后者始终 200,在数据库故障时仍会接收流量,导致请求全部失败。 + +#### Kubernetes + +```yaml +livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 15 +readinessProbe: + httpGet: + path: /health/ready + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 3 +``` + +**两探针必须区分**: +- `livenessProbe` 用 `/health`:仅在进程死锁/崩溃时重启,避免误杀。 +- `readinessProbe` 用 `/health/ready`:DB 不可用时停止转发流量,恢复后自动切回。 + +#### 验证 + +```bash +# 宿主机直接运行(经本地 8000 端口) +python3 tests/test_health_ready.py +``` + +### 评估 ## 安全与限流 diff --git a/docs/07-development.md b/docs/07-development.md index c9701f3..ff031e1 100644 --- a/docs/07-development.md +++ b/docs/07-development.md @@ -2,32 +2,70 @@ ## 本地开发环境搭建 +### 锁定依赖策略 + +项目使用 [pip-tools](https://github.com/jazzband/pip-tools) 锁定依赖版本,确保本地、CI、Docker 三端一致: + +| 文件 | 用途 | 生成命令 | +|---|---|---| +| `requirements.txt` | 生产依赖锁定(含 SHA256 哈希) | `pip-compile pyproject.toml --generate-hashes` | +| `requirements-dev.txt` | 开发+CI 依赖锁定(含哈希) | `pip-compile pyproject.toml --extra dev --generate-hashes` | + +### 安装步骤 + ```bash # 1. 克隆并进入项目 cd Profeto -# 2. 安装依赖(含 dev) -pip install -e ".[dev]" +# 2. 创建虚拟环境 +python3.11 -m venv .venv +source .venv/bin/activate -# 3. 启动 PostgreSQL +# 3. 安装 pip-tools(用于同步锁定依赖) +pip install pip-tools + +# 4. 同步生产+开发依赖到当前环境(严格按 lock 文件版本,含哈希校验) +pip-sync requirements-dev.txt + +# 5. 启动 PostgreSQL(或在 .env 配置外部库) docker run -d --name profeto-pg \ -e POSTGRES_USER=football -e POSTGRES_PASSWORD=football -e POSTGRES_DB=football \ -p 5432:5432 postgres:16-alpine -# 4. 配置环境变量 +# 6. 配置环境变量 cp .env.example .env # 编辑 .env 填 LLM_API_KEY / BZZOIRO_KEY -# 5. 建表 +# 7. 建表 alembic upgrade head -# 6. 启动 API(热重载) +# 8. 启动 API(热重载) uvicorn src.api.app:app --reload -# 7. 启动前端(另一个终端) +# 9. 启动前端(另一个终端) cd frontend && npm install && npm run dev ``` +> **注意**:不要用 `pip install -e ".[dev]"` 直接安装——它按 pyproject 下界约束解析,版本可能与 lock 文件不一致。统一用 `pip-sync requirements-dev.txt` 保证三端一致。 + +### 变更依赖时 + +```bash +# 1. 编辑 pyproject.toml(调整依赖或版本约束) + +# 2. 重新生成 lock 文件(含哈希) +pip-compile pyproject.toml --generate-hashes --output-file=requirements.txt --index-url=https://pypi.tuna.tsinghua.edu.cn/simple +pip-compile pyproject.toml --extra dev --generate-hashes --output-file=requirements-dev.txt --index-url=https://pypi.tuna.tsinghua.edu.cn/simple + +# 3. 同步到本地环境 +pip-sync requirements-dev.txt + +# 4. 提交 lock 文件 +git add requirements.txt requirements-dev.txt pyproject.toml +``` + +> **禁止无故大升级主版本依赖**:仅升级真正需要的包,并重新跑全量测试。 + ## 项目结构 ``` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b52a89c..98e73a5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,15 @@ /** * 主应用入口 * - * 整合前台(报纸风格)和后台(暗色管理)的路由。 - * - / → 先知(Profeto)主站 - * - /admin/* → 管理后台 + * 顶层三分区导航: + * - 比赛/预测 → 公开,报纸风赛程 + 预测 + * - 评估 → 只读(后端需 admin 鉴权,未登录引导登录) + * - 管理 → 采集/回测/配置等(需登录) */ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { ErrorBoundary } from './components/ErrorBoundary' +import { useState } from 'react' import Matches from './pages/Matches' import { adminRoutes } from './admin/routes' @@ -21,10 +23,34 @@ function dateLine(): string { }) } +/** 顶层导航三分区 */ +type TopView = 'matches' | 'eval' | 'admin' + +function TopNav({ onNavigate }: { onNavigate: (v: TopView) => void }) { + const go = (v: TopView) => { + onNavigate(v) + const path = v === 'matches' ? '/' : v === 'eval' ? '/admin/eval' : '/admin' + window.location.assign(path) + } + return ( + + ) +} + function HomePage() { return (
- {/* ── 报头:粗线 + 居中刊名 + 报眉 ── */} + {/* ── 报头:粗线 + 居中刊名 + 顶层导航 ── */}
@@ -40,14 +66,8 @@ function HomePage() {
{dateLine()} - - - 管理后台 -
+ {}} />
@@ -64,12 +84,18 @@ function HomePage() { ) } +/** 管理入口页:直接导向 /admin,由 AdminLayout 处理鉴权(未登录显示登录页) */ +function AdminEntry() { + return +} + export default function App() { return ( } /> + } /> {adminRoutes.map(route => ( {route.children.map(child => ( @@ -88,3 +114,4 @@ export default function App() { ) } + diff --git a/frontend/src/admin/AdminLayout.tsx b/frontend/src/admin/AdminLayout.tsx index 4c2a4d2..3712a14 100644 --- a/frontend/src/admin/AdminLayout.tsx +++ b/frontend/src/admin/AdminLayout.tsx @@ -12,16 +12,18 @@ import { fetchHealth } from './dal' import Login from './Login' const NAV_ITEMS = [ + // ── 观测(只读) ── { to: '/admin', label: '仪表盘', icon: '◇', end: true }, - { to: '/admin/collection', label: '数据采集', icon: '◈' }, + { to: '/admin/eval', label: '评估', icon: '◈' }, + { to: '/admin/monitoring', label: '监控', icon: '◐' }, + { to: '/admin/logs', label: '日志', icon: '▤' }, + // ── 操作(写入,需登录) ── { to: '/admin/predictions', label: '预测管理', icon: '◆' }, - { to: '/admin/backtest', label: '回测管理', icon: '◉' }, - { to: '/admin/monitoring', label: '监控面板', icon: '◐' }, + { to: '/admin/collection', label: '数据采集', icon: '◈' }, + { to: '/admin/backtest', label: '回测', icon: '◉' }, { to: '/admin/data-sources', label: '数据源', icon: '◫' }, { to: '/admin/llm-config', label: 'LLM 配置', icon: '◬' }, { to: '/admin/config', label: '系统配置', icon: '◑' }, - { to: '/admin/logs', label: '系统日志', icon: '▤' }, - { to: '/admin/eval', label: '评估管理', icon: '◈' }, ] /** 报眉日期行,与前台同款式 */ @@ -220,6 +222,7 @@ export default function AdminLayout() { diff --git a/frontend/src/admin/api.ts b/frontend/src/admin/api.ts index f44dc52..1bfd8e6 100644 --- a/frontend/src/admin/api.ts +++ b/frontend/src/admin/api.ts @@ -25,7 +25,7 @@ export class ApiError extends Error { async function request( path: string, - options: RequestInit & { timeoutMs?: number } = {}, + options: RequestInit & { timeoutMs?: number; skipAuthHandling?: boolean } = {}, ): Promise { // 修复: 正确拼接 API_BASE const url = path.startsWith('http') @@ -34,7 +34,7 @@ async function request( ? path // 已经是绝对路径(如 /health) : `${API_BASE}${path}` - const { timeoutMs = TIMEOUT_MS, ...fetchOptions } = options + const { timeoutMs = TIMEOUT_MS, skipAuthHandling, ...fetchOptions } = options const controller = new AbortController() const timer = setTimeout(() => controller.abort(), timeoutMs) @@ -59,7 +59,8 @@ async function request( detail && typeof detail === 'object' && 'detail' in detail ? String((detail as { detail: unknown }).detail) : `HTTP ${res.status}: ${res.statusText}` - if (res.status === 401) { + // 401 仅在没有显式跳过时广播未登录事件(改密接口的 401 表示当前密码错误,非会话过期) + if (res.status === 401 && !skipAuthHandling) { message += '\n登录已过期,请重新登录。' window.dispatchEvent(new CustomEvent(UNAUTHORIZED_EVENT)) } @@ -88,9 +89,9 @@ async function request( export const api = { get: (path: string) => request(path), - post: (path: string, body?: unknown, opts?: { timeoutMs?: number }) => + post: (path: string, body?: unknown, opts?: { timeoutMs?: number; skipAuthHandling?: boolean }) => request(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined, ...opts }), - put: (path: string, body?: unknown, opts?: { timeoutMs?: number }) => + put: (path: string, body?: unknown, opts?: { timeoutMs?: number; skipAuthHandling?: boolean }) => request(path, { method: 'PUT', body: body ? JSON.stringify(body) : undefined, ...opts }), delete: (path: string) => request(path, { method: 'DELETE' }), } @@ -118,10 +119,12 @@ export function fetchAuthState(): Promise<{ /** 修改管理员密码(成功后所有会话失效,需重新登录) */ export function changePassword(currentPassword: string, newPassword: string): Promise<{ ok: boolean; message: string }> { - return api.post(`${API_BASE}/auth/change-password`, { - current_password: currentPassword, - new_password: newPassword, - }) + // skipAuthHandling: 改密接口的 401 表示「当前密码错误」,非会话过期,不要触发登出 + return api.post( + `${API_BASE}/auth/change-password`, + { current_password: currentPassword, new_password: newPassword }, + { skipAuthHandling: true }, + ) } export { API_BASE } diff --git a/frontend/src/admin/components.tsx b/frontend/src/admin/components.tsx index ca36148..f7eb1af 100644 --- a/frontend/src/admin/components.tsx +++ b/frontend/src/admin/components.tsx @@ -8,6 +8,8 @@ import { ReactNode } from 'react' +import { ApiError } from './api' + // ── 卡片 ──────────────────────────────────────────────────────── export function Card({ @@ -271,7 +273,7 @@ export function Alert({ message, onClose, }: { - kind: 'error' | 'ok' | 'info' + kind: 'error' | 'ok' | 'info' | 'warning' title: string message?: string onClose?: () => void @@ -279,17 +281,19 @@ export function Alert({ const style = kind === 'error' ? 'border-press bg-press-wash' - : kind === 'ok' - ? 'border-ink-900 bg-paper-100' - : 'border-ink-300 bg-paper-50' - const titleCls = kind === 'error' ? 'text-press' : 'text-ink-900' + : kind === 'warning' + ? 'border-press bg-press-wash/60' + : kind === 'ok' + ? 'border-ink-900 bg-paper-100' + : 'border-ink-300 bg-paper-50' + const titleCls = kind === 'error' || kind === 'warning' ? 'text-press' : 'text-ink-900' return (

+
+ + setModel(e.target.value)} + placeholder="如 deepseek-chat / 留空使用默认" + className="field w-full" + /> +
+ {error && setError(null)} />} ) + : undefined + } + /> -
+
{summary.scored}/{summary.total}
已评分 / 总场数
+
+
+ {summary.success}/{summary.degraded} +
+
+ 成功 / 降级 (degraded) +
+
{summary.accuracy_1x2 !== undefined && summary.accuracy_1x2 !== null @@ -223,6 +279,11 @@ export default function BacktestPage() {
平均置信度
+ {summary.degraded > 0 && ( +
+ 有 {summary.degraded} 场预测降级(专家无有效结论),未计入准确率分子。建议检查该时段数据完整性或改用单次模式。 +
+ )}
diff --git a/frontend/src/admin/pages/DataSources.tsx b/frontend/src/admin/pages/DataSources.tsx index 5b77530..b82f8c4 100644 --- a/frontend/src/admin/pages/DataSources.tsx +++ b/frontend/src/admin/pages/DataSources.tsx @@ -1,20 +1,13 @@ -/** - * Admin 后台 - 数据源管理页面(报刊风) - * - * 功能: - * - 显示各数据源配置状态(脱敏),标明值来源:DB 覆盖 / .env 默认 / 未配置 - * - 在线修改数据源 API Key(写入 app_settings,覆盖 .env;清除则回落) - * - 测试连接按钮(后端真实请求上游一次,不触发入库) - */ - import { useEffect, useState, useCallback } from 'react' import { fetchDataSourceStatuses, + fetchIngestStatus, + fetchAdminStats, updateSetting, clearSetting, testDataSourceConnection, } from '../dal' -import type { DataSourceStatus, DataSourceTestResult } from '../types' +import type { DataSourceStatus, DataSourceTestResult, IngestSourceStatus, AdminStats } from '../types' import SettingRow from '../SettingRow' import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner, SkeletonBlock } from '../components' @@ -31,6 +24,9 @@ export default function DataSourcesPage() { const [sources, setSources] = useState([]) const [loading, setLoading] = useState(true) const [loadError, setLoadError] = useState('') + const [ingestStats, setIngestStats] = useState>({}) + const [ingestLoading, setIngestLoading] = useState(true) + const [stats, setStats] = useState(null) const [testingSource, setTestingSource] = useState(null) const [testResults, setTestResults] = useState>({}) @@ -52,16 +48,41 @@ export default function DataSourcesPage() { } }, []) + // 健康状态(只读,与配置加载并行;失败不阻塞配置页) + const loadIngest = useCallback(async () => { + setIngestLoading(true) + try { + const { sources } = await fetchIngestStatus() + setIngestStats(Object.fromEntries(sources.map(x => [x.name, x]))) + } catch { + /* ignore */ + } finally { + setIngestLoading(false) + } + }, []) + + // 管理区统计(只读) + const loadStats = useCallback(async () => { + try { + setStats(await fetchAdminStats()) + } catch { + /* ignore */ + } + }, []) + useEffect(() => { loadSources() - }, [loadSources]) + loadIngest() + loadStats() + }, [loadSources, loadIngest, loadStats]) async function handleTest(sourceName: string) { setTestingSource(sourceName) + setTestResults(prev => ({ ...prev, [sourceName]: { ok: false, status: null, latency_ms: 0, detail: '测试中...' } })) try { const result = await testDataSourceConnection(sourceName) setTestResults(prev => ({ ...prev, [sourceName]: result })) - } catch (err) { + } catch (err: unknown) { const msg = err instanceof Error ? err.message.split('\n')[0] : '连接失败' setTestResults(prev => ({ ...prev, [sourceName]: { ok: false, status: null, latency_ms: 0, detail: msg } })) } finally { @@ -69,6 +90,47 @@ export default function DataSourcesPage() { } } + // 渲染数据源健康块(最近采集 + 异常提示) + function renderHealth(sourceName: string) { + const st = ingestStats[sourceName] + if (!st) { + return ( +
+ 最近成功采集 + {ingestLoading ? '加载中...' : '暂无数据'} +
+ ) + } + const ago = st.last_success_at ? formatTime(st.last_success_at) : '暂无记录' + const issues: string[] = [] + if (st.status === 'key_not_configured') issues.push('未配置 API Key') + else if (st.status === 'no_data') issues.push('本地无数据,建议补采') + if (st.last_failure) issues.push('近期有采集失败') + return ( +
+
+ 最近成功采集 + {ago} +
+
+ 已入库(近期) + {st.recent_count.toLocaleString()} 条 +
+ {st.note &&

{st.note}

} + {issues.length > 0 && ( +

+ {issues.join(' / ')} — 请前往「数据采集」补采 +

+ )} + {st.last_failure && ( +

+ 最近失败: {st.last_failure.detail.slice(0, 60)} +

+ )} +
+ ) + } + async function handleSave(key: string, value: string) { setBusyKey(key) setRowNotice(null) @@ -98,11 +160,12 @@ export default function DataSourcesPage() { } } + return (
{loadError && ( @@ -132,7 +195,6 @@ export default function DataSourcesPage() { return ( - {/* 头部 */}

{source.label}

@@ -142,7 +204,6 @@ export default function DataSourcesPage() {

{source.description}

- {/* 配置项 */} {source.settings.length > 0 ? (
{source.settings.map(setting => ( @@ -151,10 +212,7 @@ export default function DataSourcesPage() { setting={setting} editing={editingKey === setting.key} busy={busyKey === setting.key} - onEdit={() => { - setEditingKey(setting.key) - setRowNotice(null) - }} + onEdit={() => { setEditingKey(setting.key); setRowNotice(null) }} onCancel={() => setEditingKey(null)} onSave={v => handleSave(setting.key, v)} onClear={() => handleClear(setting.key)} @@ -165,36 +223,21 @@ export default function DataSourcesPage() {

无需 API Key

)} - {/* 行级操作提示 */} {rowNotice && cardKeys.includes(rowNotice.key) && ( - + )} - {/* 最近采集 */} -
- 最近采集 - {formatTime(source.last_ingestion)} -
+ {/* 数据源健康:最近采集 + 异常提示 */} + {renderHealth(source.name)} - {/* 测试结果(进行中不渲染,避免占位被误读为失败) */} {result && testingSource !== source.name && ( )} - {/* 测试按钮 */}
)} - {/* 配置说明 */} + {/* 近期活动统计(只读) */} + {stats && stats.predictions && ( + + + +
+
+
{stats.predictions.last_24h}
+
近 24 小时
+
+
+
{stats.predictions.last_7d}
+
近 7 天
+
+
+
{stats.predictions.total}
+
总计
+
+
+
+
+ )} +

- 在此保存的配置存于数据库 app_settings 表并立即生效, - 优先于服务器 .env 中的同名变量;点「回落 .env」删除覆盖值。 - 若两者都未配置,相应采集功能会报「Key 未设置」。 + 保存的配置存于数据库 app_settings立即生效,优先于 .env;「回落 .env」删除覆盖值。

- 敏感值只显示末 4 位(不足 8 位全遮),完整值不回传浏览器。 + 「最近成功采集」为只读健康快照(不触发采集);近似数据已在行内标注。伤停源会区分「未配置 Key / 无数据 / 有数据」。

- 「测试连接」会真实请求上游接口一次:连通且密钥有效 → 成功并显示耗时; - HTTP 401/403 → 密钥无效;其他状态码或超时 → 按详情提示排查。 - 测试不写入任何数据。 + 「测试连接」真实请求上游一次;异常提示会引导前往「数据采集」补采,避免在空数据上误操作。

diff --git a/frontend/src/admin/pages/EvalPage.tsx b/frontend/src/admin/pages/EvalPage.tsx index c93cb87..adb1e8e 100644 --- a/frontend/src/admin/pages/EvalPage.tsx +++ b/frontend/src/admin/pages/EvalPage.tsx @@ -155,7 +155,7 @@ export default function EvalPage() { {loading ? ( @@ -163,12 +163,15 @@ export default function EvalPage() {
) : summary.length === 0 ? ( - + ) : ( ( + {row.prompt_version ?? '—'} + ) }, { key: 'total', label: '评估条数' }, { key: 'accuracy_1x2', label: '1X2 准确率', render: (row: any) => ( {row.accuracy_1x2 != null ? `${row.accuracy_1x2}%` : '—'} @@ -179,9 +182,24 @@ export default function EvalPage() { { key: 'avg_subjective_confidence', label: '平均置信度', render: (row: any) => ( {row.avg_subjective_confidence != null ? row.avg_subjective_confidence.toFixed(2) : '—'} ) }, + { key: 'calibration', label: '置信度校准(桶命中率)', render: (row: any) => ( + row.calibration ? ( +
+ {Object.entries(row.calibration).map(([name, b]: [string, any]) => ( + + {name}: + + {b.hit_rate != null ? `${b.hit_rate}%` : '—'} + + ({b.total}) + + ))} +
+ ) : '—' + ) }, ]} data={summary} - rowKey={(row: any) => `${row.provider}-${row.model}`} + rowKey={(row: any) => `${row.provider}-${row.model}-${row.prompt_version ?? ''}`} emptyText="暂无评估数据" /> )} diff --git a/frontend/src/admin/types.ts b/frontend/src/admin/types.ts index eff6d63..bc88db1 100644 --- a/frontend/src/admin/types.ts +++ b/frontend/src/admin/types.ts @@ -101,16 +101,27 @@ export interface CollectionRequest { // ── 评估 & 回测 ───────────────────────────────────────────────── +export interface EvalCalibrationBucket { + total: number + /** 该桶命中率,百分数;样本不足为 null */ + hit_rate: number | null +} + +export interface EvalSummaryRow { + provider: string + model: string + prompt_version: string | null + total: number + /** 1X2 准确率,百分数 0-100 */ + accuracy_1x2?: number + avg_score_rmse?: number | null + avg_subjective_confidence?: number | null + /** 置信度校准:按主观置信度分桶的命中率 */ + calibration?: Record +} + export interface EvalSummary { - summary: Array<{ - provider: string - model: string - total: number - /** 1X2 准确率,百分数 0-100 */ - accuracy_1x2?: number - avg_score_rmse?: number | null - avg_subjective_confidence?: number | null - }> + summary: Array /** 全量已结算数 */ total_settled: number /** 应用筛选后的已结算数 */ @@ -135,16 +146,11 @@ export interface BacktestRequest { export interface BacktestSummary { total: number scored: number + success: number + degraded: number accuracy_1x2?: number avg_score_rmse?: number - results?: Array<{ - match_id: number - actual_home: number - actual_away: number - pred_home?: number - pred_away?: number - correct_1x2: boolean - }> + avg_subjective_confidence?: number } // ── 数据源配置 ────────────────────────────────────────────────── @@ -249,3 +255,92 @@ export interface LogEntry { logger: string message: string } + +// ── 数据源健康/最近采集状态 ───────────────────────────────────── + +export interface IngestLastFailure { + at: string + logger: string + detail: string + note: string +} + +export interface IngestSourceStatus { + name: string + label: string + key_configured: boolean + base_url?: string + reachable: boolean | null + status?: 'key_not_configured' | 'no_data' | 'has_data' + last_success_at: string | null + latest_match_date?: string | null + recent_count: number + note: string + last_failure: IngestLastFailure | null +} + +// ── 比赛详情 ───────────────────────────────────────────────────── + +export interface MatchRecentPrediction { + id: number + provider: string + model: string + mode: string + pred_home_goals: number | null + pred_away_goals: number | null + alt_pred_home_goals: number | null + alt_pred_away_goals: number | null + pred_1x2: string | null + subjective_confidence: number | null + reasoning: string | null + status: string + settled: boolean + correct_1x2?: boolean + created_at: string + actual_home_goals: number | null + actual_away_goals: number | null + agent_outputs?: Array> | null + agent_weights?: Record | null +} + +export interface MatchDetailOut { + id: number + league_code: string | null + season: string | null + home_team: string + away_team: string + home_team_zh: string | null + away_team_zh: string | null + match_date: string + match_status: string + home_goals: number | null + away_goals: number | null + match_stage: string | null + home_xg: number | null + away_xg: number | null + recent_predictions: MatchRecentPrediction[] +} + +export interface TeamRecentMatch { + match_date: string | null + home_team: string | null + away_team: string | null + home_goals: number | null + away_goals: number | null +} + +export interface MatchContextOut { + home_recent: TeamRecentMatch[] + away_recent: TeamRecentMatch[] + h2h: TeamRecentMatch[] +} + +// ── 管理区统计 ───────────────────────────────────────────────── + +export interface AdminStats { + predictions: { + total: number + last_24h: number + last_7d: number + } +} diff --git a/frontend/src/index.css b/frontend/src/index.css index a56d78c..9f96725 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -65,11 +65,14 @@ disabled:hover:border-ink-300 disabled:hover:bg-transparent disabled:hover:text-ink-700; } .btn-sm { - @apply px-2.5 py-1 text-xs; + @apply px-2.5 py-1 text-xs min-h-[36px]; } .btn-solid { @apply border-ink-900 bg-ink-900 text-paper-50 hover:border-press hover:bg-press; } + .btn-danger { + @apply border-press bg-transparent text-press hover:bg-press hover:text-paper-50; + } /* ── 表单控件:方正、无圆角 ── */ .field { diff --git a/frontend/src/pages/Matches.tsx b/frontend/src/pages/Matches.tsx index 053bba5..5335943 100644 --- a/frontend/src/pages/Matches.tsx +++ b/frontend/src/pages/Matches.tsx @@ -1,5 +1,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import TeamSideTag from '../components/TeamSideTag' +import { fetchMatchDetail, fetchMatchContext } from '../admin/dal' +import type { MatchDetailOut, MatchContextOut, MatchRecentPrediction, TeamRecentMatch } from '../admin/types' interface Match { id: number @@ -31,10 +33,14 @@ interface Prediction { pred_1x2: string | null subjective_confidence: number | null reasoning: string | null + status: string agent_outputs: AgentReport[] | null agent_weights: Record | null context: string latency_ms: number | null + prompt_tokens: number | null + completion_tokens: number | null + rate_limit_remaining: number | null } interface AgentReport { @@ -182,6 +188,7 @@ function OutcomeLine({ export default function Matches() { const [league, setLeague] = useState('E0') const [status, setStatus] = useState('scheduled') + const [date, setDate] = useState('') // 日期筛选(空=全部),"today"=今日 const [matches, setMatches] = useState([]) const [nextCursor, setNextCursor] = useState(null) const [loadingMore, setLoadingMore] = useState(false) @@ -191,6 +198,10 @@ export default function Matches() { const [predictionFor, setPredictionFor] = useState(null) const [error, setError] = useState(null) const [mode, setMode] = useState<'single' | 'multi'>('multi') + const [expandedId, setExpandedId] = useState(null) + const [detailMap, setDetailMap] = useState>({}) + const [contextMap, setContextMap] = useState>({}) + const [detailLoading, setDetailLoading] = useState(null) // 请求竞态防护:切换联赛/状态很快时,先发的慢请求可能后返回, // 把旧结果覆盖到新筛选上。用递增序号只认最后一次请求的响应。 @@ -211,11 +222,12 @@ export default function Matches() { const load = useCallback(async () => { const seq = ++loadSeq.current setLoading(true) - // 切换筛选时作废进行中的「加载更多」,避免其标志位卡住 setLoadingMore(false) setError(null) try { const params = new URLSearchParams({ league, status, limit: '50' }) + if (date === 'today') params.set('date', todayStr()) + else if (date) params.set('date', date) const res = await fetch(`/api/v1/matches?${params}`) if (seq !== loadSeq.current) return // 已有更新的请求,丢弃本次结果 if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -229,7 +241,7 @@ export default function Matches() { } finally { if (seq === loadSeq.current) setLoading(false) } - }, [league, status]) + }, [league, status, date]) // 加载下一页(游标分页) const loadMore = async () => { @@ -238,6 +250,8 @@ export default function Matches() { setLoadingMore(true) try { const params = new URLSearchParams({ league, status, limit: '50', cursor: nextCursor }) + if (date === 'today') params.set('date', todayStr()) + else if (date) params.set('date', date) const res = await fetch(`/api/v1/matches?${params}`) if (seq !== loadSeq.current) return if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -255,7 +269,26 @@ export default function Matches() { useEffect(() => { load() }, [load]) + /** 今日日期 YYYY-MM-DD(用于「今日」快速筛选) */ + function todayStr(): string { + return new Date().toISOString().slice(0, 10) + } + + /** 按日期分组(YYYY-MM-DD → Match[]),保持时间序 */ + function groupByDate(list: Match[]): Array<[string, Match[]]> { + const map = new Map() + for (const m of list) { + const key = (m.match_date || '').slice(0, 10) + const arr = map.get(key) + if (arr) arr.push(m) + else map.set(key, [m]) + } + return [...map.entries()] + } + const predict = async (m: Match) => { + // 防连点:若该场比赛已在预测中,直接忽略 + if (predictingId === m.id) return const seq = ++predictSeq.current setPredictingId(m.id) setError(null) @@ -285,9 +318,7 @@ export default function Matches() { setError( e instanceof DOMException && e.name === 'AbortError' ? '预测超时(5 分钟),请稍后重试或改用单次模式' - : e instanceof Error - ? e.message - : String(e), + : readablePredictError(e), ) } finally { clearTimeout(timer) @@ -366,6 +397,27 @@ export default function Matches() { /> + + 日期 + + + setDate(e.target.value)} + className="field px-1.5 py-1 text-xs" + aria-label="按日期筛选" + /> + {date && ( + + )} + + + 共 {matches.length} 场
)} - {!loading && matches.map(m => { - const st = STATUS_META[m.match_status] ?? { label: m.match_status, cls: 'text-ink-400' } - const homeName = m.home_team_zh || m.home_team - const awayName = m.away_team_zh || m.away_team - const busy = predictingId === m.id - const active = predictionFor?.id === m.id - const finished = m.match_status === 'finished' + {!loading && groupByDate(matches).map(([dateKey, group]) => ( +
+ {/* 日期分组头 */} +
+ {formatDateHeader(dateKey)} · {group.length} 场 +
+ {group.map(m => { + const st = STATUS_META[m.match_status] ?? { label: m.match_status, cls: 'text-ink-400' } + const homeName = m.home_team_zh || m.home_team + const awayName = m.away_team_zh || m.away_team + const busy = predictingId === m.id + const finished = m.match_status === 'finished' + const expanded = expandedId === m.id + const detail = detailMap[m.id] + const ctx = contextMap[m.id] - return ( -
-
- {/* 日期 + 状态:移动端同行,桌面端日期单独归列 */} -
- {fmtDate(m.match_date)} - {st.label} -
+ // 展开时懒加载详情(只读,不触发 LLM) + async function toggleExpand() { + if (expanded) { setExpandedId(null); return } + setExpandedId(m.id) + if (!detailMap[m.id] || !contextMap[m.id]) { + setDetailLoading(m.id) + try { + const [d, c] = await Promise.all([ + fetchMatchDetail(m.id).catch(() => null), + fetchMatchContext(m.id).catch(() => null), + ]) + if (d) setDetailMap(prev => ({ ...prev, [m.id]: d })) + if (c) setContextMap(prev => ({ ...prev, [m.id]: c })) + } finally { + setDetailLoading(null) + } + } + } - {/* 对阵:移动端主队/比分/客队同一行,桌面端 sm:contents 拆回 grid 列 */} -
- {/* 主队(右对齐) */} -
- - {homeName} + return ( +
+ {/* 行:可点击展开 */} +
{ if (e.key === 'Enter') toggleExpand() }} + aria-expanded={expanded} + > + {/* 小屏:日期+状态行;桌面:日期单独一列 */} +
+ {fmtDate(m.match_date)} + {st.label}
- {/* 比分 / VS */} -
- {m.home_goals !== null && m.away_goals !== null ? ( - - {m.home_goals}:{m.away_goals} - - ) : ( - VS - )} - {m.home_xg !== null && m.away_xg !== null && ( - - xG {m.home_xg.toFixed(1)}-{m.away_xg.toFixed(1)} - - )} + {/* 对阵行:小屏主队(弹性)/比分/客队(弹性)三格;桌面 sm:contents 走 grid */} +
+ + + {homeName} + + + {m.home_goals !== null && m.away_goals !== null ? ( + + {m.home_goals}:{m.away_goals} + + ) : ( + VS + )} + {m.home_xg !== null && m.away_xg != null && ( + + xG {m.home_xg.toFixed(1)}-{m.away_xg.toFixed(1)} + + )} + + + + {awayName} +
- {/* 客队(左对齐) */} -
- - {awayName} -
-
- - {/* 状态列(桌面) */} - {st.label} - - {/* 预测按钮 */} -
+ {/* 预测按钮:小屏独占一行(桌面端 sm:contents 下隐藏) */} {!finished && ( - +
e.stopPropagation()}> + +
+ )} + + {/* 桌面端按钮(小屏隐藏) */} + {st.label} +
e.stopPropagation()}> + {!finished && ( + + )} +
+
{/* 关闭可点击行(clickable row) */} + + {/* 展开详情面板(只读数据 + 预测按钮 + 历史预测 + 专家报告入口) */} + {expanded && ( + )}
-
-
- ) - })} + ) + })} +
+ ))} {!loading && nextCursor && (
@@ -499,6 +601,55 @@ export default function Matches() { ) } +/** 日期分组头显示:今日/明天/周几 · 年月日 */ +function formatDateHeader(dateKey: string): string { + if (!dateKey) return '未开赛' + const d = new Date(dateKey + 'T00:00:00') + if (isNaN(d.getTime())) return dateKey + const today = new Date() + const todayKey = today.toISOString().slice(0, 10) + const tmr = new Date(today) + tmr.setDate(tmr.getDate() + 1) + const tmrKey = tmr.toISOString().slice(0, 10) + const weekday = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()] + if (dateKey === todayKey) return `今日 ${weekday}` + if (dateKey === tmrKey) return `明日 ${weekday}` + return `${d.getMonth() + 1}月${d.getDate()}日 ${weekday}` +} + +/** 预测成本展示:耗时 + token + 限流余量 */ +function PredictionCost({ prediction }: { prediction: Prediction }) { + const latency = prediction.latency_ms != null ? `${(prediction.latency_ms / 1000).toFixed(1)}s` : null + const tokens = prediction.prompt_tokens != null || prediction.completion_tokens != null + ? `${prediction.prompt_tokens ?? '?'}/${prediction.completion_tokens ?? '?'}` + : null + + if (!latency && !tokens && prediction.rate_limit_remaining == null) return null + + return ( +
+
+ {latency && ( + + 耗时 {latency} + + )} + {tokens && ( + + prompt/completion: {tokens} + + )} + {prediction.rate_limit_remaining != null && prediction.rate_limit_remaining <= 3 && ( + + 剩余配额: {prediction.rate_limit_remaining}/10(分钟) + + )} +
+
+ ) +} + + /** 预测过程阶段(按时长模拟;结果到达即跳到完成) */ function PredictProgress({ mode }: { mode: 'single' | 'multi' }) { @@ -573,8 +724,15 @@ function PredictProgress({ mode }: { mode: 'single' | 'multi' }) { )}

- {mode === 'multi' ? '五路专家并行分析后终裁,约需 30-90 秒;关闭窗口即取消' : '单次调用,约需 5-20 秒;关闭窗口即取消'} + {mode === 'multi' + ? '五路专家并行分析后终裁,约需 30-90 秒;多专家调用消耗较多 token,请按需使用。关闭窗口即取消' + : '单次调用,约需 5-20 秒;关闭窗口即取消'}

+ {mode === 'multi' && ( +

+ 提示:每分钟限 10 次预测,耗尽后需等待下一分钟。 +

+ )}
) } @@ -670,12 +828,14 @@ function PredictionPanel({ embedded?: boolean }) { const homeName = match.home_team_zh || match.home_team + const [expertsOpen, setExpertsOpen] = useState(false) const awayName = match.away_team_zh || match.away_team - const okReports = (prediction.agent_outputs ?? []).filter(r => r.status === 'ok') + const degraded = prediction.status === 'degraded' || prediction.status === 'failed' + const reports = prediction.agent_outputs ?? [] + const okReports = reports.filter(r => r.status === 'ok') return (
- {/* 版头(嵌入模式由弹窗报头承担) */} {!embedded && (

@@ -694,71 +854,113 @@ function PredictionPanel({ )}
- {/* ── 预测比分:版面核心,大号宋体 ── */} -
-

- {prediction.pred_home_goals ?? '-'} - : - {prediction.pred_away_goals ?? '-'} -

-

预测比分

- {prediction.alt_pred_home_goals !== null && prediction.alt_pred_away_goals !== null && ( -

- 备选{' '} - - {prediction.alt_pred_home_goals}:{prediction.alt_pred_away_goals} - + {/* ── degraded / failed 态:醒目警示 + 原因,不展示虚假比分 ── */} + {degraded && ( +

+

+ {prediction.status === 'failed' ? '预测失败' : '预测降级(degraded)'}

- )} -
+

+ {prediction.reasoning || '所有专家均无有效数据或调用失败,无法生成可靠比分。'} +

+
+ )} - {/* ── 胜平负 ── */} -
- -
- - {/* ── 元信息一行 ── */} -

- {mode === 'multi' ? `多专家模式 · ${okReports.length}/${prediction.agent_outputs?.length ?? 0} 路有效` : '单次模式'} - {prediction.prompt_version && ` · prompt ${prediction.prompt_version}`} - {prediction.latency_ms !== null && ` · 终裁耗时 ${(prediction.latency_ms / 1000).toFixed(1)} 秒`} -

- - {/* ── 专家意见 ── */} - {prediction.agent_outputs && prediction.agent_outputs.length > 0 && ( -
-
- 五路专家意见 - {prediction.agent_weights && ( - - 终裁权重:{Object.entries(prediction.agent_weights) - .sort((a, b) => b[1] - a[1]) - .map(([k, v]) => `${AGENT_LABELS[k] ?? k} ${Math.round(v * 100)}%`) - .join(' / ')} - + {/* ── 主结论(仅 success 展示) ── */} + {!degraded && ( + <> +
+

+ {prediction.pred_home_goals ?? '-'} + : + {prediction.pred_away_goals ?? '-'} +

+

预测比分

+ {prediction.alt_pred_home_goals != null && prediction.alt_pred_away_goals != null && ( +

+ 备选{' '} + + {prediction.alt_pred_home_goals}:{prediction.alt_pred_away_goals} + +

)}
-
- {prediction.agent_outputs.map((r, i) => ( - - ))} +
+
-
+ )} - {/* ── 终裁意见:引文式,红竖线 ── */} - {prediction.reasoning && ( + {/* ── 成本信息(耗时 + token + 限流余量) ── */} + {!degraded && ( + + )} + + {/* ── 元信息 ── */} +

+ {mode === 'multi' ? `多专家模式 · ${okReports.length}/${reports.length} 路有效` : '单次模式'} + {prediction.prompt_version && ` · prompt ${prediction.prompt_version}`} +

+ + {/* ── 终裁/降级说明意见 ── */} + {prediction.reasoning && degraded && (
-

终裁意见

+

降级原因

-

- {prediction.reasoning} -

+

{prediction.reasoning}

)} + {/* ── 专家意见(多模式):可折叠 + 状态摘要 + 权重条形图 ── */} + {mode === 'multi' && reports.length > 0 && ( +
+ + + {/* 权重条形图(仅 success 且有权重时显示) */} + {!degraded && prediction.agent_weights && Object.keys(prediction.agent_weights).length > 0 && ( +
+ 终裁权重分布 + {Object.entries(prediction.agent_weights) + .sort((a, b) => b[1] - a[1]) + .map(([k, v]) => ( +
+ {AGENT_LABELS[k] ?? k} +
+
+
+ {Math.round(v * 100)}% +
+ ))} +
+ )} + + {expertsOpen && ( +
+ {reports.map((r, i) => ( + + ))} +
+ )} +
+ )} + + {/* ── 终裁意见(success) ── */} + {prediction.reasoning && !degraded && mode === 'multi' && ( +
+

终裁意见

+
+

{prediction.reasoning}

+
+
+ )}

) @@ -862,3 +1064,164 @@ function AgentCard({ report: r, no }: { report: AgentReport; no: string }) { ) } + +/** 比赛详情面板:双方近况/H2H + 历史预测列表(只读) */ +function MatchDetailPanel({ + match, detail, ctx, loading, mode, +}: { + match: Match + detail: MatchDetailOut | undefined + ctx: MatchContextOut | undefined + loading: boolean + mode: 'single' | 'multi' +}) { + const homeName = match.home_team_zh || match.home_team + const awayName = match.away_team_zh || match.away_team + const finished = match.match_status === 'finished' + + return ( +
+ {loading && ( +
加载详情中…
+ )} + + {!loading && !detail && !ctx && ( +

暂无详情数据

+ )} + + {!loading && (detail || ctx) && ( +
+ {/* 比分区(终场/当前比分 + 状态 + 预测按钮) */} +
+
+

+ {match.home_goals ?? '-'}{' '}:{' '}{match.away_goals ?? '-'} +

+

+ {match.match_stage || ''} {match.match_status === 'finished' ? '· 已完赛' : match.match_status === 'scheduled' ? '· 未开赛' : `· ${match.match_status}`} +

+ {match.home_xg != null && match.away_xg != null && ( +

xG {match.home_xg.toFixed(1)}–{match.away_xg.toFixed(1)}

+ )} +
+ {!finished && ( + + 点击行首「预测」按钮发起{mode === 'multi' ? '多专家' : '单次'}分析 + + )} +
+ + {/* 双方近况 + H2H */} + {(ctx?.home_recent?.length || ctx?.away_recent?.length || ctx?.h2h?.length) ? ( +
+ + + +
+ ) : ( + !loading &&

暂无近期对战数据

+ )} + + {/* 历史预测列表 */} +
+

历史预测({detail?.recent_predictions?.length ?? 0})

+ {detail?.recent_predictions?.length ? ( +
+ {detail.recent_predictions.map(p => ( + + ))} +
+ ) : ( +

该场比赛暂无预测记录

+ )} +
+
+ )} +
+ ) +} + +/** 近况/H2H 单区块 */ +function RecentBlock({ title, rows, side }: { title: string; rows?: TeamRecentMatch[]; side: 'home' | 'away' | 'h2h' }) { + return ( +
+
{title}
+ {rows && rows.length > 0 ? ( +
    + {rows.map((r, i) => { + const date = r.match_date ? r.match_date.slice(5, 10) : '—' + const score = (r.home_goals != null && r.away_goals != null) ? `${r.home_goals}-${r.away_goals}` : 'vs' + const label = side === 'h2h' + ? `${r.home_team ?? '?'} ${score} ${r.away_team ?? '?'}` + : `${score}` + return ( +
  • + {date} + {label} +
  • + ) + })} +
+ ) : ( +

暂无

+ )} +
+ ) +} + +/** 历史预测单行(含专家报告入口) */ +function PredictionHistoryRow({ p, mode }: { p: MatchRecentPrediction; mode: 'single' | 'multi' }) { + const badge = p.status === 'degraded' + ? { label: 'degraded', cls: 'text-press' } + : p.settled + ? { label: p.correct_1x2 === undefined ? '已结算' : p.correct_1x2 ? '命中' : '未中', cls: p.correct_1x2 ? 'text-ink-900' : 'text-ink-400' } + : { label: p.status === 'success' ? '成功' : p.status, cls: 'text-ink-600' } + const score = (p.pred_home_goals != null && p.pred_away_goals != null) + ? `${p.pred_home_goals.toFixed(1)}-${p.pred_away_goals.toFixed(1)}` + : '—' + const alt = (p.alt_pred_home_goals != null && p.alt_pred_away_goals != null) + ? `${p.alt_pred_home_goals.toFixed(1)}-${p.alt_pred_away_goals.toFixed(1)}` : null + const hasAgents = mode === 'multi' && p.agent_outputs && p.agent_outputs.length > 0 + + return ( +
+
+ + {score} {p.pred_1x2 ? `(${p.pred_1x2})` : ''} + {alt && 备选 {alt}} + + + {p.subjective_confidence != null && ( + 信心 {Math.round(p.subjective_confidence * 100)}% + )} + {badge.label} + +
+
+ {p.model} · {p.mode} · {p.created_at?.slice(0, 16).replace('T', ' ') ?? '—'} + {hasAgents && {p.agent_outputs!.length} 路专家报告} +
+ {p.reasoning && ( +

{p.reasoning}

+ )} +
+ ) +} + + +function readablePredictError(e: unknown): string { + if (e instanceof Error) { + const m = e.message + if (/429/.test(m)) { + // 429 来自后端限流(每分钟 10 次),非上游 LLM + return '操作过于频繁:每分钟最多 10 次预测。为保护 LLM 额度,请稍后再试。' + } + if (/502/.test(m)) return 'LLM 服务暂时不可用(502),请稍后重试' + if (/402|Payment Required|额度|余额/.test(m)) return 'LLM 额度不足(402),请检查 API Key 余额' + if (/400|已完赛/.test(m)) return '该比赛已完赛,不再支持预测' + if (/409|已结算/.test(m)) return '该预测已结算,不能重新预测' + if (/timeout|超时|timed out/i.test(m)) return '请求超时,请稍后重试或改用单次模式' + return m + } + return String(e) +} diff --git a/pyproject.toml b/pyproject.toml index 89ea63a..3f39cd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,12 +17,11 @@ dependencies = [ ] [project.optional-dependencies] +# 开发/CI 依赖:本地安装用 pip install -e ".[dev]",CI 用 pip-sync requirements-dev.txt dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", - "httpx>=0.27", - "alembic>=1.13", - "cryptography>=42.0", + "pip-tools>=7.0", ] [build-system] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..0accdb8 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,1211 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --extra=dev --generate-hashes --index-url=https://pypi.tuna.tsinghua.edu.cn/simple --no-index --output-file=/tmp/reqdev.txt pyproject.toml +# +--index-url https://pypi.tuna.tsinghua.edu.cn/simple + +alembic==1.20.0 \ + --hash=sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d \ + --hash=sha256:db505480647bc60386c5369402f4a57a506b7539c9e9ef5e270d45cbbe4939bf + # via profeto (pyproject.toml) +annotated-doc==0.0.5 \ + --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101 \ + --hash=sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb + # via fastapi +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via pydantic +anyio==4.15.1 \ + --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \ + --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94 + # via + # httpx + # starlette + # watchfiles +asyncpg==0.31.0 \ + --hash=sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8 \ + --hash=sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be \ + --hash=sha256:0b17c89312c2f4ccea222a3a6571f7df65d4ba2c0e803339bfc7bed46a96d3be \ + --hash=sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2 \ + --hash=sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d \ + --hash=sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a \ + --hash=sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7 \ + --hash=sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218 \ + --hash=sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d \ + --hash=sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602 \ + --hash=sha256:22be6e02381bab3101cd502d9297ac71e2f966c86e20e78caead9934c98a8af6 \ + --hash=sha256:2657204552b75f8288de08ca60faf4a99a65deef3a71d1467454123205a88fab \ + --hash=sha256:2d076d42eb583601179efa246c5d7ae44614b4144bc1c7a683ad1222814ed095 \ + --hash=sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5 \ + --hash=sha256:37a58919cfef2448a920df00d1b2f821762d17194d0dbf355d6dde8d952c04f9 \ + --hash=sha256:37fc6c00a814e18eef51833545d1891cac9aa69140598bb076b4cd29b3e010b9 \ + --hash=sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c \ + --hash=sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec \ + --hash=sha256:3faa62f997db0c9add34504a68ac2c342cfee4d57a0c3062fcf0d86c7f9cb1e8 \ + --hash=sha256:480c4befbdf079c14c9ca43c8c5e1fe8b6296c96f1f927158d4f1e750aacc047 \ + --hash=sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e \ + --hash=sha256:5a4af56edf82a701aece93190cc4e094d2df7d33f6e915c222fb09efbb5afc24 \ + --hash=sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31 \ + --hash=sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186 \ + --hash=sha256:795416369c3d284e1837461909f58418ad22b305f955e625a4b3a2521d80a5f3 \ + --hash=sha256:831712dd3cf117eec68575a9b50da711893fd63ebe277fc155ecae1c6c9f0f61 \ + --hash=sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a \ + --hash=sha256:8ea599d45c361dfbf398cb67da7fd052affa556a401482d3ff1ee99bd68808a1 \ + --hash=sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2 \ + --hash=sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2 \ + --hash=sha256:9ea33213ac044171f4cac23740bed9a3805abae10e7025314cfbd725ec670540 \ + --hash=sha256:a429e842a3a4b4ea240ea52d7fe3f82d5149853249306f7ff166cb9948faa46c \ + --hash=sha256:a8d758dac9d2e723e173d286ef5e574f0b350ec00e9186fce84d0fc5f6a8e6b8 \ + --hash=sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671 \ + --hash=sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad \ + --hash=sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d \ + --hash=sha256:bb223567dea5f47c45d347f2bde5486be8d9f40339f27217adb3fb1c3be51298 \ + --hash=sha256:bc2b685f400ceae428f79f78b58110470d7b4466929a7f78d455964b17ad1008 \ + --hash=sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3 \ + --hash=sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20 \ + --hash=sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2 \ + --hash=sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4 \ + --hash=sha256:c0807be46c32c963ae40d329b3a686356e417f674c976c07fa49f1b30303f109 \ + --hash=sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403 \ + --hash=sha256:c1a9c5b71d2371a2290bc93336cd05ba4ec781683cab292adbddc084f89443c6 \ + --hash=sha256:c1e1ab5bc65373d92dd749d7308c5b26fb2dc0fbe5d3bf68a32b676aa3bcd24a \ + --hash=sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b \ + --hash=sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735 \ + --hash=sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b \ + --hash=sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab \ + --hash=sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e \ + --hash=sha256:e5d5098f63beeae93512ee513d4c0c53dc12e9aa2b7a1af5a81cddf93fe4e4da \ + --hash=sha256:e6974f36eb9a224d8fb428bcf66bd411aa12cf57c2967463178149e73d4de366 \ + --hash=sha256:ebb3cde58321a1f89ce41812be3f2a98dddedc1e76d0838aba1d724f1e4e1a95 \ + --hash=sha256:eee690960e8ab85063ba93af2ce128c0f52fd655fdff9fdb1a28df01329f031d \ + --hash=sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44 \ + --hash=sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696 + # via profeto (pyproject.toml) +certifi==2026.7.22 \ + --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ + --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 + # via + # httpcore + # httpx +cffi==2.1.1 \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \ + --hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \ + --hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \ + --hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \ + --hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \ + --hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \ + --hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \ + --hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \ + --hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \ + --hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \ + --hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \ + --hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \ + --hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \ + --hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \ + --hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \ + --hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \ + --hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \ + --hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \ + --hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \ + --hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \ + --hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \ + --hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \ + --hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \ + --hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \ + --hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \ + --hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \ + --hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \ + --hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \ + --hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \ + --hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \ + --hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \ + --hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \ + --hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \ + --hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \ + --hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \ + --hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \ + --hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \ + --hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \ + --hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \ + --hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \ + --hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \ + --hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \ + --hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \ + --hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \ + --hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \ + --hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \ + --hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \ + --hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \ + --hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \ + --hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \ + --hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via cryptography +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 + # via uvicorn +cryptography==50.0.1 \ + --hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \ + --hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \ + --hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \ + --hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \ + --hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \ + --hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \ + --hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \ + --hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \ + --hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \ + --hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \ + --hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \ + --hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \ + --hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \ + --hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \ + --hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \ + --hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \ + --hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \ + --hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \ + --hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \ + --hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \ + --hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \ + --hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \ + --hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \ + --hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \ + --hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \ + --hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \ + --hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \ + --hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \ + --hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \ + --hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \ + --hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \ + --hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \ + --hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \ + --hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \ + --hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \ + --hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \ + --hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \ + --hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \ + --hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \ + --hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \ + --hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \ + --hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \ + --hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \ + --hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \ + --hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \ + --hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef + # via profeto (pyproject.toml) +fastapi==0.141.1 \ + --hash=sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3 \ + --hash=sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1 + # via profeto (pyproject.toml) +greenlet==3.5.6 \ + --hash=sha256:0616b8f878098c5681fd8f0dc92d887551717402342a70f0abcbfea5f5ad8a44 \ + --hash=sha256:06c0e933290fba8ffe53ead4ae1b8044b0e9754b75cebf381aa2bc3e50d82fac \ + --hash=sha256:128813fc29f2336a21b4d06eedd5e16bcc7ea46f59e9ff1cb30ea70e48195d88 \ + --hash=sha256:188bf333769b7145e2b0b4a7f09615ec550ed44d3a2a8395fb7b36f0e9901e13 \ + --hash=sha256:1c20ea32a73d17b9b60e3371240e17b0068120c98a5ec01a224a7dd8c89733ba \ + --hash=sha256:2ab5f42ac6c238eb71770715e6e909ad9a1a92b6c681ccb64cd5a0f07edb953f \ + --hash=sha256:301102a49120b095e72a7838792b41233975fc1c155daec6d98f81c00c9280e0 \ + --hash=sha256:311018b46472fb26ee85870847fb89eb64cc8aaddb617400789d87076f7cfeec \ + --hash=sha256:3ac3494c381dab876cad7d0b22f3a722f3e0c8deb3a65b9e7f35ad7f58b8fcb3 \ + --hash=sha256:3c6dede9133e1da41d561bc3fb14e92b47e2ce39ae60edefaad145658ea7c5e2 \ + --hash=sha256:3dbb4596a6a4e5d47121a33ff20533a81e60f302d9e67b69909a8bc21a43f0a7 \ + --hash=sha256:3deccbb57a481e3a408fe61cdfd5c13e0678fc0a30fdd09597917ca87b4be877 \ + --hash=sha256:45663c01a4de48b9a64a2ee1509d92d1dfd3afb02b2ccfc9333029d11aef996a \ + --hash=sha256:45bfd2b51e38aaa5f9849f114d9c7c1d75f69187c849b3549cd64c465283abfa \ + --hash=sha256:460e70b033aba8ed47e2ac9b5d0d2157b05a34fbfa30a241400aef4118902cdc \ + --hash=sha256:4fb8e59f68845d56c23c031dcd79c329f345e4a9d2ffac91c3d1ab366bdc457b \ + --hash=sha256:520648db8fb92eef7b3e6013f5a6f901cdf0d6685f639c2f7a245879f865bef7 \ + --hash=sha256:5599b380c1f28efeb724e81569eac80cd92f99a85bd9775456caaf3225d40b11 \ + --hash=sha256:59deccd347735a7774223b05a93773fddbb298aba3cea21be4337fb4752dbe32 \ + --hash=sha256:5a0b2791239c99992a86c1b635b787fe2a877d9eaaa26f8891ce943832b585ae \ + --hash=sha256:5adcbbfe78bdc242c71740a02e0991cc1b2f34d33c8bb15ca45eee8fd1140942 \ + --hash=sha256:5b602b4201b965a8354d74e232364a66ff243dd142e350d035f46169bb36e13d \ + --hash=sha256:5bbda3c70dd35d60671bc33b01916802707a052130d9e50cdb871d34594d35cb \ + --hash=sha256:602024dae6d77e161f4b89491b62ca1d4f19949d79d47b2db057e476d21179d6 \ + --hash=sha256:61a61b4a95a4f97922c3a6f5606d3e360851584bd47e500a5161373c53810e3d \ + --hash=sha256:63aff70fe5aac59c72215f42ec39fcb59ff46774fa966e717f8ecb6ee2273577 \ + --hash=sha256:71890d5247020c25c21a6b65202782bfc281d4e6e244842419d30e3492bb6dcc \ + --hash=sha256:73a29b5ba642e35433166a03a3e02935e7238c4b3467fbd77523b99edea23e5b \ + --hash=sha256:7969bffa322c097bd46ae595ada6a931cefda613f18ba64587e9cff4cb320756 \ + --hash=sha256:7ac4abb3877c43af320392c664774eef6fa2cc063c79a55fc02d844a3cbe7395 \ + --hash=sha256:7f731ebac68ea06d628658295cb2d217b10186329fcf9a3b6a149045059bf92e \ + --hash=sha256:7f924a5a9d5890649566f2f6682e0d8ad8ca23028bacffbbac36dbd7fd680176 \ + --hash=sha256:874cea8bb1ec1ddccbacbd027856f6bf496f6bc18aba97a918c20e067edab236 \ + --hash=sha256:876077e7ebb8c84ed068e2b23d4c62ebb010d60df84b9591af1be2f39010ffb2 \ + --hash=sha256:886bcf1870af74c32bc310fd00a6b803445e17e51b7d5a107c7b35c0f362cc16 \ + --hash=sha256:8b27df301f56e3b3d2298095c8f7d6b68f2521f6b1693e901fa039bdbae34424 \ + --hash=sha256:8b7c73d1cef3d9ae963e9ff03f6222df43efbb9054ffd2f1969c935b7fc84c02 \ + --hash=sha256:8cda13494d86a4f12429641117cb6ac4bbbc9c30a33f711f7d3a2e5fbe4b0b7e \ + --hash=sha256:8cddea1b8339451c2fb3388e138347b6126744f33b611bdb55b7357361cfef46 \ + --hash=sha256:8dba0129b93e7091dfefaf4cf7000172741bff7f47bf6326fcf17f32fbb54d6b \ + --hash=sha256:8e67c43bdfc88d5fee6db0d3e40175b362fc95fb85f0412d233b9b203c53a575 \ + --hash=sha256:9133d68624b1f2e89ec2f554d56aea8a5b0d7168cd9320200ba58d4d794845a4 \ + --hash=sha256:916f92f2a8db10508f739d0b5e00b83defe5d1115a997c54532a6d7cf8c95404 \ + --hash=sha256:9297fb9c39b9a2c039dbcd306c410bd6906b95244dec3bba4318d36c718c164c \ + --hash=sha256:95e7c44d072db623a1aab04ce488cf9533294a77ed9d072cd503a3596f4106ac \ + --hash=sha256:975736b002ed080d124cf81a79cb7e05cb26d6b3f5c7a7b651c0fcce70353aa1 \ + --hash=sha256:97c5a53e8c1754df58e73f047a99e287d4da1bdfe64b0072fb25c87000897951 \ + --hash=sha256:9a09d59bef1db94f384b5bcc2d523694d338f3df6b757aeeaf7baca5d0c0be88 \ + --hash=sha256:a364c1ea75dc51b83a17f52fe0c79cf8bc4ddf740403bebd4581c7666eea017d \ + --hash=sha256:a3b4a01c6da07ef9f80d4fe8933b994bc99747bcea3eab0330a9c34d3c12655b \ + --hash=sha256:a5876d0a60355af98d535c47f6cd6eb0f8a432396dab26845d380b92f8412422 \ + --hash=sha256:a6a4b98a9132e0f45c9fc245a63894cfd8c45fb7a0d6bffc5eab3ec327cf7324 \ + --hash=sha256:a6b4ff33f7e011bbaa148238d131c4fd4f8afbab3c104ddfbdb2b12b74ff7016 \ + --hash=sha256:a93ee7c6e8fd0f8a83525a51bd777be57ee17787e91d805bd8d6faf9dcada18e \ + --hash=sha256:b374e79ffa7511afc11773aef40a4ccea6191fba1c856ea2f9c56738dca69d7a \ + --hash=sha256:b7d501d5eb5d4f67207df364752ad697465b834268744be7581c18d81d35d41d \ + --hash=sha256:c59acfa8eb73a1e0d484392dc002bdf001fd4ce73394e0132df3d1ab6093d7cb \ + --hash=sha256:c75116c9de79949de23006e2d9b35ee82874c594fcf5c0311b439acaa14b8441 \ + --hash=sha256:ca80a49b53ed1d22f7282da7255f7bb2fd1935fd0f623d8613fda38745f18961 \ + --hash=sha256:cad5782f93f7f738b62c6527b6f32a60694d924029f299a8b524758cfa53d815 \ + --hash=sha256:ccadce0130fd813ec86ebfe969a6c58b42acc1d0fe55a47525375b740e07b605 \ + --hash=sha256:d701eab36200c36224833d07dbdb709adb7fd4253429548ddb5e547b8ed40586 \ + --hash=sha256:dad3d233d441a022c1f7155f0fb9d5aff7b97c1ea8c7dfa02cce586b16ab2d0b \ + --hash=sha256:dd0b83bed3405b586a3133629f1d1a5bc7bfd64822a3b7ab342bdc68e6dbc61b \ + --hash=sha256:de3de000d459402cda015068fd135aa50c0bf6f2477a80d4da1e646f123b4e78 \ + --hash=sha256:de9923832f2d8c1a5ecd8d7260465a6ca5a86888a0d129e3bd5cf0406d2fc5bf \ + --hash=sha256:df19e2d0b1620039af5102563fbd96e8938c7f5c3f5828528d641d9fc585525e \ + --hash=sha256:e85880b538e59a59f55117b81f208a6660ad5ac328aad9305f812d9b8bc67a0f \ + --hash=sha256:ee7d9da3bf493909cf811a3f038840cb34fab5ae2956b8a263919f6e289ab188 \ + --hash=sha256:eed88b64a5e5da72d6a71cdc5aaeefaa5ced9b748f8d19f89800b339961dad39 \ + --hash=sha256:f0ba7c2a329d650628f4c8572fd1db29f0a59dd70a3e3e0710dcf18a35cce9d8 \ + --hash=sha256:f8e63209c3e1e828ee6a457529b4a6d8b05d050fe0ae03a7ae49e967c5d312e0 \ + --hash=sha256:f8f0bd690e1a41294ac87905e8121c81a3761ec2583c768f13467428606c8c7a \ + --hash=sha256:f96f0e30b5a95c7631b12bfe214cbc90ec8fe8cfa36920596c10514a65743519 \ + --hash=sha256:f98e8215e172f567ce80eeaed9107fb4d32b6c44f26983d9b8334658136a205a \ + --hash=sha256:f9fe868463ec7e1363733af77e38a5fda3e9b63940337048c945d69e0c80ff24 \ + --hash=sha256:fdacf26402389bdd89857ad3c045a26fe8f3314f9a8b28226f82f88463a65b77 \ + --hash=sha256:fe3170a69fe039b18ad18171e66faa9a75f6fe9d78f968fd9b54e09fbd714d81 \ + --hash=sha256:fea4427d1ffdb3b523d7daa6712038428a4c16c450b9777bdd1221cfee0eab49 + # via sqlalchemy +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # httpcore + # uvicorn +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via httpx +httptools==0.8.0 \ + --hash=sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683 \ + --hash=sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb \ + --hash=sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b \ + --hash=sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527 \ + --hash=sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124 \ + --hash=sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca \ + --hash=sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081 \ + --hash=sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c \ + --hash=sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77 \ + --hash=sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09 \ + --hash=sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f \ + --hash=sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085 \ + --hash=sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376 \ + --hash=sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5 \ + --hash=sha256:5d7fa4ba7292c1139c0526f0b5aad507c6263c948206ea1b1cbca015c8af1b62 \ + --hash=sha256:5eb911c515b96ee44bbd861e42cbefc488681d450545b1d02127f6136e3a86f5 \ + --hash=sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8 \ + --hash=sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681 \ + --hash=sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999 \ + --hash=sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1 \ + --hash=sha256:7b71e7d7031928c650e1006e6c03e911bf967f7c69c011d37d541c3e7bf55005 \ + --hash=sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d \ + --hash=sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d \ + --hash=sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d \ + --hash=sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d \ + --hash=sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba \ + --hash=sha256:9fc1644f415372cec4f8a5be3a64183737398f10dbb1263602a036427fe75247 \ + --hash=sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745 \ + --hash=sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07 \ + --hash=sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b \ + --hash=sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4 \ + --hash=sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2 \ + --hash=sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557 \ + --hash=sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d \ + --hash=sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826 \ + --hash=sha256:c08ffe3e79756e0963cbc8fe410139f38a5884874b6f2e17761bef6563fdcd9b \ + --hash=sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813 \ + --hash=sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0 \ + --hash=sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150 \ + --hash=sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e \ + --hash=sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77 \ + --hash=sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568 \ + --hash=sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6 \ + --hash=sha256:df31ef5494f406ab6cf827b7e64a22841c6e2d654100e6a116ea15b46d02d5e8 \ + --hash=sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b \ + --hash=sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7 \ + --hash=sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168 \ + --hash=sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a \ + --hash=sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0 \ + --hash=sha256:fe2a4c95aeba2209434e7b31172da572846cae8ca0bf1e7013e61b99fbbf5e72 + # via uvicorn +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via profeto (pyproject.toml) +idna==3.20 \ + --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \ + --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c + # via + # anyio + # httpx +iniconfig==2.3.0 \ + --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ + --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + # via pytest +mako==1.4.1 \ + --hash=sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617 \ + --hash=sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27 + # via alembic +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via mako +packaging==26.3 \ + --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c + # via pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +psycopg2-binary==2.9.13 \ + --hash=sha256:0405dd4d97720e7ab177aa02e493f524907c4cb3c445ac173e2627948d3d0528 \ + --hash=sha256:0463c00f946517f3e69192a59e6601e023ff9de45ad0a875eda3d6b1bebeb7ce \ + --hash=sha256:07b7bd9f410650c34c3532162cc329f112368d78a3fc8668cb1ea9df61bc11bf \ + --hash=sha256:086659ab083119f7ee87a779e31b94211cf162b708fc9a6bec771f75c73ac3e6 \ + --hash=sha256:08d3b81a6a91775c937abf97d4c58fc9142e8e35fb91c387d24f81d15c98e6cf \ + --hash=sha256:0a6444ac48e2c04f691c2ddd542b38ba30c89463a2d446b3d74ec7d8fc90c964 \ + --hash=sha256:0ebcf3c4266a695df9d0ef51296155f60c86ac51cf82f0d0dd2e827255a891c5 \ + --hash=sha256:13d955f6054a705a19554364fe9888d0a6e8b0746dc7ebc08a447c7b4fd4145c \ + --hash=sha256:1752b9821f1377404d65ac43af03d59a1eccc57fb2c1eb8305f9a3fe8eb7a8ba \ + --hash=sha256:190c18b97d9ef72f2e88c451b6588af90d6bd7bf54cb94b963280dc86a2c7076 \ + --hash=sha256:1f4c7bdbafdf9dc018efbc29213b73f8308332888ba76a4cf503f560bfd21705 \ + --hash=sha256:202dedd5cadb3e5dfd4d0415ab2fc5d5b44f4208de5308938e3e74ae222b638e \ + --hash=sha256:215777c62ce81c3b487cefdb6a41969944eb982309f91349ff3ca0323d6f17ed \ + --hash=sha256:27e539b4cafd5e03dcd32921db1b12dd72fe549dd06bae6d4d2a5b5838465f24 \ + --hash=sha256:28eb30bf4a52c1117406f45771038faa96f882fdeeeb0ce43b960a1dbc6c1fd2 \ + --hash=sha256:2bf9f97a6df69a5d89d054b8cf5257a0916096c479800715fbfe7974dbcb3a26 \ + --hash=sha256:2ca263643ae37998ae04d18e431df34d0d61f12b47640dab585f14b6dbe00798 \ + --hash=sha256:31db6cba66df5231dfd91d9f69188bec3fe6c8baae384e93a0ce792067ee2d98 \ + --hash=sha256:32cd049095135d2b69e824aea9056745a4aaaa9115a9febbc65584793665d0d0 \ + --hash=sha256:33a6d3c47f9655b481b2cdc1b4bf71c235e054e55663d3066036b6ce5fbe5165 \ + --hash=sha256:376ebf7d8aee4b7386b2bac31fdc27911e7e57cd0a88f1e038b8b149398ac008 \ + --hash=sha256:38397def2d794ffde9db80f63d6820253e61b17483112652a318355f51a56f50 \ + --hash=sha256:3aea95340825f5ff236e7b40f0b5602c2c77a1e95943f71fae34909834043d29 \ + --hash=sha256:3dc3372b3731b3ef23407fe06b94f640ef87a2bda242fa386033d5589c87514a \ + --hash=sha256:3e60b06ec7f9dc3e5f1106d12706514b6d6b92c3dc438fcdf4e43e65cc660d1b \ + --hash=sha256:3f699a5225094a5c61402984e2fc1eca20e940223e76767c88189efb0c313f69 \ + --hash=sha256:41c2eb569ebd0e1b02d30d361a46932923b193fe1b5e641fb4d547c75e218955 \ + --hash=sha256:4c0214c7da18a28d108aa7108c8a3cca8035c7911ec97ef9ec0827569c9a2720 \ + --hash=sha256:4d66bfd44a46eb88cff0287929a4193fb45166b6c1f84bb1b233cc17ece0813c \ + --hash=sha256:4e55357d1943673d491bbabb171c891704fc6a22441fea539e05a5c27a79ea3c \ + --hash=sha256:4ff0f575cbb14f30445858dcfdd751e043486f5290915df78a9818bc74042eff \ + --hash=sha256:5085f7ff7b1e890f279577cedeb8c628957869a340fa34a39f7f406500b3c916 \ + --hash=sha256:541a487a9ccd72b5e38f37f27b0ce78cb7eb3e336e7b5277d45463010c03a7a8 \ + --hash=sha256:562fe2a43b30e781848dce63d9080c15414c777c96df348c4342558338cc7bf3 \ + --hash=sha256:5d89e064bb12b40cad696cf4975e6da86f8c60f14cd06cb6c1bc0a7f5d01761f \ + --hash=sha256:5f04ae99c9fbb94c3197ec88599ed7db921f6adcddfe83687a74c7ead4037c22 \ + --hash=sha256:691da68ae5dd7c3ac77514357d35ece7b1ba8b5f3e6c92735198aa6159c355c8 \ + --hash=sha256:6e696297891b56ff0115f0665de6ad774e1e301e4f60745b8d5024001ae7c2f6 \ + --hash=sha256:6ede8595767e19d30a7e8a84a7d47bfde6176d45d194fed08dbb68d1584a780b \ + --hash=sha256:70d091f5c3a6177fac50c0da20181ce0e0c053f1e43c872d5f75bd6d9429c020 \ + --hash=sha256:7e2405196a8cfe6cd3e54172a54452dcf85c241eaf2e9dde7190d7469f7f5ef7 \ + --hash=sha256:81404c37e0344ebcf10aac127d33d35137e5dbab1daf9f3deee46188fd5879c2 \ + --hash=sha256:81682c227cc1849c4a6adf7b85274229073bb4c9d6ad5697222c695dcea5a8a7 \ + --hash=sha256:8cb734989420c18ca1b71a82da880e11988f5ff3fcdaadd669161de3e98794ac \ + --hash=sha256:930e7e58b33a4f9c39e7532d7a40147925cf3372baed4229cbebe0cf3ba9ce6b \ + --hash=sha256:aa37089795bd9701576edc2eb5849ce77a439eda9dfdfa47857449332cfa5292 \ + --hash=sha256:b6ae51708201f501a171b02419d0c30878a743c369c9054eb1289f0f8d5979e2 \ + --hash=sha256:c00ebe9a2f31151aade0db233dc1446513a95e92c39ce055ee097af0ae86be1c \ + --hash=sha256:c24c98fe1a113db287dfb1958771eafca97b7db812f23b7897c2a12b6b904c22 \ + --hash=sha256:c519e406287085f43aa0d3061936edf1ba51286093532f215315c6ab8ba92c3b \ + --hash=sha256:d19aec88857d2a52f99eefcefdbbb45921fb2f777bee5186a355a23d9cf8a0b9 \ + --hash=sha256:d2fc9342aad969b9a28490a4c3eaba94b35beb2d26e9a39b31d1430378aa71b2 \ + --hash=sha256:d79530b4c1af657d5620a1d21b8e39f2996aa06821d5564d05b22d6b8cd413d0 \ + --hash=sha256:db31cf7f617a51625f1473d8a66fc35dac159af8b28e80bc014ed3ee994a9fbf \ + --hash=sha256:dddfe650e7dda464d676c27fbedb5061f1ad05e1604627f54c770d7f799d36e9 \ + --hash=sha256:dde942b46ce20f6c4464cdf551f3293207f803f4e4354454eb1f5599c3eb1fa1 \ + --hash=sha256:dff5c70ed9789ccb0d97ff4a7da51dc523a255c4ec95df188fa5d44adcae4ea8 \ + --hash=sha256:e324ecf60f952d21dd11413b8bbed0951bbd99579a06fd06f28bfc37737cd373 \ + --hash=sha256:e3861eba31f8ea8663fd876166b032fd89179e42aa63764d6feb281f13f9eb60 \ + --hash=sha256:f04ada42bcd537adbaf8b7f3140237a204e452a88d0c1831cfce69f7d2e59f4e \ + --hash=sha256:f124954a32640dfb5c000d33028f48053930d7ff226bc74cde5fb316f9c6fcb6 \ + --hash=sha256:f28b5f2fa8154d0d97e97a664136f58d1639ca008d45d6e09e69fff24826abee \ + --hash=sha256:f3088eb80f58ed933c62d87128741d31e786edc862e23266d3c286763d646de0 \ + --hash=sha256:f47f23db2d70db39cfb714b64fd5df76595b51b2ec0a669710a78f2dceb0c3f8 \ + --hash=sha256:f4cdfe41149dcc5583a3b7a2f0ad433f75bb3afd1c7a7332e63df89b05e34666 \ + --hash=sha256:f818161d2302b3b3e9c75d5a1d0a5c5679e92e45cfec6432b9d5432dde5ff1f1 \ + --hash=sha256:feb7b1856f6ca805cc0e08739858f6cdfed8ce903390126af30343c62899a389 + # via profeto (pyproject.toml) +pycparser==3.0 \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via cffi +pydantic==2.13.5 \ + --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73 \ + --hash=sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08 + # via + # fastapi + # profeto (pyproject.toml) + # pydantic-settings +pydantic-core==2.46.5 \ + --hash=sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942 \ + --hash=sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821 \ + --hash=sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5 \ + --hash=sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c \ + --hash=sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184 \ + --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2 \ + --hash=sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc \ + --hash=sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5 \ + --hash=sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0 \ + --hash=sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931 \ + --hash=sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e \ + --hash=sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25 \ + --hash=sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d \ + --hash=sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6 \ + --hash=sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47 \ + --hash=sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038 \ + --hash=sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8 \ + --hash=sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b \ + --hash=sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a \ + --hash=sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e \ + --hash=sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074 \ + --hash=sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0 \ + --hash=sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433 \ + --hash=sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f \ + --hash=sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034 \ + --hash=sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21 \ + --hash=sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736 \ + --hash=sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0 \ + --hash=sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7 \ + --hash=sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c \ + --hash=sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4 \ + --hash=sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c \ + --hash=sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c \ + --hash=sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069 \ + --hash=sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb \ + --hash=sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061 \ + --hash=sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29 \ + --hash=sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3 \ + --hash=sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa \ + --hash=sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e \ + --hash=sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38 \ + --hash=sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f \ + --hash=sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b \ + --hash=sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8 \ + --hash=sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e \ + --hash=sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c \ + --hash=sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be \ + --hash=sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6 \ + --hash=sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793 \ + --hash=sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1 \ + --hash=sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b \ + --hash=sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64 \ + --hash=sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f \ + --hash=sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761 \ + --hash=sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d \ + --hash=sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168 \ + --hash=sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869 \ + --hash=sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111 \ + --hash=sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5 \ + --hash=sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b \ + --hash=sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7 \ + --hash=sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129 \ + --hash=sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e \ + --hash=sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655 \ + --hash=sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c \ + --hash=sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec \ + --hash=sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689 \ + --hash=sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f \ + --hash=sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf \ + --hash=sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea \ + --hash=sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9 \ + --hash=sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464 \ + --hash=sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519 \ + --hash=sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b \ + --hash=sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f \ + --hash=sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee \ + --hash=sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a \ + --hash=sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e \ + --hash=sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6 \ + --hash=sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2 \ + --hash=sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f \ + --hash=sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a \ + --hash=sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f \ + --hash=sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a \ + --hash=sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47 \ + --hash=sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda \ + --hash=sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0 \ + --hash=sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4 \ + --hash=sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d \ + --hash=sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3 \ + --hash=sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2 \ + --hash=sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355 \ + --hash=sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461 \ + --hash=sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed \ + --hash=sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f \ + --hash=sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5 \ + --hash=sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2 \ + --hash=sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829 \ + --hash=sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0 \ + --hash=sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266 \ + --hash=sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575 \ + --hash=sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290 \ + --hash=sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f \ + --hash=sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62 \ + --hash=sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f \ + --hash=sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a \ + --hash=sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7 \ + --hash=sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed \ + --hash=sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f \ + --hash=sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1 \ + --hash=sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615 \ + --hash=sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8 \ + --hash=sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3 \ + --hash=sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a \ + --hash=sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff \ + --hash=sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084 \ + --hash=sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0 \ + --hash=sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3 \ + --hash=sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13 \ + --hash=sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9 + # via pydantic +pydantic-settings==2.15.0 \ + --hash=sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42 \ + --hash=sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117 + # via profeto (pyproject.toml) +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c + # via pytest +pytest==9.1.1 \ + --hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \ + --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + # via + # profeto (pyproject.toml) + # pytest-asyncio +pytest-asyncio==1.4.0 \ + --hash=sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1 \ + --hash=sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42 + # via profeto (pyproject.toml) +python-dotenv==1.2.3 \ + --hash=sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9 \ + --hash=sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35 + # via + # pydantic-settings + # uvicorn +pyyaml==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via uvicorn +sqlalchemy==2.0.54 \ + --hash=sha256:03cbf8d9a67da618bd65500a5eb3ddac89caf4c61e99b2f03fa4a1952a0725a9 \ + --hash=sha256:0e7a76d5dce712ce50435d0f97181eb955ec27d138c004176f01282e063bac52 \ + --hash=sha256:1019abef05a4b5eafc8eae6fb483167fa28a4dbe5f518d577b744f31a5276a37 \ + --hash=sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77 \ + --hash=sha256:1d887fbd5d248e250807bd801e697fc73e3b44866ce5f093dbc90512e75bde25 \ + --hash=sha256:24ae093dec196ba37fc2beb0316de53e7871d3d246a50faecbbb53034e41ded2 \ + --hash=sha256:264460333ed0b177cbb1956355d0ee4e0cab83fb415c934ce12a25db2e7be39c \ + --hash=sha256:279bde5bfedb0f3e0f1bdbcffa2daa39c6c54d90f9408ef3b1802001597199f0 \ + --hash=sha256:2f61a70b3b82e2ec7ad6a4f2301422b9ca93ff06917983e41317bcae878bddf6 \ + --hash=sha256:31d5458672a6f72db2c087f4a5098b3c8503ea0254186ff29205d63afa9401a4 \ + --hash=sha256:32de6deded25e8b9b11d07428d496ff24dfbc882b8e990c177266948cb5f3d9e \ + --hash=sha256:330d35f9ce815d35cb1daab038d4d7ec0e907f4d7ed0fc8bcb2411d1f23d0b50 \ + --hash=sha256:34e10af7d274a5c4b7cd0fced5e7361008c5e07d97dd48a93852d5b2f1142a1c \ + --hash=sha256:3de32cc6721eb42c3aad35bcfb244bb7a18f66c00f3582aae6281d6287a339b5 \ + --hash=sha256:415239eb2ddbbc508ba4cac97affb91c0f210548fd1731edda6e529b0bb93015 \ + --hash=sha256:48611087a75d26d798003645c688c7d3cfc26b89dbe4a2c568d6b378d330deae \ + --hash=sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd \ + --hash=sha256:4e8a4afcc7d714cc3c8a57facdff4c3529f5f93d71e54b7da1e03e022c9089c9 \ + --hash=sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139 \ + --hash=sha256:5800ddea045c2c860ef1d359a07a3066c7c0c426f45e3abc3874e116cb3c6937 \ + --hash=sha256:63cae7210fea9899e0bf35c1f1ae55d3ddd9c6d47cae8b6b43d945afa79dd65b \ + --hash=sha256:68d994e9b0d0423a02a20039631fa6fcbb7fa829a992f7605025774940305d19 \ + --hash=sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f \ + --hash=sha256:6b6d4e601c4f6d85e99bb3416107cc9418c5603ca73d4ee0f5f8d79c2a1ed9e8 \ + --hash=sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e \ + --hash=sha256:7108f410f596c5ac22fe43ba467e864d27c4e1477ae89e90c6c87120b2c1be23 \ + --hash=sha256:744fb219a390561a57dbbd59cd69a22b5b5b2facfde794c1f79236dd847fa67a \ + --hash=sha256:762cfe4d340c56368256d936a98b620a9a5650e49c1c84eba51d6edd17ffefb2 \ + --hash=sha256:7b973e4facc2f80e42f5a27b841feb7e202661881a6320580abbe597a28a007f \ + --hash=sha256:7d03084f3352dd92048cb19c71d90f116d076c9c7937e0ebc7752c4685de6d38 \ + --hash=sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2 \ + --hash=sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44 \ + --hash=sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615 \ + --hash=sha256:92622fbbda1b1fe1632f3402a6e516a93c0e41d9158839c6b3dfb12117f26b72 \ + --hash=sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912 \ + --hash=sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0 \ + --hash=sha256:b374e3bc91e246a942592a98ba6a23be76fff21358b00546ac8c0ebc0fd0e00b \ + --hash=sha256:b67749f7da3985a529cefbb1474783cb91ef44371cb9713630bade3de908760d \ + --hash=sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970 \ + --hash=sha256:b6c419c83a87fd901f0b1b5338ffcb82471c3ac32a86bb8883688c18f8eb85d3 \ + --hash=sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51 \ + --hash=sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b \ + --hash=sha256:c1a3455a88f66e4851792bedb098ed942912253d31caed1dbc58afbfa9e875cd \ + --hash=sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a \ + --hash=sha256:cad78d04254967bdbcccbed5e631d88fe4868530946ab0929aa45e9032849518 \ + --hash=sha256:cf89e92bf0d4204a6afcc17af27b9271ed9c7e34e17d6f80c085d431ea4a1747 \ + --hash=sha256:d31a2bc06a854ee52dd86b455be4df7c750b28817e2d1b884e31fff126c4fd7b \ + --hash=sha256:d566099d60cded87d175d4171dc899b9613d2e3b663573364565ca1b27ccd241 \ + --hash=sha256:d65f8ca742ef1e1e14bc417ef59dc2ddf207a7b66b30cfdc6152447314e030cf \ + --hash=sha256:d6adf80277372a89910a0f3ccfe960b846d279dc55b366dd5c5ec07f41c84758 \ + --hash=sha256:deeab253fe01a770f634c7007c73702df2324c868a79ae756507a9a1a76294fe \ + --hash=sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb \ + --hash=sha256:e1f455db400289f77ba2f7b62fffafe8875153812d0e3777aa4ff2b34a0fc1f7 \ + --hash=sha256:f3ea33bcf0aa599c1511fe5c9fb126f45aa450419084c4823f786155fe4c79f1 \ + --hash=sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835 \ + --hash=sha256:f5c09090b1a7c4d389d1431f820931e8df318f82caafc53f9a72c872fef467c5 \ + --hash=sha256:f8cc6532f930c27974e9239e5ce5abebe7600ba9807cea4fcf42f1b6cab18fe7 \ + --hash=sha256:ffba7eb2d67c7505e82a0902aa854d8824b74c28a183820d6a8bd3cfd0f812c2 + # via + # alembic + # profeto (pyproject.toml) +starlette==1.6.0 \ + --hash=sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c \ + --hash=sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b + # via fastapi +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # alembic + # anyio + # fastapi + # pydantic + # pydantic-core + # pytest-asyncio + # sqlalchemy + # starlette + # typing-inspection +typing-inspection==0.4.4 \ + --hash=sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47 \ + --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147 + # via + # fastapi + # pydantic + # pydantic-settings +uvicorn[standard]==0.53.0 \ + --hash=sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e \ + --hash=sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e + # via profeto (pyproject.toml) +uvloop==0.22.1 \ + --hash=sha256:017bd46f9e7b78e81606329d07141d3da446f8798c6baeec124260e22c262772 \ + --hash=sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e \ + --hash=sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743 \ + --hash=sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54 \ + --hash=sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec \ + --hash=sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659 \ + --hash=sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8 \ + --hash=sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad \ + --hash=sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7 \ + --hash=sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35 \ + --hash=sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289 \ + --hash=sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142 \ + --hash=sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77 \ + --hash=sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733 \ + --hash=sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd \ + --hash=sha256:4a968a72422a097b09042d5fa2c5c590251ad484acf910a651b4b620acd7f193 \ + --hash=sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74 \ + --hash=sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0 \ + --hash=sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6 \ + --hash=sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473 \ + --hash=sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21 \ + --hash=sha256:55502bc2c653ed2e9692e8c55cb95b397d33f9f2911e929dc97c4d6b26d04242 \ + --hash=sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705 \ + --hash=sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702 \ + --hash=sha256:57df59d8b48feb0e613d9b1f5e57b7532e97cbaf0d61f7aa9aa32221e84bc4b6 \ + --hash=sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f \ + --hash=sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e \ + --hash=sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d \ + --hash=sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370 \ + --hash=sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4 \ + --hash=sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792 \ + --hash=sha256:80eee091fe128e425177fbd82f8635769e2f32ec9daf6468286ec57ec0313efa \ + --hash=sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079 \ + --hash=sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2 \ + --hash=sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86 \ + --hash=sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6 \ + --hash=sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4 \ + --hash=sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3 \ + --hash=sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21 \ + --hash=sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c \ + --hash=sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e \ + --hash=sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25 \ + --hash=sha256:c3e5c6727a57cb6558592a95019e504f605d1c54eb86463ee9f7a2dbd411c820 \ + --hash=sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9 \ + --hash=sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88 \ + --hash=sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2 \ + --hash=sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c \ + --hash=sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c \ + --hash=sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42 + # via uvicorn +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551 \ + --hash=sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d \ + --hash=sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7 \ + --hash=sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db \ + --hash=sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69 \ + --hash=sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242 \ + --hash=sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925 \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5 \ + --hash=sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5 \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba \ + --hash=sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30 \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33 \ + --hash=sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2 \ + --hash=sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b \ + --hash=sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579 \ + --hash=sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7 \ + --hash=sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666 \ + --hash=sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5 \ + --hash=sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201 \ + --hash=sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103 \ + --hash=sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1 \ + --hash=sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a \ + --hash=sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1 \ + --hash=sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377 \ + --hash=sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8 \ + --hash=sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add \ + --hash=sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e \ + --hash=sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0 \ + --hash=sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28 \ + --hash=sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07 \ + --hash=sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb \ + --hash=sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4 \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e \ + --hash=sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4 \ + --hash=sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9 \ + --hash=sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7 \ + --hash=sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488 \ + --hash=sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b \ + --hash=sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2 \ + --hash=sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22 \ + --hash=sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6 \ + --hash=sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e \ + --hash=sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310 \ + --hash=sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8 \ + --hash=sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 + # via uvicorn +websockets==17.1 \ + --hash=sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d \ + --hash=sha256:00679b7468b4c2b12b0757118174e8eabac56bb2f579a928a104d9554a56e098 \ + --hash=sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3 \ + --hash=sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a \ + --hash=sha256:020e271205f8ab3406d7a59cd00de6dec722315924411c421bd00642f18bad86 \ + --hash=sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f \ + --hash=sha256:054c28db2dcec0e857e3b705d8c28012613e555b38c765d6a4f75340a4fc06a0 \ + --hash=sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d \ + --hash=sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f \ + --hash=sha256:0c84bdef916556cbe1d5a43b423398be4dd3cba6522b463e53d848578b920695 \ + --hash=sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6 \ + --hash=sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e \ + --hash=sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391 \ + --hash=sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927 \ + --hash=sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb \ + --hash=sha256:10ecb38ffc05e1841b619d99c725307a223ef9ad58e7b1ed33311d472dc43918 \ + --hash=sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d \ + --hash=sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394 \ + --hash=sha256:17aa424ab61620aad21b36b2240efc87b500cc496e7d0e999a5c2ae99395e886 \ + --hash=sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2 \ + --hash=sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13 \ + --hash=sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c \ + --hash=sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c \ + --hash=sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e \ + --hash=sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3 \ + --hash=sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906 \ + --hash=sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e \ + --hash=sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5 \ + --hash=sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608 \ + --hash=sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68 \ + --hash=sha256:2d72879819f5145a342d0030c418702496c65a4b913ef81f5ae944dd91dd50f6 \ + --hash=sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256 \ + --hash=sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335 \ + --hash=sha256:34879e19bb0a3c44f9317679435aea5327fac993933a704cbf353bf1234b10c7 \ + --hash=sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e \ + --hash=sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c \ + --hash=sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad \ + --hash=sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb \ + --hash=sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51 \ + --hash=sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780 \ + --hash=sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f \ + --hash=sha256:469355ab1af100b9380f1afb09985019f4a4b94fa1dd0e9396db4361626d7ab8 \ + --hash=sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82 \ + --hash=sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80 \ + --hash=sha256:47a62d6045c6eaa0d8f97bc2fb68b8cf90077a0cbfd4e83d6f2d2145611ee134 \ + --hash=sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc \ + --hash=sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d \ + --hash=sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b \ + --hash=sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642 \ + --hash=sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98 \ + --hash=sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441 \ + --hash=sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11 \ + --hash=sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c \ + --hash=sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da \ + --hash=sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641 \ + --hash=sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a \ + --hash=sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec \ + --hash=sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090 \ + --hash=sha256:65be6bda2b537fefa4b3a5ccd6ab386533ce39dd8fe62433ec90901fdc81752d \ + --hash=sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555 \ + --hash=sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8 \ + --hash=sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7 \ + --hash=sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a \ + --hash=sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5 \ + --hash=sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9 \ + --hash=sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b \ + --hash=sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774 \ + --hash=sha256:764cf7bfa149365f32b7a0fd9fed32debdac29dd06295d5635cde1745b446cd8 \ + --hash=sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3 \ + --hash=sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a \ + --hash=sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2 \ + --hash=sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb \ + --hash=sha256:8196d217eeca52b9235ee1f8a684a09885a5f953d5a31e80ef915bf2c5c94f9d \ + --hash=sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc \ + --hash=sha256:84f25e099fdfe3b09f953d84698f729a1f7d1e99101b2787d7a28ed77b323750 \ + --hash=sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab \ + --hash=sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24 \ + --hash=sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74 \ + --hash=sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f \ + --hash=sha256:88b882764ef65147a7a5ae13168dedbe225a04e2ff4858fe543f2c402f093e9c \ + --hash=sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2 \ + --hash=sha256:8d1b108bd8f5f6a8b90801f6db3b3858d5deca889acfdb8ac497bbb24e4b0edf \ + --hash=sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51 \ + --hash=sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db \ + --hash=sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6 \ + --hash=sha256:98a5b2589a56a4b4f098b0a958099a4356ab904a7844f1da3841efca469af7e9 \ + --hash=sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8 \ + --hash=sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729 \ + --hash=sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce \ + --hash=sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6 \ + --hash=sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c \ + --hash=sha256:a62d8c424383c9dc769ff3672018df822603117e32686e567d452ed035b6fb2e \ + --hash=sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab \ + --hash=sha256:a9fe648abd1d9b89aebfa30407bfdd08a0271ec5dc7d44a4c6ccd1ce22cf562a \ + --hash=sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a \ + --hash=sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110 \ + --hash=sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257 \ + --hash=sha256:b1bc819c6db90e8f91a38250a1ab4c058261871aa52d2fe36382eddedf146dee \ + --hash=sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe \ + --hash=sha256:b3b451fd2723ad3191a209afe6f3f4bc86c83e9a85bdc255353b91803ee6aa66 \ + --hash=sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566 \ + --hash=sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8 \ + --hash=sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a \ + --hash=sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51 \ + --hash=sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78 \ + --hash=sha256:c89406fa3dcd4aa8662c6406cc5c0de1790e9614d2c3aaf03ca53a8a8ccf3405 \ + --hash=sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60 \ + --hash=sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435 \ + --hash=sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d \ + --hash=sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6 \ + --hash=sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03 \ + --hash=sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5 \ + --hash=sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603 \ + --hash=sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08 \ + --hash=sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d \ + --hash=sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42 \ + --hash=sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0 \ + --hash=sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800 \ + --hash=sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19 \ + --hash=sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922 \ + --hash=sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151 \ + --hash=sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1 \ + --hash=sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b \ + --hash=sha256:dcb8d5f7edef7a399d322cf28d4c4e6f98dab64d301c8f50581a1080e5198142 \ + --hash=sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067 \ + --hash=sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81 \ + --hash=sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c \ + --hash=sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6 \ + --hash=sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e \ + --hash=sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2 \ + --hash=sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2 \ + --hash=sha256:edadce7a22052056fd4384543019856b34850363c9d387929f677ae01d79709c \ + --hash=sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837 \ + --hash=sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23 \ + --hash=sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623 \ + --hash=sha256:f47aafd92aa28b941180e6da8a42b0f711851b14b81a5b6bb28dbbb1fa35152c \ + --hash=sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4 \ + --hash=sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc \ + --hash=sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd \ + --hash=sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a \ + --hash=sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4 \ + --hash=sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a \ + --hash=sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3 \ + --hash=sha256:f8e822efd54137d8cc8310eb64635ab827a4a6c72ff08691f38aa624776d8ecb \ + --hash=sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce \ + --hash=sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81 \ + --hash=sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80 \ + --hash=sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7 + # via uvicorn diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..62a3e1d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,1184 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes --index-url=https://pypi.tuna.tsinghua.edu.cn/simple --no-index --output-file=/tmp/req.txt pyproject.toml +# +--index-url https://pypi.tuna.tsinghua.edu.cn/simple + +alembic==1.20.0 \ + --hash=sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d \ + --hash=sha256:db505480647bc60386c5369402f4a57a506b7539c9e9ef5e270d45cbbe4939bf + # via profeto (pyproject.toml) +annotated-doc==0.0.5 \ + --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101 \ + --hash=sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb + # via fastapi +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via pydantic +anyio==4.15.1 \ + --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \ + --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94 + # via + # httpx + # starlette + # watchfiles +asyncpg==0.31.0 \ + --hash=sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8 \ + --hash=sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be \ + --hash=sha256:0b17c89312c2f4ccea222a3a6571f7df65d4ba2c0e803339bfc7bed46a96d3be \ + --hash=sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2 \ + --hash=sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d \ + --hash=sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a \ + --hash=sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7 \ + --hash=sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218 \ + --hash=sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d \ + --hash=sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602 \ + --hash=sha256:22be6e02381bab3101cd502d9297ac71e2f966c86e20e78caead9934c98a8af6 \ + --hash=sha256:2657204552b75f8288de08ca60faf4a99a65deef3a71d1467454123205a88fab \ + --hash=sha256:2d076d42eb583601179efa246c5d7ae44614b4144bc1c7a683ad1222814ed095 \ + --hash=sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5 \ + --hash=sha256:37a58919cfef2448a920df00d1b2f821762d17194d0dbf355d6dde8d952c04f9 \ + --hash=sha256:37fc6c00a814e18eef51833545d1891cac9aa69140598bb076b4cd29b3e010b9 \ + --hash=sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c \ + --hash=sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec \ + --hash=sha256:3faa62f997db0c9add34504a68ac2c342cfee4d57a0c3062fcf0d86c7f9cb1e8 \ + --hash=sha256:480c4befbdf079c14c9ca43c8c5e1fe8b6296c96f1f927158d4f1e750aacc047 \ + --hash=sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e \ + --hash=sha256:5a4af56edf82a701aece93190cc4e094d2df7d33f6e915c222fb09efbb5afc24 \ + --hash=sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31 \ + --hash=sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186 \ + --hash=sha256:795416369c3d284e1837461909f58418ad22b305f955e625a4b3a2521d80a5f3 \ + --hash=sha256:831712dd3cf117eec68575a9b50da711893fd63ebe277fc155ecae1c6c9f0f61 \ + --hash=sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a \ + --hash=sha256:8ea599d45c361dfbf398cb67da7fd052affa556a401482d3ff1ee99bd68808a1 \ + --hash=sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2 \ + --hash=sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2 \ + --hash=sha256:9ea33213ac044171f4cac23740bed9a3805abae10e7025314cfbd725ec670540 \ + --hash=sha256:a429e842a3a4b4ea240ea52d7fe3f82d5149853249306f7ff166cb9948faa46c \ + --hash=sha256:a8d758dac9d2e723e173d286ef5e574f0b350ec00e9186fce84d0fc5f6a8e6b8 \ + --hash=sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671 \ + --hash=sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad \ + --hash=sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d \ + --hash=sha256:bb223567dea5f47c45d347f2bde5486be8d9f40339f27217adb3fb1c3be51298 \ + --hash=sha256:bc2b685f400ceae428f79f78b58110470d7b4466929a7f78d455964b17ad1008 \ + --hash=sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3 \ + --hash=sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20 \ + --hash=sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2 \ + --hash=sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4 \ + --hash=sha256:c0807be46c32c963ae40d329b3a686356e417f674c976c07fa49f1b30303f109 \ + --hash=sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403 \ + --hash=sha256:c1a9c5b71d2371a2290bc93336cd05ba4ec781683cab292adbddc084f89443c6 \ + --hash=sha256:c1e1ab5bc65373d92dd749d7308c5b26fb2dc0fbe5d3bf68a32b676aa3bcd24a \ + --hash=sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b \ + --hash=sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735 \ + --hash=sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b \ + --hash=sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab \ + --hash=sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e \ + --hash=sha256:e5d5098f63beeae93512ee513d4c0c53dc12e9aa2b7a1af5a81cddf93fe4e4da \ + --hash=sha256:e6974f36eb9a224d8fb428bcf66bd411aa12cf57c2967463178149e73d4de366 \ + --hash=sha256:ebb3cde58321a1f89ce41812be3f2a98dddedc1e76d0838aba1d724f1e4e1a95 \ + --hash=sha256:eee690960e8ab85063ba93af2ce128c0f52fd655fdff9fdb1a28df01329f031d \ + --hash=sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44 \ + --hash=sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696 + # via profeto (pyproject.toml) +certifi==2026.7.22 \ + --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ + --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 + # via + # httpcore + # httpx +cffi==2.1.1 \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \ + --hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \ + --hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \ + --hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \ + --hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \ + --hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \ + --hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \ + --hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \ + --hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \ + --hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \ + --hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \ + --hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \ + --hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \ + --hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \ + --hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \ + --hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \ + --hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \ + --hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \ + --hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \ + --hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \ + --hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \ + --hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \ + --hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \ + --hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \ + --hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \ + --hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \ + --hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \ + --hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \ + --hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \ + --hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \ + --hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \ + --hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \ + --hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \ + --hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \ + --hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \ + --hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \ + --hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \ + --hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \ + --hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \ + --hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \ + --hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \ + --hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \ + --hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \ + --hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \ + --hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \ + --hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \ + --hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \ + --hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \ + --hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \ + --hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \ + --hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via cryptography +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 + # via uvicorn +cryptography==50.0.1 \ + --hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \ + --hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \ + --hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \ + --hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \ + --hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \ + --hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \ + --hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \ + --hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \ + --hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \ + --hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \ + --hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \ + --hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \ + --hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \ + --hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \ + --hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \ + --hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \ + --hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \ + --hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \ + --hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \ + --hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \ + --hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \ + --hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \ + --hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \ + --hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \ + --hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \ + --hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \ + --hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \ + --hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \ + --hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \ + --hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \ + --hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \ + --hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \ + --hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \ + --hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \ + --hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \ + --hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \ + --hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \ + --hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \ + --hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \ + --hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \ + --hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \ + --hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \ + --hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \ + --hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \ + --hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \ + --hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef + # via profeto (pyproject.toml) +fastapi==0.141.1 \ + --hash=sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3 \ + --hash=sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1 + # via profeto (pyproject.toml) +greenlet==3.5.6 \ + --hash=sha256:0616b8f878098c5681fd8f0dc92d887551717402342a70f0abcbfea5f5ad8a44 \ + --hash=sha256:06c0e933290fba8ffe53ead4ae1b8044b0e9754b75cebf381aa2bc3e50d82fac \ + --hash=sha256:128813fc29f2336a21b4d06eedd5e16bcc7ea46f59e9ff1cb30ea70e48195d88 \ + --hash=sha256:188bf333769b7145e2b0b4a7f09615ec550ed44d3a2a8395fb7b36f0e9901e13 \ + --hash=sha256:1c20ea32a73d17b9b60e3371240e17b0068120c98a5ec01a224a7dd8c89733ba \ + --hash=sha256:2ab5f42ac6c238eb71770715e6e909ad9a1a92b6c681ccb64cd5a0f07edb953f \ + --hash=sha256:301102a49120b095e72a7838792b41233975fc1c155daec6d98f81c00c9280e0 \ + --hash=sha256:311018b46472fb26ee85870847fb89eb64cc8aaddb617400789d87076f7cfeec \ + --hash=sha256:3ac3494c381dab876cad7d0b22f3a722f3e0c8deb3a65b9e7f35ad7f58b8fcb3 \ + --hash=sha256:3c6dede9133e1da41d561bc3fb14e92b47e2ce39ae60edefaad145658ea7c5e2 \ + --hash=sha256:3dbb4596a6a4e5d47121a33ff20533a81e60f302d9e67b69909a8bc21a43f0a7 \ + --hash=sha256:3deccbb57a481e3a408fe61cdfd5c13e0678fc0a30fdd09597917ca87b4be877 \ + --hash=sha256:45663c01a4de48b9a64a2ee1509d92d1dfd3afb02b2ccfc9333029d11aef996a \ + --hash=sha256:45bfd2b51e38aaa5f9849f114d9c7c1d75f69187c849b3549cd64c465283abfa \ + --hash=sha256:460e70b033aba8ed47e2ac9b5d0d2157b05a34fbfa30a241400aef4118902cdc \ + --hash=sha256:4fb8e59f68845d56c23c031dcd79c329f345e4a9d2ffac91c3d1ab366bdc457b \ + --hash=sha256:520648db8fb92eef7b3e6013f5a6f901cdf0d6685f639c2f7a245879f865bef7 \ + --hash=sha256:5599b380c1f28efeb724e81569eac80cd92f99a85bd9775456caaf3225d40b11 \ + --hash=sha256:59deccd347735a7774223b05a93773fddbb298aba3cea21be4337fb4752dbe32 \ + --hash=sha256:5a0b2791239c99992a86c1b635b787fe2a877d9eaaa26f8891ce943832b585ae \ + --hash=sha256:5adcbbfe78bdc242c71740a02e0991cc1b2f34d33c8bb15ca45eee8fd1140942 \ + --hash=sha256:5b602b4201b965a8354d74e232364a66ff243dd142e350d035f46169bb36e13d \ + --hash=sha256:5bbda3c70dd35d60671bc33b01916802707a052130d9e50cdb871d34594d35cb \ + --hash=sha256:602024dae6d77e161f4b89491b62ca1d4f19949d79d47b2db057e476d21179d6 \ + --hash=sha256:61a61b4a95a4f97922c3a6f5606d3e360851584bd47e500a5161373c53810e3d \ + --hash=sha256:63aff70fe5aac59c72215f42ec39fcb59ff46774fa966e717f8ecb6ee2273577 \ + --hash=sha256:71890d5247020c25c21a6b65202782bfc281d4e6e244842419d30e3492bb6dcc \ + --hash=sha256:73a29b5ba642e35433166a03a3e02935e7238c4b3467fbd77523b99edea23e5b \ + --hash=sha256:7969bffa322c097bd46ae595ada6a931cefda613f18ba64587e9cff4cb320756 \ + --hash=sha256:7ac4abb3877c43af320392c664774eef6fa2cc063c79a55fc02d844a3cbe7395 \ + --hash=sha256:7f731ebac68ea06d628658295cb2d217b10186329fcf9a3b6a149045059bf92e \ + --hash=sha256:7f924a5a9d5890649566f2f6682e0d8ad8ca23028bacffbbac36dbd7fd680176 \ + --hash=sha256:874cea8bb1ec1ddccbacbd027856f6bf496f6bc18aba97a918c20e067edab236 \ + --hash=sha256:876077e7ebb8c84ed068e2b23d4c62ebb010d60df84b9591af1be2f39010ffb2 \ + --hash=sha256:886bcf1870af74c32bc310fd00a6b803445e17e51b7d5a107c7b35c0f362cc16 \ + --hash=sha256:8b27df301f56e3b3d2298095c8f7d6b68f2521f6b1693e901fa039bdbae34424 \ + --hash=sha256:8b7c73d1cef3d9ae963e9ff03f6222df43efbb9054ffd2f1969c935b7fc84c02 \ + --hash=sha256:8cda13494d86a4f12429641117cb6ac4bbbc9c30a33f711f7d3a2e5fbe4b0b7e \ + --hash=sha256:8cddea1b8339451c2fb3388e138347b6126744f33b611bdb55b7357361cfef46 \ + --hash=sha256:8dba0129b93e7091dfefaf4cf7000172741bff7f47bf6326fcf17f32fbb54d6b \ + --hash=sha256:8e67c43bdfc88d5fee6db0d3e40175b362fc95fb85f0412d233b9b203c53a575 \ + --hash=sha256:9133d68624b1f2e89ec2f554d56aea8a5b0d7168cd9320200ba58d4d794845a4 \ + --hash=sha256:916f92f2a8db10508f739d0b5e00b83defe5d1115a997c54532a6d7cf8c95404 \ + --hash=sha256:9297fb9c39b9a2c039dbcd306c410bd6906b95244dec3bba4318d36c718c164c \ + --hash=sha256:95e7c44d072db623a1aab04ce488cf9533294a77ed9d072cd503a3596f4106ac \ + --hash=sha256:975736b002ed080d124cf81a79cb7e05cb26d6b3f5c7a7b651c0fcce70353aa1 \ + --hash=sha256:97c5a53e8c1754df58e73f047a99e287d4da1bdfe64b0072fb25c87000897951 \ + --hash=sha256:9a09d59bef1db94f384b5bcc2d523694d338f3df6b757aeeaf7baca5d0c0be88 \ + --hash=sha256:a364c1ea75dc51b83a17f52fe0c79cf8bc4ddf740403bebd4581c7666eea017d \ + --hash=sha256:a3b4a01c6da07ef9f80d4fe8933b994bc99747bcea3eab0330a9c34d3c12655b \ + --hash=sha256:a5876d0a60355af98d535c47f6cd6eb0f8a432396dab26845d380b92f8412422 \ + --hash=sha256:a6a4b98a9132e0f45c9fc245a63894cfd8c45fb7a0d6bffc5eab3ec327cf7324 \ + --hash=sha256:a6b4ff33f7e011bbaa148238d131c4fd4f8afbab3c104ddfbdb2b12b74ff7016 \ + --hash=sha256:a93ee7c6e8fd0f8a83525a51bd777be57ee17787e91d805bd8d6faf9dcada18e \ + --hash=sha256:b374e79ffa7511afc11773aef40a4ccea6191fba1c856ea2f9c56738dca69d7a \ + --hash=sha256:b7d501d5eb5d4f67207df364752ad697465b834268744be7581c18d81d35d41d \ + --hash=sha256:c59acfa8eb73a1e0d484392dc002bdf001fd4ce73394e0132df3d1ab6093d7cb \ + --hash=sha256:c75116c9de79949de23006e2d9b35ee82874c594fcf5c0311b439acaa14b8441 \ + --hash=sha256:ca80a49b53ed1d22f7282da7255f7bb2fd1935fd0f623d8613fda38745f18961 \ + --hash=sha256:cad5782f93f7f738b62c6527b6f32a60694d924029f299a8b524758cfa53d815 \ + --hash=sha256:ccadce0130fd813ec86ebfe969a6c58b42acc1d0fe55a47525375b740e07b605 \ + --hash=sha256:d701eab36200c36224833d07dbdb709adb7fd4253429548ddb5e547b8ed40586 \ + --hash=sha256:dad3d233d441a022c1f7155f0fb9d5aff7b97c1ea8c7dfa02cce586b16ab2d0b \ + --hash=sha256:dd0b83bed3405b586a3133629f1d1a5bc7bfd64822a3b7ab342bdc68e6dbc61b \ + --hash=sha256:de3de000d459402cda015068fd135aa50c0bf6f2477a80d4da1e646f123b4e78 \ + --hash=sha256:de9923832f2d8c1a5ecd8d7260465a6ca5a86888a0d129e3bd5cf0406d2fc5bf \ + --hash=sha256:df19e2d0b1620039af5102563fbd96e8938c7f5c3f5828528d641d9fc585525e \ + --hash=sha256:e85880b538e59a59f55117b81f208a6660ad5ac328aad9305f812d9b8bc67a0f \ + --hash=sha256:ee7d9da3bf493909cf811a3f038840cb34fab5ae2956b8a263919f6e289ab188 \ + --hash=sha256:eed88b64a5e5da72d6a71cdc5aaeefaa5ced9b748f8d19f89800b339961dad39 \ + --hash=sha256:f0ba7c2a329d650628f4c8572fd1db29f0a59dd70a3e3e0710dcf18a35cce9d8 \ + --hash=sha256:f8e63209c3e1e828ee6a457529b4a6d8b05d050fe0ae03a7ae49e967c5d312e0 \ + --hash=sha256:f8f0bd690e1a41294ac87905e8121c81a3761ec2583c768f13467428606c8c7a \ + --hash=sha256:f96f0e30b5a95c7631b12bfe214cbc90ec8fe8cfa36920596c10514a65743519 \ + --hash=sha256:f98e8215e172f567ce80eeaed9107fb4d32b6c44f26983d9b8334658136a205a \ + --hash=sha256:f9fe868463ec7e1363733af77e38a5fda3e9b63940337048c945d69e0c80ff24 \ + --hash=sha256:fdacf26402389bdd89857ad3c045a26fe8f3314f9a8b28226f82f88463a65b77 \ + --hash=sha256:fe3170a69fe039b18ad18171e66faa9a75f6fe9d78f968fd9b54e09fbd714d81 \ + --hash=sha256:fea4427d1ffdb3b523d7daa6712038428a4c16c450b9777bdd1221cfee0eab49 + # via sqlalchemy +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # httpcore + # uvicorn +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via httpx +httptools==0.8.0 \ + --hash=sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683 \ + --hash=sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb \ + --hash=sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b \ + --hash=sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527 \ + --hash=sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124 \ + --hash=sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca \ + --hash=sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081 \ + --hash=sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c \ + --hash=sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77 \ + --hash=sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09 \ + --hash=sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f \ + --hash=sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085 \ + --hash=sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376 \ + --hash=sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5 \ + --hash=sha256:5d7fa4ba7292c1139c0526f0b5aad507c6263c948206ea1b1cbca015c8af1b62 \ + --hash=sha256:5eb911c515b96ee44bbd861e42cbefc488681d450545b1d02127f6136e3a86f5 \ + --hash=sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8 \ + --hash=sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681 \ + --hash=sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999 \ + --hash=sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1 \ + --hash=sha256:7b71e7d7031928c650e1006e6c03e911bf967f7c69c011d37d541c3e7bf55005 \ + --hash=sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d \ + --hash=sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d \ + --hash=sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d \ + --hash=sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d \ + --hash=sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba \ + --hash=sha256:9fc1644f415372cec4f8a5be3a64183737398f10dbb1263602a036427fe75247 \ + --hash=sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745 \ + --hash=sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07 \ + --hash=sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b \ + --hash=sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4 \ + --hash=sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2 \ + --hash=sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557 \ + --hash=sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d \ + --hash=sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826 \ + --hash=sha256:c08ffe3e79756e0963cbc8fe410139f38a5884874b6f2e17761bef6563fdcd9b \ + --hash=sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813 \ + --hash=sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0 \ + --hash=sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150 \ + --hash=sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e \ + --hash=sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77 \ + --hash=sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568 \ + --hash=sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6 \ + --hash=sha256:df31ef5494f406ab6cf827b7e64a22841c6e2d654100e6a116ea15b46d02d5e8 \ + --hash=sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b \ + --hash=sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7 \ + --hash=sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168 \ + --hash=sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a \ + --hash=sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0 \ + --hash=sha256:fe2a4c95aeba2209434e7b31172da572846cae8ca0bf1e7013e61b99fbbf5e72 + # via uvicorn +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via profeto (pyproject.toml) +idna==3.20 \ + --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \ + --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c + # via + # anyio + # httpx +mako==1.4.1 \ + --hash=sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617 \ + --hash=sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27 + # via alembic +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via mako +psycopg2-binary==2.9.13 \ + --hash=sha256:0405dd4d97720e7ab177aa02e493f524907c4cb3c445ac173e2627948d3d0528 \ + --hash=sha256:0463c00f946517f3e69192a59e6601e023ff9de45ad0a875eda3d6b1bebeb7ce \ + --hash=sha256:07b7bd9f410650c34c3532162cc329f112368d78a3fc8668cb1ea9df61bc11bf \ + --hash=sha256:086659ab083119f7ee87a779e31b94211cf162b708fc9a6bec771f75c73ac3e6 \ + --hash=sha256:08d3b81a6a91775c937abf97d4c58fc9142e8e35fb91c387d24f81d15c98e6cf \ + --hash=sha256:0a6444ac48e2c04f691c2ddd542b38ba30c89463a2d446b3d74ec7d8fc90c964 \ + --hash=sha256:0ebcf3c4266a695df9d0ef51296155f60c86ac51cf82f0d0dd2e827255a891c5 \ + --hash=sha256:13d955f6054a705a19554364fe9888d0a6e8b0746dc7ebc08a447c7b4fd4145c \ + --hash=sha256:1752b9821f1377404d65ac43af03d59a1eccc57fb2c1eb8305f9a3fe8eb7a8ba \ + --hash=sha256:190c18b97d9ef72f2e88c451b6588af90d6bd7bf54cb94b963280dc86a2c7076 \ + --hash=sha256:1f4c7bdbafdf9dc018efbc29213b73f8308332888ba76a4cf503f560bfd21705 \ + --hash=sha256:202dedd5cadb3e5dfd4d0415ab2fc5d5b44f4208de5308938e3e74ae222b638e \ + --hash=sha256:215777c62ce81c3b487cefdb6a41969944eb982309f91349ff3ca0323d6f17ed \ + --hash=sha256:27e539b4cafd5e03dcd32921db1b12dd72fe549dd06bae6d4d2a5b5838465f24 \ + --hash=sha256:28eb30bf4a52c1117406f45771038faa96f882fdeeeb0ce43b960a1dbc6c1fd2 \ + --hash=sha256:2bf9f97a6df69a5d89d054b8cf5257a0916096c479800715fbfe7974dbcb3a26 \ + --hash=sha256:2ca263643ae37998ae04d18e431df34d0d61f12b47640dab585f14b6dbe00798 \ + --hash=sha256:31db6cba66df5231dfd91d9f69188bec3fe6c8baae384e93a0ce792067ee2d98 \ + --hash=sha256:32cd049095135d2b69e824aea9056745a4aaaa9115a9febbc65584793665d0d0 \ + --hash=sha256:33a6d3c47f9655b481b2cdc1b4bf71c235e054e55663d3066036b6ce5fbe5165 \ + --hash=sha256:376ebf7d8aee4b7386b2bac31fdc27911e7e57cd0a88f1e038b8b149398ac008 \ + --hash=sha256:38397def2d794ffde9db80f63d6820253e61b17483112652a318355f51a56f50 \ + --hash=sha256:3aea95340825f5ff236e7b40f0b5602c2c77a1e95943f71fae34909834043d29 \ + --hash=sha256:3dc3372b3731b3ef23407fe06b94f640ef87a2bda242fa386033d5589c87514a \ + --hash=sha256:3e60b06ec7f9dc3e5f1106d12706514b6d6b92c3dc438fcdf4e43e65cc660d1b \ + --hash=sha256:3f699a5225094a5c61402984e2fc1eca20e940223e76767c88189efb0c313f69 \ + --hash=sha256:41c2eb569ebd0e1b02d30d361a46932923b193fe1b5e641fb4d547c75e218955 \ + --hash=sha256:4c0214c7da18a28d108aa7108c8a3cca8035c7911ec97ef9ec0827569c9a2720 \ + --hash=sha256:4d66bfd44a46eb88cff0287929a4193fb45166b6c1f84bb1b233cc17ece0813c \ + --hash=sha256:4e55357d1943673d491bbabb171c891704fc6a22441fea539e05a5c27a79ea3c \ + --hash=sha256:4ff0f575cbb14f30445858dcfdd751e043486f5290915df78a9818bc74042eff \ + --hash=sha256:5085f7ff7b1e890f279577cedeb8c628957869a340fa34a39f7f406500b3c916 \ + --hash=sha256:541a487a9ccd72b5e38f37f27b0ce78cb7eb3e336e7b5277d45463010c03a7a8 \ + --hash=sha256:562fe2a43b30e781848dce63d9080c15414c777c96df348c4342558338cc7bf3 \ + --hash=sha256:5d89e064bb12b40cad696cf4975e6da86f8c60f14cd06cb6c1bc0a7f5d01761f \ + --hash=sha256:5f04ae99c9fbb94c3197ec88599ed7db921f6adcddfe83687a74c7ead4037c22 \ + --hash=sha256:691da68ae5dd7c3ac77514357d35ece7b1ba8b5f3e6c92735198aa6159c355c8 \ + --hash=sha256:6e696297891b56ff0115f0665de6ad774e1e301e4f60745b8d5024001ae7c2f6 \ + --hash=sha256:6ede8595767e19d30a7e8a84a7d47bfde6176d45d194fed08dbb68d1584a780b \ + --hash=sha256:70d091f5c3a6177fac50c0da20181ce0e0c053f1e43c872d5f75bd6d9429c020 \ + --hash=sha256:7e2405196a8cfe6cd3e54172a54452dcf85c241eaf2e9dde7190d7469f7f5ef7 \ + --hash=sha256:81404c37e0344ebcf10aac127d33d35137e5dbab1daf9f3deee46188fd5879c2 \ + --hash=sha256:81682c227cc1849c4a6adf7b85274229073bb4c9d6ad5697222c695dcea5a8a7 \ + --hash=sha256:8cb734989420c18ca1b71a82da880e11988f5ff3fcdaadd669161de3e98794ac \ + --hash=sha256:930e7e58b33a4f9c39e7532d7a40147925cf3372baed4229cbebe0cf3ba9ce6b \ + --hash=sha256:aa37089795bd9701576edc2eb5849ce77a439eda9dfdfa47857449332cfa5292 \ + --hash=sha256:b6ae51708201f501a171b02419d0c30878a743c369c9054eb1289f0f8d5979e2 \ + --hash=sha256:c00ebe9a2f31151aade0db233dc1446513a95e92c39ce055ee097af0ae86be1c \ + --hash=sha256:c24c98fe1a113db287dfb1958771eafca97b7db812f23b7897c2a12b6b904c22 \ + --hash=sha256:c519e406287085f43aa0d3061936edf1ba51286093532f215315c6ab8ba92c3b \ + --hash=sha256:d19aec88857d2a52f99eefcefdbbb45921fb2f777bee5186a355a23d9cf8a0b9 \ + --hash=sha256:d2fc9342aad969b9a28490a4c3eaba94b35beb2d26e9a39b31d1430378aa71b2 \ + --hash=sha256:d79530b4c1af657d5620a1d21b8e39f2996aa06821d5564d05b22d6b8cd413d0 \ + --hash=sha256:db31cf7f617a51625f1473d8a66fc35dac159af8b28e80bc014ed3ee994a9fbf \ + --hash=sha256:dddfe650e7dda464d676c27fbedb5061f1ad05e1604627f54c770d7f799d36e9 \ + --hash=sha256:dde942b46ce20f6c4464cdf551f3293207f803f4e4354454eb1f5599c3eb1fa1 \ + --hash=sha256:dff5c70ed9789ccb0d97ff4a7da51dc523a255c4ec95df188fa5d44adcae4ea8 \ + --hash=sha256:e324ecf60f952d21dd11413b8bbed0951bbd99579a06fd06f28bfc37737cd373 \ + --hash=sha256:e3861eba31f8ea8663fd876166b032fd89179e42aa63764d6feb281f13f9eb60 \ + --hash=sha256:f04ada42bcd537adbaf8b7f3140237a204e452a88d0c1831cfce69f7d2e59f4e \ + --hash=sha256:f124954a32640dfb5c000d33028f48053930d7ff226bc74cde5fb316f9c6fcb6 \ + --hash=sha256:f28b5f2fa8154d0d97e97a664136f58d1639ca008d45d6e09e69fff24826abee \ + --hash=sha256:f3088eb80f58ed933c62d87128741d31e786edc862e23266d3c286763d646de0 \ + --hash=sha256:f47f23db2d70db39cfb714b64fd5df76595b51b2ec0a669710a78f2dceb0c3f8 \ + --hash=sha256:f4cdfe41149dcc5583a3b7a2f0ad433f75bb3afd1c7a7332e63df89b05e34666 \ + --hash=sha256:f818161d2302b3b3e9c75d5a1d0a5c5679e92e45cfec6432b9d5432dde5ff1f1 \ + --hash=sha256:feb7b1856f6ca805cc0e08739858f6cdfed8ce903390126af30343c62899a389 + # via profeto (pyproject.toml) +pycparser==3.0 \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via cffi +pydantic==2.13.5 \ + --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73 \ + --hash=sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08 + # via + # fastapi + # profeto (pyproject.toml) + # pydantic-settings +pydantic-core==2.46.5 \ + --hash=sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942 \ + --hash=sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821 \ + --hash=sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5 \ + --hash=sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c \ + --hash=sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184 \ + --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2 \ + --hash=sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc \ + --hash=sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5 \ + --hash=sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0 \ + --hash=sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931 \ + --hash=sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e \ + --hash=sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25 \ + --hash=sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d \ + --hash=sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6 \ + --hash=sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47 \ + --hash=sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038 \ + --hash=sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8 \ + --hash=sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b \ + --hash=sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a \ + --hash=sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e \ + --hash=sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074 \ + --hash=sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0 \ + --hash=sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433 \ + --hash=sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f \ + --hash=sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034 \ + --hash=sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21 \ + --hash=sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736 \ + --hash=sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0 \ + --hash=sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7 \ + --hash=sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c \ + --hash=sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4 \ + --hash=sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c \ + --hash=sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c \ + --hash=sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069 \ + --hash=sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb \ + --hash=sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061 \ + --hash=sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29 \ + --hash=sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3 \ + --hash=sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa \ + --hash=sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e \ + --hash=sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38 \ + --hash=sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f \ + --hash=sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b \ + --hash=sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8 \ + --hash=sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e \ + --hash=sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c \ + --hash=sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be \ + --hash=sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6 \ + --hash=sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793 \ + --hash=sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1 \ + --hash=sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b \ + --hash=sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64 \ + --hash=sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f \ + --hash=sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761 \ + --hash=sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d \ + --hash=sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168 \ + --hash=sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869 \ + --hash=sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111 \ + --hash=sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5 \ + --hash=sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b \ + --hash=sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7 \ + --hash=sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129 \ + --hash=sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e \ + --hash=sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655 \ + --hash=sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c \ + --hash=sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec \ + --hash=sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689 \ + --hash=sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f \ + --hash=sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf \ + --hash=sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea \ + --hash=sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9 \ + --hash=sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464 \ + --hash=sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519 \ + --hash=sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b \ + --hash=sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f \ + --hash=sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee \ + --hash=sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a \ + --hash=sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e \ + --hash=sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6 \ + --hash=sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2 \ + --hash=sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f \ + --hash=sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a \ + --hash=sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f \ + --hash=sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a \ + --hash=sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47 \ + --hash=sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda \ + --hash=sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0 \ + --hash=sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4 \ + --hash=sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d \ + --hash=sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3 \ + --hash=sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2 \ + --hash=sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355 \ + --hash=sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461 \ + --hash=sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed \ + --hash=sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f \ + --hash=sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5 \ + --hash=sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2 \ + --hash=sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829 \ + --hash=sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0 \ + --hash=sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266 \ + --hash=sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575 \ + --hash=sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290 \ + --hash=sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f \ + --hash=sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62 \ + --hash=sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f \ + --hash=sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a \ + --hash=sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7 \ + --hash=sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed \ + --hash=sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f \ + --hash=sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1 \ + --hash=sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615 \ + --hash=sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8 \ + --hash=sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3 \ + --hash=sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a \ + --hash=sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff \ + --hash=sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084 \ + --hash=sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0 \ + --hash=sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3 \ + --hash=sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13 \ + --hash=sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9 + # via pydantic +pydantic-settings==2.15.0 \ + --hash=sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42 \ + --hash=sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117 + # via profeto (pyproject.toml) +python-dotenv==1.2.3 \ + --hash=sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9 \ + --hash=sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35 + # via + # pydantic-settings + # uvicorn +pyyaml==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via uvicorn +sqlalchemy==2.0.54 \ + --hash=sha256:03cbf8d9a67da618bd65500a5eb3ddac89caf4c61e99b2f03fa4a1952a0725a9 \ + --hash=sha256:0e7a76d5dce712ce50435d0f97181eb955ec27d138c004176f01282e063bac52 \ + --hash=sha256:1019abef05a4b5eafc8eae6fb483167fa28a4dbe5f518d577b744f31a5276a37 \ + --hash=sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77 \ + --hash=sha256:1d887fbd5d248e250807bd801e697fc73e3b44866ce5f093dbc90512e75bde25 \ + --hash=sha256:24ae093dec196ba37fc2beb0316de53e7871d3d246a50faecbbb53034e41ded2 \ + --hash=sha256:264460333ed0b177cbb1956355d0ee4e0cab83fb415c934ce12a25db2e7be39c \ + --hash=sha256:279bde5bfedb0f3e0f1bdbcffa2daa39c6c54d90f9408ef3b1802001597199f0 \ + --hash=sha256:2f61a70b3b82e2ec7ad6a4f2301422b9ca93ff06917983e41317bcae878bddf6 \ + --hash=sha256:31d5458672a6f72db2c087f4a5098b3c8503ea0254186ff29205d63afa9401a4 \ + --hash=sha256:32de6deded25e8b9b11d07428d496ff24dfbc882b8e990c177266948cb5f3d9e \ + --hash=sha256:330d35f9ce815d35cb1daab038d4d7ec0e907f4d7ed0fc8bcb2411d1f23d0b50 \ + --hash=sha256:34e10af7d274a5c4b7cd0fced5e7361008c5e07d97dd48a93852d5b2f1142a1c \ + --hash=sha256:3de32cc6721eb42c3aad35bcfb244bb7a18f66c00f3582aae6281d6287a339b5 \ + --hash=sha256:415239eb2ddbbc508ba4cac97affb91c0f210548fd1731edda6e529b0bb93015 \ + --hash=sha256:48611087a75d26d798003645c688c7d3cfc26b89dbe4a2c568d6b378d330deae \ + --hash=sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd \ + --hash=sha256:4e8a4afcc7d714cc3c8a57facdff4c3529f5f93d71e54b7da1e03e022c9089c9 \ + --hash=sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139 \ + --hash=sha256:5800ddea045c2c860ef1d359a07a3066c7c0c426f45e3abc3874e116cb3c6937 \ + --hash=sha256:63cae7210fea9899e0bf35c1f1ae55d3ddd9c6d47cae8b6b43d945afa79dd65b \ + --hash=sha256:68d994e9b0d0423a02a20039631fa6fcbb7fa829a992f7605025774940305d19 \ + --hash=sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f \ + --hash=sha256:6b6d4e601c4f6d85e99bb3416107cc9418c5603ca73d4ee0f5f8d79c2a1ed9e8 \ + --hash=sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e \ + --hash=sha256:7108f410f596c5ac22fe43ba467e864d27c4e1477ae89e90c6c87120b2c1be23 \ + --hash=sha256:744fb219a390561a57dbbd59cd69a22b5b5b2facfde794c1f79236dd847fa67a \ + --hash=sha256:762cfe4d340c56368256d936a98b620a9a5650e49c1c84eba51d6edd17ffefb2 \ + --hash=sha256:7b973e4facc2f80e42f5a27b841feb7e202661881a6320580abbe597a28a007f \ + --hash=sha256:7d03084f3352dd92048cb19c71d90f116d076c9c7937e0ebc7752c4685de6d38 \ + --hash=sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2 \ + --hash=sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44 \ + --hash=sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615 \ + --hash=sha256:92622fbbda1b1fe1632f3402a6e516a93c0e41d9158839c6b3dfb12117f26b72 \ + --hash=sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912 \ + --hash=sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0 \ + --hash=sha256:b374e3bc91e246a942592a98ba6a23be76fff21358b00546ac8c0ebc0fd0e00b \ + --hash=sha256:b67749f7da3985a529cefbb1474783cb91ef44371cb9713630bade3de908760d \ + --hash=sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970 \ + --hash=sha256:b6c419c83a87fd901f0b1b5338ffcb82471c3ac32a86bb8883688c18f8eb85d3 \ + --hash=sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51 \ + --hash=sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b \ + --hash=sha256:c1a3455a88f66e4851792bedb098ed942912253d31caed1dbc58afbfa9e875cd \ + --hash=sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a \ + --hash=sha256:cad78d04254967bdbcccbed5e631d88fe4868530946ab0929aa45e9032849518 \ + --hash=sha256:cf89e92bf0d4204a6afcc17af27b9271ed9c7e34e17d6f80c085d431ea4a1747 \ + --hash=sha256:d31a2bc06a854ee52dd86b455be4df7c750b28817e2d1b884e31fff126c4fd7b \ + --hash=sha256:d566099d60cded87d175d4171dc899b9613d2e3b663573364565ca1b27ccd241 \ + --hash=sha256:d65f8ca742ef1e1e14bc417ef59dc2ddf207a7b66b30cfdc6152447314e030cf \ + --hash=sha256:d6adf80277372a89910a0f3ccfe960b846d279dc55b366dd5c5ec07f41c84758 \ + --hash=sha256:deeab253fe01a770f634c7007c73702df2324c868a79ae756507a9a1a76294fe \ + --hash=sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb \ + --hash=sha256:e1f455db400289f77ba2f7b62fffafe8875153812d0e3777aa4ff2b34a0fc1f7 \ + --hash=sha256:f3ea33bcf0aa599c1511fe5c9fb126f45aa450419084c4823f786155fe4c79f1 \ + --hash=sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835 \ + --hash=sha256:f5c09090b1a7c4d389d1431f820931e8df318f82caafc53f9a72c872fef467c5 \ + --hash=sha256:f8cc6532f930c27974e9239e5ce5abebe7600ba9807cea4fcf42f1b6cab18fe7 \ + --hash=sha256:ffba7eb2d67c7505e82a0902aa854d8824b74c28a183820d6a8bd3cfd0f812c2 + # via + # alembic + # profeto (pyproject.toml) +starlette==1.6.0 \ + --hash=sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c \ + --hash=sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b + # via fastapi +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # alembic + # anyio + # fastapi + # pydantic + # pydantic-core + # sqlalchemy + # starlette + # typing-inspection +typing-inspection==0.4.4 \ + --hash=sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47 \ + --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147 + # via + # fastapi + # pydantic + # pydantic-settings +uvicorn[standard]==0.53.0 \ + --hash=sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e \ + --hash=sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e + # via profeto (pyproject.toml) +uvloop==0.22.1 \ + --hash=sha256:017bd46f9e7b78e81606329d07141d3da446f8798c6baeec124260e22c262772 \ + --hash=sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e \ + --hash=sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743 \ + --hash=sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54 \ + --hash=sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec \ + --hash=sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659 \ + --hash=sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8 \ + --hash=sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad \ + --hash=sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7 \ + --hash=sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35 \ + --hash=sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289 \ + --hash=sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142 \ + --hash=sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77 \ + --hash=sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733 \ + --hash=sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd \ + --hash=sha256:4a968a72422a097b09042d5fa2c5c590251ad484acf910a651b4b620acd7f193 \ + --hash=sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74 \ + --hash=sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0 \ + --hash=sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6 \ + --hash=sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473 \ + --hash=sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21 \ + --hash=sha256:55502bc2c653ed2e9692e8c55cb95b397d33f9f2911e929dc97c4d6b26d04242 \ + --hash=sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705 \ + --hash=sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702 \ + --hash=sha256:57df59d8b48feb0e613d9b1f5e57b7532e97cbaf0d61f7aa9aa32221e84bc4b6 \ + --hash=sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f \ + --hash=sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e \ + --hash=sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d \ + --hash=sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370 \ + --hash=sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4 \ + --hash=sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792 \ + --hash=sha256:80eee091fe128e425177fbd82f8635769e2f32ec9daf6468286ec57ec0313efa \ + --hash=sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079 \ + --hash=sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2 \ + --hash=sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86 \ + --hash=sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6 \ + --hash=sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4 \ + --hash=sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3 \ + --hash=sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21 \ + --hash=sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c \ + --hash=sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e \ + --hash=sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25 \ + --hash=sha256:c3e5c6727a57cb6558592a95019e504f605d1c54eb86463ee9f7a2dbd411c820 \ + --hash=sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9 \ + --hash=sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88 \ + --hash=sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2 \ + --hash=sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c \ + --hash=sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c \ + --hash=sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42 + # via uvicorn +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551 \ + --hash=sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d \ + --hash=sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7 \ + --hash=sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db \ + --hash=sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69 \ + --hash=sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242 \ + --hash=sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925 \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5 \ + --hash=sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5 \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba \ + --hash=sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30 \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33 \ + --hash=sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2 \ + --hash=sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b \ + --hash=sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579 \ + --hash=sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7 \ + --hash=sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666 \ + --hash=sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5 \ + --hash=sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201 \ + --hash=sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103 \ + --hash=sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1 \ + --hash=sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a \ + --hash=sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1 \ + --hash=sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377 \ + --hash=sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8 \ + --hash=sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add \ + --hash=sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e \ + --hash=sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0 \ + --hash=sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28 \ + --hash=sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07 \ + --hash=sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb \ + --hash=sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4 \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e \ + --hash=sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4 \ + --hash=sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9 \ + --hash=sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7 \ + --hash=sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488 \ + --hash=sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b \ + --hash=sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2 \ + --hash=sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22 \ + --hash=sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6 \ + --hash=sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e \ + --hash=sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310 \ + --hash=sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8 \ + --hash=sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 + # via uvicorn +websockets==17.1 \ + --hash=sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d \ + --hash=sha256:00679b7468b4c2b12b0757118174e8eabac56bb2f579a928a104d9554a56e098 \ + --hash=sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3 \ + --hash=sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a \ + --hash=sha256:020e271205f8ab3406d7a59cd00de6dec722315924411c421bd00642f18bad86 \ + --hash=sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f \ + --hash=sha256:054c28db2dcec0e857e3b705d8c28012613e555b38c765d6a4f75340a4fc06a0 \ + --hash=sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d \ + --hash=sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f \ + --hash=sha256:0c84bdef916556cbe1d5a43b423398be4dd3cba6522b463e53d848578b920695 \ + --hash=sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6 \ + --hash=sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e \ + --hash=sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391 \ + --hash=sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927 \ + --hash=sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb \ + --hash=sha256:10ecb38ffc05e1841b619d99c725307a223ef9ad58e7b1ed33311d472dc43918 \ + --hash=sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d \ + --hash=sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394 \ + --hash=sha256:17aa424ab61620aad21b36b2240efc87b500cc496e7d0e999a5c2ae99395e886 \ + --hash=sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2 \ + --hash=sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13 \ + --hash=sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c \ + --hash=sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c \ + --hash=sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e \ + --hash=sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3 \ + --hash=sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906 \ + --hash=sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e \ + --hash=sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5 \ + --hash=sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608 \ + --hash=sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68 \ + --hash=sha256:2d72879819f5145a342d0030c418702496c65a4b913ef81f5ae944dd91dd50f6 \ + --hash=sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256 \ + --hash=sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335 \ + --hash=sha256:34879e19bb0a3c44f9317679435aea5327fac993933a704cbf353bf1234b10c7 \ + --hash=sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e \ + --hash=sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c \ + --hash=sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad \ + --hash=sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb \ + --hash=sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51 \ + --hash=sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780 \ + --hash=sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f \ + --hash=sha256:469355ab1af100b9380f1afb09985019f4a4b94fa1dd0e9396db4361626d7ab8 \ + --hash=sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82 \ + --hash=sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80 \ + --hash=sha256:47a62d6045c6eaa0d8f97bc2fb68b8cf90077a0cbfd4e83d6f2d2145611ee134 \ + --hash=sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc \ + --hash=sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d \ + --hash=sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b \ + --hash=sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642 \ + --hash=sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98 \ + --hash=sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441 \ + --hash=sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11 \ + --hash=sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c \ + --hash=sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da \ + --hash=sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641 \ + --hash=sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a \ + --hash=sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec \ + --hash=sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090 \ + --hash=sha256:65be6bda2b537fefa4b3a5ccd6ab386533ce39dd8fe62433ec90901fdc81752d \ + --hash=sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555 \ + --hash=sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8 \ + --hash=sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7 \ + --hash=sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a \ + --hash=sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5 \ + --hash=sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9 \ + --hash=sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b \ + --hash=sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774 \ + --hash=sha256:764cf7bfa149365f32b7a0fd9fed32debdac29dd06295d5635cde1745b446cd8 \ + --hash=sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3 \ + --hash=sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a \ + --hash=sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2 \ + --hash=sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb \ + --hash=sha256:8196d217eeca52b9235ee1f8a684a09885a5f953d5a31e80ef915bf2c5c94f9d \ + --hash=sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc \ + --hash=sha256:84f25e099fdfe3b09f953d84698f729a1f7d1e99101b2787d7a28ed77b323750 \ + --hash=sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab \ + --hash=sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24 \ + --hash=sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74 \ + --hash=sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f \ + --hash=sha256:88b882764ef65147a7a5ae13168dedbe225a04e2ff4858fe543f2c402f093e9c \ + --hash=sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2 \ + --hash=sha256:8d1b108bd8f5f6a8b90801f6db3b3858d5deca889acfdb8ac497bbb24e4b0edf \ + --hash=sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51 \ + --hash=sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db \ + --hash=sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6 \ + --hash=sha256:98a5b2589a56a4b4f098b0a958099a4356ab904a7844f1da3841efca469af7e9 \ + --hash=sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8 \ + --hash=sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729 \ + --hash=sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce \ + --hash=sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6 \ + --hash=sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c \ + --hash=sha256:a62d8c424383c9dc769ff3672018df822603117e32686e567d452ed035b6fb2e \ + --hash=sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab \ + --hash=sha256:a9fe648abd1d9b89aebfa30407bfdd08a0271ec5dc7d44a4c6ccd1ce22cf562a \ + --hash=sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a \ + --hash=sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110 \ + --hash=sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257 \ + --hash=sha256:b1bc819c6db90e8f91a38250a1ab4c058261871aa52d2fe36382eddedf146dee \ + --hash=sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe \ + --hash=sha256:b3b451fd2723ad3191a209afe6f3f4bc86c83e9a85bdc255353b91803ee6aa66 \ + --hash=sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566 \ + --hash=sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8 \ + --hash=sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a \ + --hash=sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51 \ + --hash=sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78 \ + --hash=sha256:c89406fa3dcd4aa8662c6406cc5c0de1790e9614d2c3aaf03ca53a8a8ccf3405 \ + --hash=sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60 \ + --hash=sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435 \ + --hash=sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d \ + --hash=sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6 \ + --hash=sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03 \ + --hash=sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5 \ + --hash=sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603 \ + --hash=sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08 \ + --hash=sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d \ + --hash=sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42 \ + --hash=sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0 \ + --hash=sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800 \ + --hash=sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19 \ + --hash=sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922 \ + --hash=sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151 \ + --hash=sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1 \ + --hash=sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b \ + --hash=sha256:dcb8d5f7edef7a399d322cf28d4c4e6f98dab64d301c8f50581a1080e5198142 \ + --hash=sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067 \ + --hash=sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81 \ + --hash=sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c \ + --hash=sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6 \ + --hash=sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e \ + --hash=sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2 \ + --hash=sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2 \ + --hash=sha256:edadce7a22052056fd4384543019856b34850363c9d387929f677ae01d79709c \ + --hash=sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837 \ + --hash=sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23 \ + --hash=sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623 \ + --hash=sha256:f47aafd92aa28b941180e6da8a42b0f711851b14b81a5b6bb28dbbb1fa35152c \ + --hash=sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4 \ + --hash=sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc \ + --hash=sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd \ + --hash=sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a \ + --hash=sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4 \ + --hash=sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a \ + --hash=sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3 \ + --hash=sha256:f8e822efd54137d8cc8310eb64635ab827a4a6c72ff08691f38aa624776d8ecb \ + --hash=sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce \ + --hash=sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81 \ + --hash=sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80 \ + --hash=sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7 + # via uvicorn diff --git a/src/api/app.py b/src/api/app.py index 5d72138..06c7643 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -10,6 +10,8 @@ from fastapi.middleware.cors import CORSMiddleware from src.core.config import settings +logger = logging.getLogger(__name__) + @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: @@ -19,9 +21,11 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: ensure_admin_password_hashed, migrate_plaintext_sensitive_settings, ) + from src.core.security_check import assert_security_on_startup await init_db() # 验证连接,不建表 await migrate_plaintext_sensitive_settings() # 明文敏感配置 → 加密(幂等) await ensure_admin_password_hashed() # .env 明文密码 → scrypt 哈希(幂等) + await assert_security_on_startup() # 启动安全校验(生产拒绝/开发警告) yield await close_client() @@ -74,14 +78,23 @@ def create_app() -> FastAPI: @app.get("/health/ready") async def health_ready(): - """就绪检查: 验证数据库连接。""" + """就绪检查:验证数据库连接。 + + 数据库不可达时返回 HTTP 503,而非 200 + not_ready —— + 这样 K8s/Compose 的 readinessProbe 才能正确判定「未就绪」并停止流量。 + """ from src.db.base import engine + from fastapi.responses import JSONResponse try: async with engine.begin() as conn: await conn.run_sync(lambda conn: None) return {"status": "ready"} - except Exception: - return {"status": "not_ready"} + except Exception as e: + logger.warning("就绪检查失败(数据库不可达): %s", e) + return JSONResponse( + status_code=503, + content={"status": "not_ready", "reason": "database_unreachable"}, + ) return app diff --git a/src/api/deps.py b/src/api/deps.py index 88da424..0f095c0 100644 --- a/src/api/deps.py +++ b/src/api/deps.py @@ -167,6 +167,12 @@ class _RateLimiter: self._hits[key] = timestamps return True + def remaining(self, key: str) -> int: + """当前窗口内剩余可用次数。""" + now = time.time() + timestamps = [t for t in self._hits.get(key, []) if t > now - self.window_seconds] + return max(0, self.max_requests - len(timestamps)) + # 全局限流实例: /api/v1/predict 每分钟 10 次 _predict_limiter = _RateLimiter(max_requests=10, window_seconds=60) diff --git a/src/api/routes/admin_settings.py b/src/api/routes/admin_settings.py index 8f09fa3..781a1fb 100644 --- a/src/api/routes/admin_settings.py +++ b/src/api/routes/admin_settings.py @@ -7,7 +7,7 @@ from __future__ import annotations import logging import time -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel @@ -28,7 +28,7 @@ from src.core.runtime_config import ( set_runtime_value, ) from src.db.base import AsyncSession, get_db_read -from src.db.models import Injury, MatchStats +from src.db.models import Injury, Match, MatchStats logger = logging.getLogger(__name__) @@ -317,3 +317,123 @@ async def test_datasource(name: str): "https://v3.football.api-sports.io/status", headers={"x-apisports-key": api_key}, ) + + +# ── 数据源健康/最近采集状态(只读,不触发采集) ────────────────────── + + +@router.get("/ingest/status") +async def ingest_status(db: AsyncSession = Depends(get_db_read)): + """各数据源采集健康概览(只读,不触发任何采集)。 + + 返回尽量可得的信息;基于现有表近似的数据会标明 approximation。 + 失败追踪目前依赖系统日志缓冲,无专用采集失败表。 + """ + # ── bzzoiro: 落库目标是 matches 表,无专用采集时间戳 ── + # 近似:以 matches 表最大 match_date(已覆盖的最远比赛日) 与 created_at 作为参考 + bzzoiro_key = await get_runtime_value("BZZOIRO_KEY") + bzzoiro_base = await get_runtime_value("BZZOIRO_BASE") + row = ( + await db.execute( + select( + func.count().label("cnt"), + func.max(Match.match_date).label("latest_match_date"), + func.max(Match.created_at).label("latest_row_at"), + ).where(Match.match_status == "finished") + ) + ).one() + bzzoiro = { + "name": "bzzoiro", + "label": "Bzzoiro", + "key_configured": bool(bzzoiro_key), + "base_url": (bzzoiro_base.rstrip("/") if bzzoiro_base else None) or settings.BZZOIRO_BASE, + "reachable": None, # 不主动探测 + "last_success_at": row.latest_row_at.isoformat() if row.latest_row_at else None, + "latest_match_date": row.latest_match_date.isoformat() if row.latest_match_date else None, + "recent_count": row.cnt or 0, + "note": "approx:基于 matches.finished 表,last_success_at 为行写入时间而非精确采集完成时间", + "last_failure": _last_failure_log("bzzoiro"), + } + + # ── understat: 落库到 match_stats(source=understat),有精确 retrieved_at ── + row = ( + await db.execute( + select( + func.count().label("cnt"), + func.max(MatchStats.retrieved_at).label("latest_retrieved"), + ).where(MatchStats.source == "understat") + ) + ).one() + understat = { + "name": "understat", + "label": "Understat", + "key_configured": True, # 无需 Key + "reachable": None, + "last_success_at": row.latest_retrieved.isoformat() if row.latest_retrieved else None, + "recent_count": row.cnt or 0, + "note": "基于 match_stats.source=understat 的 retrieved_at", + "last_failure": _last_failure_log("understat"), + } + + # ── injuries: 落库到 injuries 表,有精确 retrieved_at;区分 Key/无数据/有数据 ── + api_key = await get_runtime_value("API_FOOTBALL_KEY") + row = ( + await db.execute( + select( + func.count().label("cnt"), + func.max(Injury.retrieved_at).label("latest_retrieved"), + ) + ) + ).one() + if not api_key: + injuries_status, injuries_note = "key_not_configured", "API_FOOTBALL_KEY 未配置" + elif not row.cnt: + injuries_status, injuries_note = "no_data", "本地无伤停数据,请先采集" + else: + injuries_status, injuries_note = "has_data", f"共 {row.cnt} 条伤停记录" + injuries = { + "name": "injuries", + "label": "Injuries (API-Football)", + "key_configured": bool(api_key), + "reachable": None, + "status": injuries_status, + "last_success_at": row.latest_retrieved.isoformat() if row.latest_retrieved else None, + "recent_count": row.cnt or 0, + "note": injuries_note, + "last_failure": _last_failure_log("injuries"), + } + + return {"sources": [bzzoiro, understat, injuries]} + + +def _last_failure_log(source: str) -> dict | None: + """从系统日志缓冲中查找某数据源的最近一次错误(仅作参考,非专用失败表)。""" + entries = get_entries(min_level="ERROR", keyword=source, limit=5) + if not entries: + return None + e = entries[0] + return { + "at": datetime.fromtimestamp(e["ts"]).isoformat(), + "logger": e["logger"], + "detail": e["message"][:200], + "note": "approx:来自内存日志缓冲,非专用采集失败表;进程重启后清零", + } + + +@router.get("/stats") +async def admin_stats(db: AsyncSession = Depends(get_db_read)): + """管理区统计(只读):最近预测次数。轻量聚合,无 LLM 调用。""" + from sqlalchemy import func, text + from src.db.models import Prediction + day_ago = datetime.now(timezone.utc) - timedelta(days=1) + week_ago = datetime.now(timezone.utc) - timedelta(days=7) + r = ( + await db.execute( + select( + func.count().label("total"), + func.count().filter(Prediction.created_at >= day_ago).label("last_24h"), + func.count().filter(Prediction.created_at >= week_ago).label("last_7d"), + ) + ) + ).one() + return {"predictions": {"total": r.total, "last_24h": r.last_24h, "last_7d": r.last_7d}} diff --git a/src/api/routes/backtest.py b/src/api/routes/backtest.py index 39dd912..29427d7 100644 --- a/src/api/routes/backtest.py +++ b/src/api/routes/backtest.py @@ -28,13 +28,16 @@ async def backtest(req: BacktestRequest): """对历史比赛运行回测。 该接口会对每场已完赛比赛各发起一次 LLM 预测,成本高 —— 因此需要 - `X-API-Key` 鉴权(见审查报告 P2-7)。 + 管理员鉴权(require_admin)。 对每场已完赛比赛: - 1. 用比赛之前的数据构建上下文 (防未来信息泄漏) - 2. 调 LLM 预测 - 3. 用实际比分回填 + 1. 用比赛之前的数据构建上下文 (防未来信息泄漏,cutoff=match_date-1天) + 2. 调 LLM 预测(强制 use_cache=False,避免缓存命中导致反复 settle 同一行) + 3. 用实际比分回填(settle) 4. 统计准确率 / RMSE / 校准度 + + 限流:单请求上限 200 场(默认 20),避免一次打爆 LLM 额度。 + 回测写入 run_type='backtest',与实盘(live)互不覆盖(唯一键含 run_type)。 """ try: summary = await run_backtest( @@ -53,10 +56,11 @@ async def backtest(req: BacktestRequest): "summary": { "total": summary.total, "scored": summary.scored, + "success": summary.success, + "degraded": summary.degraded, "accuracy_1x2": summary.accuracy_1x2, "avg_score_rmse": summary.avg_score_rmse, "avg_subjective_confidence": summary.avg_subjective_confidence, - "calibration": summary.calibration, }, "results": [ { diff --git a/src/api/routes/eval.py b/src/api/routes/eval.py index 71521a8..5cf6b15 100644 --- a/src/api/routes/eval.py +++ b/src/api/routes/eval.py @@ -19,13 +19,19 @@ router = APIRouter(prefix="/api/v1", tags=["eval"]) async def settle(req: SettleRequest, db: AsyncSession = Depends(get_db)): """回填实际结果。 - status 为 degraded/failed 的预测无法结算。 + status 为 degraded/failed 的预测无法结算(返回 400); + 记录不存在返回 404。 """ try: pred = await settle_prediction(req.prediction_id, req.home_goals, req.away_goals) return {"id": pred.id, "settled": pred.settled} except ValueError as e: - logger.warning("settle failed: %s", e) + msg = str(e) + # degraded/failed 拒绝:明确的 400,而非与"未找到"混为一谈 + if "无法结算" in msg: + logger.warning("settle rejected: %s", msg) + raise HTTPException(400, msg) + logger.warning("settle failed: %s", msg) raise HTTPException(404, "预测记录不存在") except Exception as e: logger.exception("settle error") diff --git a/src/api/routes/ingest.py b/src/api/routes/ingest.py index 409f740..a48e90b 100644 --- a/src/api/routes/ingest.py +++ b/src/api/routes/ingest.py @@ -73,6 +73,7 @@ async def _run_bzzoiro(leagues: list[str], date_from: str | None, date_to: str | logger.warning("bzzoiro 部分联赛存在错误: %s", sample) if merged["errors"]: logger.warning("bzzoiro 采集错误 %d 条: %s", len(merged["errors"]), merged["errors"][:3]) + logger.debug("bzzoiro 采集明细: leagues=%s", list(merged["leagues"].keys())) except Exception: logger.exception("bzzoiro 采集任务失败") diff --git a/src/api/routes/matches.py b/src/api/routes/matches.py index d14f262..1b64101 100644 --- a/src/api/routes/matches.py +++ b/src/api/routes/matches.py @@ -4,13 +4,13 @@ from __future__ import annotations from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import select +from sqlalchemy import or_, select from sqlalchemy.orm import selectinload from src.api.deps import require_admin -from src.api.schemas import MatchListOut, MatchOut +from src.api.schemas import MatchListOut, MatchOut, PredictionOut from src.db.base import AsyncSession, get_db_read -from src.db.models import League, Match +from src.db.models import League, Match, Prediction router = APIRouter(prefix="/api/v1", tags=["data"]) @@ -114,12 +114,26 @@ async def list_matches( async def get_match(match_id: int, db: AsyncSession = Depends(get_db_read)): stmt = ( select(Match) - .options(selectinload(Match.league), selectinload(Match.home_team), selectinload(Match.away_team)) + .options( + selectinload(Match.league), + selectinload(Match.home_team), + selectinload(Match.away_team), + selectinload(Match.stats), + ) .where(Match.id == match_id) ) m = (await db.execute(stmt)).scalar_one_or_none() if m is None: raise HTTPException(404, "match not found") + # 最近预测(倒序,最多 5 条)——复用 PredictionOut 结构,只读,不触发 LLM + preds = ( + await db.execute( + select(Prediction) + .where(Prediction.match_id == match_id) + .order_by(Prediction.created_at.desc()) + .limit(5) + ) + ).scalars().all() return MatchOut( id=m.id, league_code=m.league.code if m.league else None, @@ -135,4 +149,94 @@ async def get_match(match_id: int, db: AsyncSession = Depends(get_db_read)): match_stage=m.match_stage, home_xg=m.stats.home_xg if m.stats else None, away_xg=m.stats.away_xg if m.stats else None, + recent_predictions=[ + PredictionOut( + id=p.id, match_id=p.match_id, provider=p.provider, model=p.model, + prompt_version=p.prompt_version, mode=p.mode or "single", + pred_home_goals=p.pred_home_goals, pred_away_goals=p.pred_away_goals, + alt_pred_home_goals=p.alt_pred_home_goals, alt_pred_away_goals=p.alt_pred_away_goals, + pred_1x2=p.pred_1x2, subjective_confidence=p.subjective_confidence, + reasoning=p.reasoning, status=p.status or "success", + agent_outputs=p.agent_outputs, agent_weights=p.agent_weights, + created_at=p.created_at, actual_home_goals=p.actual_home_goals, + actual_away_goals=p.actual_away_goals, settled=p.settled, + ) + for p in preds + ], ) + + +@router.get("/matches/{match_id}/context", dependencies=[Depends(require_admin)]) +async def match_context(match_id: int, db: AsyncSession = Depends(get_db_read)): + """比赛上下文(只读,不触发 LLM):双方近况 + 历史交锋。 + + 全部基于现有数据聚合: + - recent_home / recent客队:该队最近 5 场已完赛(进球/结果) + - h2h:双方最近 5 次交手 + 若数据不足,对应列表为空(前端展示空态)。 + """ + m = ( + await db.execute( + select(Match) + .options(selectinload(Match.home_team), selectinload(Match.away_team)) + .where(Match.id == match_id) + ) + ).scalar_one_or_none() + if m is None: + raise HTTPException(404, "match not found") + home_id = m.home_team_id + away_id = m.away_team_id + + def _row_to_dict(row): + return { + "match_date": row.match_date.isoformat() if row.match_date else None, + "home_team": row.home_team.name_zh or row.home_team.name if row.home_team else None, + "away_team": row.away_team.name_zh or row.away_team.name if row.away_team else None, + "home_goals": row.home_goals, + "away_goals": row.away_goals, + } + + # 主队近况(已完赛,含主/客场) + home_recent = ( + await db.execute( + select(Match) + .where(Match.match_status == "finished", Match.home_team_id == home_id) + .order_by(Match.match_date.desc()) + .limit(5) + .options(selectinload(Match.home_team), selectinload(Match.away_team)) + ) + ).scalars().all() + + # 客队近况 + away_recent = ( + await db.execute( + select(Match) + .where(Match.match_status == "finished", Match.away_team_id == away_id) + .order_by(Match.match_date.desc()) + .limit(5) + .options(selectinload(Match.home_team), selectinload(Match.away_team)) + ) + ).scalars().all() + + # 历史交锋(双方已完赛) + h2h = ( + await db.execute( + select(Match) + .where( + Match.match_status == "finished", + or_( + (Match.home_team_id == home_id) & (Match.away_team_id == away_id), + (Match.home_team_id == away_id) & (Match.away_team_id == home_id), + ), + ) + .order_by(Match.match_date.desc()) + .limit(5) + .options(selectinload(Match.home_team), selectinload(Match.away_team)) + ) + ).scalars().all() + + return { + "home_recent": [_row_to_dict(r) for r in home_recent], + "away_recent": [_row_to_dict(r) for r in away_recent], + "h2h": [_row_to_dict(r) for r in h2h], + } diff --git a/src/api/routes/predict.py b/src/api/routes/predict.py index 575a32a..4df8811 100644 --- a/src/api/routes/predict.py +++ b/src/api/routes/predict.py @@ -42,7 +42,7 @@ async def predict(req: PredictRequest): if m.match_status == "finished": raise HTTPException(400, "该比赛已完赛,不再支持预测") - # 2. LLM 调用(不持有任何 DB 连接) + # 2. 预测调用(不持有任何 DB 连接) try: result = await predict_match( req.match_id, @@ -63,32 +63,77 @@ async def predict(req: PredictRequest): logger.exception("predict unexpected error") raise HTTPException(500, "预测失败,请查看服务器日志") + # baseline 模式:结果已是 dict,需独立落库(prediction_id) + if req.mode == "baseline": + prediction_id = await _persist_baseline(req.match_id, result) + else: + prediction_id = result.prediction_id + # 3. 结果映射(无 DB 访问) logger.info( "预测完成 match=%s mode=%s pred=%s:%s (%s)", - req.match_id, req.mode, result.pred_home_goals, result.pred_away_goals, result.pred_1x2, + req.match_id, req.mode, + result.get("pred_home_goals") if isinstance(result, dict) else result.pred_home_goals, + result.get("pred_away_goals") if isinstance(result, dict) else result.pred_away_goals, + result.get("pred_1x2") if isinstance(result, dict) else result.pred_1x2, ) + result_dict = result if isinstance(result, dict) else None + return PredictOut( - prediction_id=result.prediction_id, - provider=result.provider, - model=result.model, - prompt_version=getattr(result, "prompt_version", None), - mode=getattr(result, "mode", "single"), - pred_home_goals=result.pred_home_goals, - pred_away_goals=result.pred_away_goals, - alt_pred_home_goals=result.alt_pred_home_goals, - alt_pred_away_goals=result.alt_pred_away_goals, - pred_1x2=result.pred_1x2, - subjective_confidence=result.subjective_confidence, - reasoning=result.reasoning, - agent_outputs=getattr(result, "agent_outputs", None), - agent_weights=getattr(result, "agent_weights", None), - context=result.context, - latency_ms=result.latency_ms, + prediction_id=prediction_id, + provider=result.get("provider") if result_dict else result.provider, + model=result.get("model") if result_dict else result.model, + prompt_version=result.get("prompt_version") if result_dict else getattr(result, "prompt_version", None), + mode=req.mode, + pred_home_goals=result.get("pred_home_goals") if result_dict else result.pred_home_goals, + pred_away_goals=result.get("pred_away_goals") if result_dict else result.pred_away_goals, + alt_pred_home_goals=result.get("alt_pred_home_goals") if result_dict else result.alt_pred_home_goals, + alt_pred_away_goals=result.get("alt_pred_away_goals") if result_dict else result.alt_pred_away_goals, + pred_1x2=result.get("pred_1x2") if result_dict else result.pred_1x2, + subjective_confidence=result.get("subjective_confidence") if result_dict else result.subjective_confidence, + reasoning=result.get("reasoning") if result_dict else result.reasoning, + status=result.get("status", "success") if result_dict else getattr(result, "status", "success"), + agent_outputs=result.get("agent_outputs") if result_dict else getattr(result, "agent_outputs", None), + agent_weights=result.get("agent_weights") if result_dict else getattr(result, "agent_weights", None), + context=result.get("context", "") if result_dict else result.context, + latency_ms=result.get("latency_ms", 0) if result_dict else result.latency_ms, + prompt_tokens=result.get("prompt_tokens") if result_dict else getattr(result, "prompt_tokens", None), + completion_tokens=result.get("completion_tokens") if result_dict else getattr(result, "completion_tokens", None), + rate_limit_remaining=_predict_limiter.remaining(get_client_ip(request)), ) +async def _persist_baseline(match_id: int, baseline: dict) -> int: + """将基线预测结果写入 prediction 表,复用 upsert 语义。""" + from src.db.unit_of_work import get_uow + from src.llm.predict import _upsert_prediction + + async with get_uow() as session: + pred = await _upsert_prediction( + session, + match_id=match_id, + provider_name="baseline", + model="baseline", + mode="baseline", + run_type="baseline", + values={ + "prompt_version": "baseline_v1", + "prompt_tokens": 0, + "completion_tokens": 0, + "latency_ms": 0, + "pred_home_goals": baseline["pred_home_goals"], + "pred_away_goals": baseline["pred_away_goals"], + "pred_1x2": baseline["pred_1x2"], + "subjective_confidence": baseline["subjective_confidence"], + "reasoning": baseline["reasoning"], + "raw_response": baseline.get("raw", baseline), + "status": "success", + }, + ) + return pred.id + + @router.get("/predictions", response_model=list[PredictionOut], dependencies=[Depends(require_admin)]) async def list_predictions( match_id: int | None = None, @@ -110,10 +155,14 @@ async def list_predictions( mode=p.mode or "single", pred_home_goals=p.pred_home_goals, pred_away_goals=p.pred_away_goals, + alt_pred_home_goals=p.alt_pred_home_goals, + alt_pred_away_goals=p.alt_pred_away_goals, pred_1x2=p.pred_1x2, subjective_confidence=p.subjective_confidence, reasoning=p.reasoning, + status=p.status or "success", agent_outputs=p.agent_outputs, + agent_weights=p.agent_weights, created_at=p.created_at, actual_home_goals=p.actual_home_goals, actual_away_goals=p.actual_away_goals, @@ -137,10 +186,14 @@ async def get_prediction(prediction_id: int, db: AsyncSession = Depends(get_db_r mode=p.mode or "single", pred_home_goals=p.pred_home_goals, pred_away_goals=p.pred_away_goals, + alt_pred_home_goals=p.alt_pred_home_goals, + alt_pred_away_goals=p.alt_pred_away_goals, pred_1x2=p.pred_1x2, subjective_confidence=p.subjective_confidence, reasoning=p.reasoning, + status=p.status or "success", agent_outputs=p.agent_outputs, + agent_weights=p.agent_weights, created_at=p.created_at, actual_home_goals=p.actual_home_goals, actual_away_goals=p.actual_away_goals, diff --git a/src/api/schemas.py b/src/api/schemas.py index a1b0938..a00adc1 100644 --- a/src/api/schemas.py +++ b/src/api/schemas.py @@ -29,6 +29,8 @@ class MatchOut(BaseModel): match_stage: str | None home_xg: float | None = None away_xg: float | None = None + # 该场比赛的最近预测摘要(按时间倒序,最多 5 条;无预测为空) + recent_predictions: list[PredictionOut] = [] class MatchListOut(BaseModel): @@ -42,7 +44,13 @@ class PredictRequest(BaseModel): provider: str | None = None model: str | None = None prompt_version: str | None = None - mode: str = "multi" # multi(默认, 5专家+终裁) | single(单次调用) + mode: str = Field( + "multi", + description="multi(默认,5专家+终裁) | single(单次) | baseline(极简统计基线,不调用 LLM)", + ) + use_cache: bool = True + backtest: bool = False + cutoff_at: str | None = Field(None, description="显式截止时间 ISO8601,用于回测防未来信息") class PredictOut(BaseModel): @@ -58,6 +66,13 @@ class PredictOut(BaseModel): pred_1x2: str | None subjective_confidence: float | None reasoning: str | None + status: str = "success" + # 成本信息(可选;单次/多专家均有) + latency_ms: int | None = None + prompt_tokens: int | None = None + completion_tokens: int | None = None + # 限流提示:当请求被节流时告知用户剩余配额(可选) + rate_limit_remaining: int | None = None agent_outputs: list[dict] | None = None agent_weights: dict | None = None context: str @@ -78,7 +93,9 @@ class PredictionOut(BaseModel): pred_1x2: str | None subjective_confidence: float | None reasoning: str | None + status: str = "success" agent_outputs: list[dict] | None = None + agent_weights: dict | None = None created_at: datetime actual_home_goals: int | None actual_away_goals: int | None diff --git a/src/core/security_check.py b/src/core/security_check.py new file mode 100644 index 0000000..4e9ead4 --- /dev/null +++ b/src/core/security_check.py @@ -0,0 +1,121 @@ +"""生产环境启动安全校验:缺失关键配置则拒绝启动(生产)或警告(开发)。 + +校验项: + - SECRET_KEY 非空且非弱默认值 + - 鉴权已配置(密码哈希 / .env 明文密码 / API Key 任一) + - DATABASE_URL 不使用示例弱密码(football:football) + +与 deps.py 的 fail-closed 互补:此处是「启动时一次性校验 + 明确报错」, +避免生产带着危险配置上线却只在被攻击时才暴露。 +""" +from __future__ import annotations + +import logging +import sys + +from src.core.config import settings +from src.core.runtime_config import get_admin_password_hash + +logger = logging.getLogger(__name__) + +# 明显的弱 SECRET_KEY 黑名单(大小写无关) +_WEAK_SECRET_KEYS = { + "", "changeme", "secret", "password", "123456", "admin", + "default", "dev", "development", "test", "example", + "openssl rand -base64 32", # 有人把生成指令直接粘进去 +} +_MIN_SECRET_KEY_LEN = 16 + +# 示例弱数据库密码(仅识别最明显的;自定义强密码不受影响) +_WEAK_DB_PATTERNS = ("football:football@", "admin:admin@", "password@", "123456@") + + +class SecurityCheckError(Exception): + """生产环境安全校验失败。""" + + +async def _auth_configured() -> bool: + """运行时鉴权是否已配置(含数据库密码哈希/.env 明文/API Key)。""" + if await get_admin_password_hash(): + return True + if settings.ADMIN_PASSWORD or settings.ADMIN_API_KEY: + return True + return False + + +def _check_secret_key() -> list[str]: + """返回 SECRET_KEY 的问题列表(空=通过)。""" + problems: list[str] = [] + key = settings.SECRET_KEY + if not key: + problems.append("SECRET_KEY 未设置,加密与会话签名无法保障") + return problems + if key.lower().strip() in _WEAK_SECRET_KEYS: + problems.append(f"SECRET_KEY 为弱默认值({key[:20]}...),请生成强随机值: openssl rand -base64 32") + elif len(key) < _MIN_SECRET_KEY_LEN: + problems.append(f"SECRET_KEY 过短({len(key)} 字符),建议至少 {_MIN_SECRET_KEY_LEN} 位") + return problems + + +def _check_database_url() -> list[str]: + problems: list[str] = [] + url = settings.DATABASE_URL.lower() + for pat in _WEAK_DB_PATTERNS: + if pat in url: + problems.append(f"DATABASE_URL 使用示例弱密码({pat.rstrip('@')}),生产环境必须更换") + break + return problems + + +async def validate_security() -> dict: + """执行安全校验。 + + 返回 {"ok": bool, "errors": [...], "warnings": [...]}。 + errors 为阻断性问题,warnings 为建议。 + """ + errors: list[str] = [] + warnings: list[str] = [] + + errors.extend(_check_secret_key()) + if not await _auth_configured(): + errors.append("管理鉴权未配置:请设置 ADMIN_PASSWORD 或 ADMIN_API_KEY") + warnings.extend(_check_database_url()) + + # 生产环境:DB 弱密码也升级为阻断 + if settings.APP_ENV == "production" and warnings: + errors.extend(warnings) + warnings = [] + + ok = not errors + return {"ok": ok, "errors": errors, "warnings": warnings} + + +async def assert_security_on_startup() -> None: + """启动入口:生产环境校验失败则拒绝启动,开发环境仅警告。""" + result = await validate_security() + + for w in result["warnings"]: + logger.warning("[security-check] %s", w) + + if result["ok"]: + if result["warnings"]: + logger.warning("[security-check] 存在 %d 项警告,建议修复", len(result["warnings"])) + else: + logger.info("[security-check] 安全校验通过") + return + + # 阻断 + is_prod = settings.APP_ENV == "production" + level = logging.ERROR if is_prod else logging.WARNING + for e in result["errors"]: + logger.log(level, "[security-check] %s", e) + + if is_prod: + logger.critical( + "[security-check] 生产环境安全校验失败,拒绝启动。请修复上述 %d 项问题后重试。", + len(result["errors"]), + ) + # 明确退出,避免带着危险配置上线 + sys.exit(1) + + logger.warning("[security-check] 开发环境存在 %d 项问题(未阻断),请尽快修复", len(result["errors"])) diff --git a/src/llm/agents/base.py b/src/llm/agents/base.py index 7ce46c0..71baa49 100644 --- a/src/llm/agents/base.py +++ b/src/llm/agents/base.py @@ -35,7 +35,7 @@ def load_agent_prompt(name: str, version: str = "v1") -> str: @dataclass class AgentSpec: """领域专家 agent 定义。""" - name: str # h2h / form / standings / injuries / xg + name: str # h2h / form / home_away / injuries / stats system_prompt: str # system message slice_fn: object # async (header, before) -> str 切片函数 diff --git a/src/llm/agents/orchestrator.py b/src/llm/agents/orchestrator.py index 3791fd2..63e1bb1 100644 --- a/src/llm/agents/orchestrator.py +++ b/src/llm/agents/orchestrator.py @@ -97,9 +97,12 @@ class MultiPredictResult: reasoning: str | None agent_outputs: list[dict] agent_weights: dict | None + status: str = "success" context: str - latency_ms: int | None - raw: dict | None + latency_ms: int | None = None + prompt_tokens: int | None = None + completion_tokens: int | None = None + raw: dict | None = None async def _agent_provider(agent_id: str, *, tier: str) -> LLMProvider: @@ -158,7 +161,10 @@ async def run_specialists( reports: list[AgentReport] = [] for spec, r in zip(SPECIALIST_SPECS, results): if isinstance(r, Exception): - logger.warning("agent %s raised: %s", spec.name, r) + logger.warning( + "专家调用失败 match=%s agent=%s error=%s", + header.match_id, spec.name, str(r)[:120], + ) reports.append(AgentReport(agent=spec.name, status="error", analysis=str(r)[:200])) else: reports.append(r) @@ -256,8 +262,8 @@ async def predict_match_multi( else: # 所有专家无数据/均失败:跳过终裁,标记 degraded logger.warning( - "match %s: 所有 %d 位专家均无有效数据,跳过终裁,标记 degraded", - match_id, len(reports), + "预测降级 match=%s mode=%s status=degraded experts=%d/%d 均无有效数据", + match_id, "multi", ok_reports, len(reports), ) # 无有效专家时不调用 aggregator provider,避免多余开销 # model 使用 settings 默认值占位(无实际 LLM 调用) @@ -337,6 +343,13 @@ async def predict_match_multi( }, ) + logger.info( + "预测完成 match=%s mode=%s status=%s pred=%s:%s (%s) latency=%sms, experts=%d/%d, prediction_id=%s", + match_id, "multi", pred_status, + pred.pred_home_goals, pred.pred_away_goals, pred.pred_1x2, + latency_ms, ok_reports, len(reports), pred.id, + ) + return MultiPredictResult( prediction_id=pred.id, provider=pred.provider, @@ -350,9 +363,12 @@ async def predict_match_multi( pred_1x2=pred.pred_1x2, subjective_confidence=pred.subjective_confidence, reasoning=pred.reasoning, + status=pred_status, agent_outputs=pred.agent_outputs, agent_weights=agent_weights, context=_reports_to_json(reports), latency_ms=latency_ms, + prompt_tokens=pred.prompt_tokens, + completion_tokens=pred.completion_tokens, raw=final, ) diff --git a/src/llm/backtest.py b/src/llm/backtest.py index f402cbc..258f180 100644 --- a/src/llm/backtest.py +++ b/src/llm/backtest.py @@ -70,6 +70,8 @@ class BacktestSummary: """回测汇总统计。""" total: int scored: int + success: int = 0 # status=success 的预测数(有完整比分+1x2) + degraded: int = 0 # status=degraded 的预测数(专家失败/无有效数据) accuracy_1x2: float | None = None avg_score_rmse: float | None = None avg_subjective_confidence: float | None = None @@ -194,6 +196,17 @@ async def run_backtest( if r is not None: summary.results.append(r) summary.scored += 1 + # success:有完整预测比分+1x2;degraded:多专家模式无有效结论 + if r.pred_1x2 is not None and r.pred_home is not None and r.pred_away is not None: + summary.success += 1 + else: + summary.degraded += 1 + + logger.info( + "回测汇总 mode=%s total=%d scored=%d success=%d accuracy=%s%%", + mode, summary.total, summary.scored, summary.success, + f"{(sum(1 for r in summary.results if r.correct_1x2) / summary.scored * 100):.1f}" if summary.scored else "n/a", + ) # 汇总统计 if summary.scored > 0: diff --git a/src/llm/baseline.py b/src/llm/baseline.py new file mode 100644 index 0000000..baad4c7 --- /dev/null +++ b/src/llm/baseline.py @@ -0,0 +1,113 @@ +"""极简基线预测:主客场场均进球估计(不调用 LLM,不产生费用)。 + +用于与 LLM 预测做 eval 对比。这是最简单的统计基线,仅供研究参考, +文档与 reasoning 均明确标注「非投注建议」。 +""" +from __future__ import annotations + +import logging +from datetime import datetime + +from sqlalchemy import case, func, select + +from src.db.base import AsyncSession, AsyncSessionLocal +from src.db.models import Match + +logger = logging.getLogger(__name__) + + +async def _avg_goals( + db: AsyncSession, + *, + team_id: int, + side: str, + league_id: int, + before: datetime | None, +) -> float: + """某队在该联赛已完赛场次的场均进球(side=home/away)。""" + if side == "home": + goals_col = Match.home_goals + team_col = Match.home_team_id + else: + goals_col = Match.away_goals + team_col = Match.away_team_id + + stmt = ( + select(func.avg(goals_col).label("avg_goals"), func.count().label("cnt")) + .where( + Match.match_status == "finished", + team_col == team_id, + Match.league_id == league_id, + goals_col.is_not(None), + ) + ) + if before is not None: + stmt = stmt.where(Match.match_date < before) + row = (await db.execute(stmt)).one() + return float(row.avg_goals) if row.avg_goals is not None and row.cnt > 0 else 0.0 + + +async def predict_baseline( + match_id: int, + *, + backtest: bool = False, + cutoff_at: datetime | None = None, +) -> dict: + """极简基线预测:主场场均进球 vs 客场场均进球。 + + 返回与 PredictResult 兼容的字典: + provider=model="baseline", 不调用 LLM,latency_ms≈0。 + """ + async with AsyncSessionLocal() as db: + match = await db.get(Match, match_id) + if match is None: + raise ValueError(f"match {match_id} not found") + + before = None + if backtest and match.match_dt: + from datetime import timedelta + + before = match.match_dt - timedelta(days=1) + elif cutoff_at is not None: + before = cutoff_at + + home_avg = await _avg_goals( + db, team_id=match.home_team_id, side="home", + league_id=match.league_id, before=before, + ) + away_avg = await _avg_goals( + db, team_id=match.away_team_id, side="away", + league_id=match.league_id, before=before, + ) + + pred_home = max(0, min(10, round(home_avg))) + pred_away = max(0, min(10, round(away_avg))) + # 主场轻微加成(可选,这里保持极简不额外加权) + if pred_home > pred_away: + pred_1x2 = "1" + elif pred_home < pred_away: + pred_1x2 = "2" + else: + pred_1x2 = "X" + + return { + "pred_home_goals": float(pred_home), + "pred_away_goals": float(pred_away), + "alt_pred_home_goals": None, + "alt_pred_away_goals": None, + "pred_1x2": pred_1x2, + "subjective_confidence": 0.5, + "prompt_tokens": 0, + "completion_tokens": 0, + "reasoning": ( + f"基线估计(非投注建议): 主队主场场均进球 {home_avg:.2f} → 预测 {pred_home}; " + f"客队客场场均进球 {away_avg:.2f} → 预测 {pred_away}。" + ), + "provider": "baseline", + "model": "baseline", + "prompt_version": "baseline_v1", + "mode": "baseline", + "status": "success", + "latency_ms": 0, + "raw": {"home_avg": round(home_avg, 2), "away_avg": round(away_avg, 2)}, + } diff --git a/src/llm/eval.py b/src/llm/eval.py index 4e4d58b..612a6cf 100644 --- a/src/llm/eval.py +++ b/src/llm/eval.py @@ -25,6 +25,10 @@ async def settle_prediction(prediction_id: int, home_goals: int, away_goals: int pred.actual_home_goals = home_goals pred.actual_away_goals = away_goals pred.settled = True + logger.info( + "结算完成 prediction_id=%s match=%s actual=%s:%s mode=%s", + prediction_id, pred.match_id, home_goals, away_goals, pred.mode or "single", + ) return pred @@ -109,8 +113,14 @@ async def get_eval_summary( rows = list((await session.execute(stmt)).scalars().all()) from collections import defaultdict - buckets: dict[tuple[str, str], dict] = defaultdict(lambda: { + buckets: dict[tuple[str, str, str], dict] = defaultdict(lambda: { "total": 0, "correct_1x2": 0, "score_errors": [], "conf_sum": 0.0, "conf_count": 0, + # 置信度校准分桶(仅 settled 且 pred 完整者计入) + "conf_buckets": { + "low(0-0.5)": {"total": 0, "correct": 0}, + "medium(0.5-0.7)": {"total": 0, "correct": 0}, + "high(0.7-1)": {"total": 0, "correct": 0}, + }, }) evaluated = 0 skipped_incomplete = 0 @@ -118,35 +128,59 @@ async def get_eval_summary( if (p.pred_home_goals is None or p.pred_away_goals is None or p.pred_1x2 is None): skipped_incomplete += 1 continue - key = (p.provider, p.model) + key = (p.provider, p.model, p.prompt_version or "") b = buckets[key] b["total"] += 1 evaluated += 1 - if p.actual_home_goals is None or p.actual_away_goals is None: - continue - actual = _actual_1x2(p.actual_home_goals, p.actual_away_goals) - if p.pred_1x2 == actual: - b["correct_1x2"] += 1 - if p.pred_home_goals is not None and p.pred_away_goals is not None: + + correct = False + if p.actual_home_goals is not None and p.actual_away_goals is not None: + actual = _actual_1x2(p.actual_home_goals, p.actual_away_goals) + if p.pred_1x2 == actual: + b["correct_1x2"] += 1 + correct = True + if ( + p.pred_home_goals is not None and p.pred_away_goals is not None + and p.actual_home_goals is not None and p.actual_away_goals is not None + ): err = ((p.pred_home_goals - p.actual_home_goals) ** 2 + (p.pred_away_goals - p.actual_away_goals) ** 2) ** 0.5 b["score_errors"].append(err) if p.subjective_confidence is not None: b["conf_sum"] += p.subjective_confidence b["conf_count"] += 1 + # 仅当有实际结果可用于校准时,才落入置信度分桶 + if p.actual_home_goals is not None and p.actual_away_goals is not None: + conf = p.subjective_confidence + if conf < 0.5: + bucket = "low(0-0.5)" + elif conf < 0.7: + bucket = "medium(0.5-0.7)" + else: + bucket = "high(0.7-1)" + b["conf_buckets"][bucket]["total"] += 1 + if correct: + b["conf_buckets"][bucket]["correct"] += 1 summary = [] - for (prov, model), b in sorted(buckets.items()): + for (prov, model, ver), b in sorted(buckets.items()): acc = (b["correct_1x2"] / b["total"] * 100) if b["total"] else 0 avg_err = (sum(b["score_errors"]) / len(b["score_errors"])) if b["score_errors"] else None avg_conf = (b["conf_sum"] / b["conf_count"]) if b["conf_count"] else None + # 校准分桶 → 命中率 + calibration = {} + for name, cb in b["conf_buckets"].items(): + hit_rate = round(cb["correct"] / cb["total"] * 100, 1) if cb["total"] else None + calibration[name] = {"total": cb["total"], "hit_rate": hit_rate} summary.append({ "provider": prov, "model": model, + "prompt_version": ver or None, "total": b["total"], "accuracy_1x2": round(acc, 1), "avg_score_rmse": round(avg_err, 2) if avg_err is not None else None, "avg_subjective_confidence": round(avg_conf, 2) if avg_conf is not None else None, + "calibration": calibration, }) return { "summary": summary, diff --git a/src/llm/predict.py b/src/llm/predict.py index 19e319a..2f384ac 100644 --- a/src/llm/predict.py +++ b/src/llm/predict.py @@ -101,8 +101,9 @@ class PredictResult: subjective_confidence: float | None reasoning: str | None context: str - latency_ms: int | None - raw: dict | None + status: str = "success" + latency_ms: int | None = None + raw: dict | None = None async def _upsert_prediction( @@ -158,15 +159,22 @@ async def predict_match( backtest: bool = False, cutoff_at=None, ) -> "PredictResult | MultiPredictResult": - """预测入口。mode=multi(默认)走多 agent;mode=single 走单次调用。 + """预测入口。mode=multi(默认)走多 agent;mode=single 走单次调用;mode=baseline 走无 LLM 基线。 Args: - use_cache: 是否允许返回进程内缓存结果。回测必须传 False—— + mode: multi(默认,5 专家+终裁) / single(单次) / baseline(极简统计基线,不调用 LLM)。 + use_cache:是否允许返回进程内缓存结果。回测必须传 False—— 缓存命中不会新建 prediction 行,调用方会对同一个 prediction_id 反复 settle,把不同比赛的真实比分覆盖到同一条记录上。 - backtest: 是否回测模式。True 时 cutoff 自动设为 match_date-1天。 - cutoff_at: 显式截止时间,优先级高于 backtest 自动计算。 + backtest:是否回测模式。True 时 cutoff 自动设为 match_date-1天。 + cutoff_at:显式截止时间,优先级高于 backtest 自动计算。 """ + if mode == "baseline": + from src.llm.baseline import predict_baseline + + return await predict_baseline( + match_id, backtest=backtest, cutoff_at=cutoff_at, + ) if mode == "single": return await _predict_single( match_id, @@ -300,6 +308,7 @@ async def _predict_single( pred_1x2=pred.pred_1x2, subjective_confidence=pred.subjective_confidence, reasoning=pred.reasoning, + status=pred.status, context=ctx.text, latency_ms=resp.latency_ms, raw=resp.raw, @@ -308,4 +317,10 @@ async def _predict_single( # 5. 写入缓存(仅当允许缓存时) if use_cache: _set_cached(match_id, settings.LLM_PROVIDER, provider.model, version, tpl_hash, result) + logger.info( + "预测完成 match=%s mode=%s status=%s pred=%s:%s (%s) latency=%sms", + match_id, "single", "success", + validated.pred_home_goals, validated.pred_away_goals, validated.pred_1x2, + resp.latency_ms, + ) return result diff --git a/src/llm/prompts/match_prediction_v2.md b/src/llm/prompts/match_prediction_v2.md index ce5c459..fe606ea 100644 --- a/src/llm/prompts/match_prediction_v2.md +++ b/src/llm/prompts/match_prediction_v2.md @@ -6,7 +6,6 @@ 1. 主客队近期状态差异 2. 主客场因素 3. 历史交锋心理优势 -4. 联赛排名差距 严格按此 JSON 输出,不要其他内容: ```json diff --git a/tests/fixtures/bzzoiro_event.json b/tests/fixtures/bzzoiro_event.json new file mode 100644 index 0000000..9cc3900 --- /dev/null +++ b/tests/fixtures/bzzoiro_event.json @@ -0,0 +1,29 @@ +{ + "_note": "脱敏样例:球队名/日期/ID 已替换为占位符,字段结构对齐真实 bzzoiro 响应。待获取真实响应后替换。", + "id": "evt_placeholder_001", + "event_date": "2026-09-12T19:00:00+00:00", + "status": "finished", + "league": { "id": 1, "code": "E0", "name": "Premier League" }, + "season": "2026-2027", + "round_number": 5, + "round_name": null, + "home_team": "Home United FC", + "away_team": "Away City FC", + "home_score": 2, + "away_score": 1, + "home_score_ht": 1, + "away_score_ht": 0, + "home_shots": 14, + "away_shots": 8, + "home_shots_on_target": 5, + "away_shots_on_target": 3, + "home_corners": 6, + "away_corners": 4, + "home_possession": 58.5, + "home_xg": 1.85, + "away_xg": 0.92, + "home_yellow_cards": 2, + "away_yellow_cards": 3, + "home_red_cards": 0, + "away_red_cards": 0 +} diff --git a/tests/test_api_critical.py b/tests/test_api_critical.py new file mode 100644 index 0000000..d29726d --- /dev/null +++ b/tests/test_api_critical.py @@ -0,0 +1,189 @@ +"""FastAPI 关键路径测试:鉴权、限流、游标方向。 + +运行(需先 pip-sync requirements-dev.txt): + pytest tests/test_api_critical.py -v + +设计: + - 鉴权:直接测 require_admin / auth_configured 逻辑,monkeypatch 切换环境, + 避免启动完整 app lifespan(异步 DB 引擎与同步 TestClient 不兼容)。 + - 限流:直接测 _RateLimiter 单元。 + - 游标:直接构造 SQL 验证 scheduled ASC / 其他 DESC 方向。 + - 不依赖真实 LLM / 数据库:纯逻辑测试。 +""" +from __future__ import annotations + +import asyncio +from unittest.mock import AsyncMock, patch + +import pytest + +from src.api import deps +from src.api.deps import ( + _RateLimiter, + auth_configured, + require_admin, +) +from src.core import runtime_config + + +# ── 1. 鉴权:fail-closed(生产) vs fail-open(开发) ───────────────────── + + +class TestAuthFailClosed: + """未配置鉴权策略时,production 环境应拒绝(503),development 应放行。""" + + @pytest.mark.asyncio + async def test_production_no_auth_returns_503(self): + """APP_ENV=production + 未配置任何鉴权 → require_admin 抛 503。""" + from fastapi import HTTPException + from fastapi.requests import Request + + request = Request(scope={"type": "http", "client": ("1.2.3.4", 0), "cookies": {}, "headers": []}) + + with patch.object(deps, "settings") as s, \ + patch("src.api.deps.auth_configured", AsyncMock(return_value=False)): + s.REQUIRE_ADMIN_AUTH = False + s.APP_ENV = "production" + s.ADMIN_API_KEY = "" + s.ADMIN_PASSWORD = "" + with pytest.raises(HTTPException) as exc: + await require_admin(request, x_api_key=None) + assert exc.value.status_code == 503 + + @pytest.mark.asyncio + async def test_development_no_auth_passes(self): + """APP_ENV=development + 未配置鉴权 → 放行(只打 warning)。""" + from fastapi.requests import Request + + request = Request(scope={"type": "http", "client": ("1.2.3.4", 0), "cookies": {}, "headers": []}) + + with patch.object(deps, "settings") as s, \ + patch("src.api.deps.auth_configured", AsyncMock(return_value=False)): + s.REQUIRE_ADMIN_AUTH = False + s.APP_ENV = "development" + s.ADMIN_API_KEY = "" + s.ADMIN_PASSWORD = "" + # 不应抛异常 + await require_admin(request, x_api_key=None) + + @pytest.mark.asyncio + async def test_require_admin_key_valid(self): + """配置 ADMIN_API Key 后,带正确 X-API-Key 头 → 通过。""" + from fastapi.requests import Request + + request = Request(scope={"type": "http", "client": ("1.2.3.4", 0), "cookies": {}, + "headers": [(b"x-api-key", b"test-secret-key")]}) + + with patch.object(deps, "settings") as s, \ + patch("src.core.runtime_config.get_admin_password_hash", AsyncMock(return_value="")): + s.REQUIRE_ADMIN_AUTH = True + s.APP_ENV = "production" + s.ADMIN_API_KEY = "test-secret-key" + # 不应抛异常 + await require_admin(request, x_api_key="test-secret-key") + + @pytest.mark.asyncio + async def test_require_admin_key_invalid(self): + """API Key 错误 → 401。""" + from fastapi import HTTPException + from fastapi.requests import Request + + request = Request(scope={"type": "http", "client": ("1.2.3.4", 0), "cookies": {}, + "headers": [(b"x-api-key", b"wrong")]}) + + with patch.object(deps, "settings") as s, \ + patch("src.core.runtime_config.get_admin_password_hash", AsyncMock(return_value="")): + s.REQUIRE_ADMIN_AUTH = True + s.APP_ENV = "production" + s.ADMIN_API_KEY = "test-secret-key" + with pytest.raises(HTTPException) as exc: + await require_admin(request, x_api_key="wrong") + assert exc.value.status_code == 401 + + +# ── 2. 限流:滑动窗口逻辑 ───────────────────────────────────────────── + + +class TestRateLimit: + """预测限流:_RateLimiter 单元测试。""" + + def test_rate_limit_triggers_after_max(self): + """max=2/60s → 第 3 次被拒。""" + limiter = _RateLimiter(max_requests=2, window_seconds=60) + ip = "1.2.3.4" + assert limiter.is_allowed(ip) is True + assert limiter.is_allowed(ip) is True + assert limiter.is_allowed(ip) is False # 超限 + assert limiter.remaining(ip) == 0 + + def test_rate_limit_remaining_decrements(self): + """剩余配额计算准确。""" + limiter = _RateLimiter(max_requests=5, window_seconds=60) + ip = "5.6.7.8" + assert limiter.remaining(ip) == 5 + limiter.is_allowed(ip) + limiter.is_allowed(ip) + assert limiter.remaining(ip) == 3 + + def test_rate_limit_per_ip_isolated(self): + """不同 IP 独立计数。""" + limiter = _RateLimiter(max_requests=1, window_seconds=60) + assert limiter.is_allowed("1.1.1.1") is True + assert limiter.is_allowed("1.1.1.1") is False # 同 IP 超限 + assert limiter.is_allowed("2.2.2.2") is True # 不同 IP 不受影响 + + +# ── 3. 游标方向:scheduled ASC 用 > ─────────────────────────────────── + + +class TestCursorDirection: + """验证 scheduled 状态查询时排序方向为 ASC(使用 > 游标)。""" + + def test_scheduled_uses_ascending_order(self): + """scheduled → match_date ASC(最近的未开赛排最前)。""" + from src.api.routes import matches as matches_mod + from sqlalchemy import select + + q = select(matches_mod.Match).where(matches_mod.Match.match_status == "scheduled") + q = q.order_by(matches_mod.Match.match_date.asc(), matches_mod.Match.id.asc()) + sql = str(q) + assert "ORDER BY matches.match_date ASC" in sql, sql + + def test_finished_uses_descending_order(self): + """finished/其他 → DESC(最新赛果在前)。""" + from src.api.routes import matches as matches_mod + from sqlalchemy import select + + q = select(matches_mod.Match).where(matches_mod.Match.match_status == "finished") + q = q.order_by(matches_mod.Match.match_date.desc(), matches_mod.Match.id.desc()) + sql = str(q) + assert "ORDER BY matches.match_date DESC" in sql, sql + + +# ── 评估置信度校准分桶 ───────────────────────────────────────────── + + + + +# ── 评估置信度校准分桶 ───────────────────────────────────────────── + + +class TestEvalCalibration: + """验证 settled 预测按置信度分桶统计命中率。""" + + def test_confidence_bucketing(self): + """置信度落入正确的桶。""" + assert _bucket_key(0.3) == "low(0-0.5)" + assert _bucket_key(0.5) == "medium(0.5-0.7)" + assert _bucket_key(0.6) == "medium(0.5-0.7)" + assert _bucket_key(0.7) == "high(0.7-1)" + assert _bucket_key(0.95) == "high(0.7-1)" + + +def _bucket_key(conf: float) -> str: + if conf < 0.5: + return "low(0-0.5)" + elif conf < 0.7: + return "medium(0.5-0.7)" + else: + return "high(0.7-1)" diff --git a/tests/test_baseline.py b/tests/test_baseline.py new file mode 100644 index 0000000..6d76338 --- /dev/null +++ b/tests/test_baseline.py @@ -0,0 +1,130 @@ +"""测试极简基线预测:不调用 LLM,基于主客场场均进球估计,写入 prediction 表。 + +运行(需先 pip-sync requirements-dev.txt): + pytest tests/test_baseline.py -v +""" +from __future__ import annotations + +from unittest.mock import patch + +import pytest + +from src.llm.baseline import _avg_goals, predict_baseline + + +@pytest.mark.asyncio +async def test_avg_goals_no_data_returns_zero(): + """无历史数据时场均进球为 0(不抛异常)。""" + class FakeRow: + avg_goals = None + cnt = 0 + + class FakeResult: + def one(self): + return FakeRow() + + class FakeSession: + async def execute(self, stmt): + return FakeResult() + + avg = await _avg_goals(FakeSession(), team_id=1, side="home", league_id=1, before=None) + assert avg == 0.0 + + +@pytest.mark.asyncio +async def test_avg_goals_with_data(): + """有数据时返回正确均值。""" + class FakeRow: + avg_goals = 1.5 + cnt = 10 + + class FakeResult: + def one(self): + return FakeRow() + + class FakeSession: + async def execute(self, stmt): + return FakeResult() + + avg = await _avg_goals(FakeSession(), team_id=1, side="home", league_id=1, before=None) + assert avg == 1.5 + + +@pytest.mark.asyncio +async def test_predict_baseline_no_llm(): + """基线预测不调用 LLM(provider=model=baseline),latency_ms=0。""" + captured = {} + + async def fake_avg(db, *, team_id, side, league_id, before): + captured[f"{side}_{team_id}"] = True + return 2.4 if side == "home" else 1.6 + + class FakeMatch: + id = 1 + match_id = 1 + home_team_id = 10 + away_team_id = 20 + league_id = 1 + match_status = "scheduled" + + with patch("src.llm.baseline._avg_goals", fake_avg), \ + patch("src.llm.baseline.AsyncSessionLocal") as SLC: + class FakeSession: + async def get(self, cls, mid): + return FakeMatch() + class FakeCM: + async def __aenter__(self): + return FakeSession() + async def __aexit__(self, *a): + return None + SLC.return_value = FakeCM() + + result = await predict_baseline(1) + + assert result["provider"] == "baseline" + assert result["model"] == "baseline" + assert result["mode"] == "baseline" + assert result["latency_ms"] == 0 + assert result["prompt_tokens"] == 0 + assert result["completion_tokens"] == 0 + # 2.4 → round = 2, 1.6 → round = 2 → 平局 X + assert result["pred_home_goals"] == 2.0 + assert result["pred_away_goals"] == 2.0 + assert result["pred_1x2"] == "X" + assert result["subjective_confidence"] == 0.5 + assert "非投注建议" in result["reasoning"] + # 确认未调用任何 LLM 相关模块 + assert "home_10" in captured and "away_20" in captured + + +@pytest.mark.asyncio +async def test_predict_baseline_clamps_to_range(): + """预测进球数裁剪到 [0, 10]。""" + async def fake_avg(db, *, team_id, side, league_id, before): + return 15.0 if side == "home" else -3.0 + + class FakeMatch: + id = 2 + match_id = 2 + home_team_id = 10 + away_team_id = 20 + league_id = 1 + match_status = "scheduled" + + with patch("src.llm.baseline._avg_goals", fake_avg), \ + patch("src.llm.baseline.AsyncSessionLocal") as SLC: + class FakeSession: + async def get(self, cls, mid): + return FakeMatch() + class FakeCM: + async def __aenter__(self): + return FakeSession() + async def __aexit__(self, *a): + return None + SLC.return_value = FakeCM() + + result = await predict_baseline(2) + + assert result["pred_home_goals"] == 10.0 # clamped + assert result["pred_away_goals"] == 0.0 # clamped + assert result["pred_1x2"] == "1" # 10:0 主胜 diff --git a/tests/test_bzzoiro_normalize.py b/tests/test_bzzoiro_normalize.py new file mode 100644 index 0000000..eca952e --- /dev/null +++ b/tests/test_bzzoiro_normalize.py @@ -0,0 +1,176 @@ +"""测试 bzzoiro 事件规范化:基于 fixtures/bzzoiro_event.json 的真实字段映射。 + +运行(需先 pip-sync requirements-dev.txt): + pytest tests/test_bzzoiro_normalize.py -v + +若真实 bzzoiro 字段名与样例不同,断言会失败 —— 这正是本测试的目的: +锁定 normalize_bzzoiro 所依赖的字段名,避免上游静默变更导致数据丢失。 +""" +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from src.data.config import LEAGUE_NAMES +from src.data.normalize import normalize_bzzoiro + +FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures" + + +def load_event() -> dict: + with open(FIXTURE_DIR / "bzzoiro_event.json", encoding="utf-8") as f: + return json.load(f) + + +class TestBzzoiroNormalize: + """验证 normalize_bzzoiro 对 fixture 样例的解析结果。""" + + def test_basic_fields(self): + """基础字段:日期、状态、对阵、进球。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m is not None + assert m.home_team == "Home United FC" + assert m.away_team == "Away City FC" + assert m.match_status == "finished" + assert m.home_goals == 2 + assert m.away_goals == 1 + assert m.home_ht_goals == 1 + assert m.away_ht_goals == 0 + + def test_shots_mapping(self): + """射门数映射到 home_shots / away_shots。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_shots == 14 + assert m.away_shots == 8 + + def test_shots_on_target_mapping(self): + """射正数映射到 home_shots_on_target / away_shots_on_target。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_shots_on_target == 5 + assert m.away_shots_on_target == 3 + + def test_corners_mapping(self): + """角球映射。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_corners == 6 + assert m.away_corners == 4 + + def test_possession_mapping(self): + """控球率:API 提供 home 值。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_possession == 58.5 + + def test_xg_mapping(self): + """xG 映射到 home_xg / away_xg。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_xg == 1.85 + assert m.away_xg == 0.92 + + def test_cards_mapping(self): + """黄牌、红牌映射。""" + m = normalize_bzzoiro(load_event(), "E0") + assert m.home_yellow_cards == 2 + assert m.away_yellow_cards == 3 + assert m.home_red_cards == 0 + assert m.away_red_cards == 0 + + def test_fallback_aliases(self): + """回退别名:shots_home → home_shots, xg_home → home_xg。""" + raw = { + "event_date": "2026-09-12T19:00:00+00:00", + "status": "finished", + "home_team": "FC Alpha", + "away_team": "FC Beta", + "home_goals": 1, + "away_goals": 1, + "shots_home": 10, "shots_away": 5, + "sot_home": 4, "sot_away": 2, + "corners_home": 3, "corners_away": 2, + "possession": 55.0, + "xg_home": 1.2, "xg_away": 0.8, + "yellow_cards_home": 1, "yellow_cards_away": 2, + "red_cards_home": 0, "red_cards_away": 1, + } + m = normalize_bzzoiro(raw, "SP1") + assert m is not None + assert m.home_shots == 10 + assert m.away_shots == 5 + assert m.home_shots_on_target == 4 + assert m.away_shots_on_target == 2 + assert m.home_corners == 3 + assert m.away_corners == 2 + assert m.home_possession == 55.0 + assert m.home_xg == 1.2 + assert m.away_xg == 0.8 + assert m.home_yellow_cards == 1 + assert m.away_yellow_cards == 2 + assert m.home_red_cards == 0 + assert m.away_red_cards == 1 + + def test_missing_stats_still_normalizes(self): + """缺少统计字段时仍应解析基础数据,统计字段为 None(不伪造)。""" + raw = { + "event_date": "2026-09-12T19:00:00+00:00", + "status": "finished", + "home_team": "FC One", + "away_team": "FC Two", + "home_goals": 3, + "away_goals": 0, + } + m = normalize_bzzoiro(raw, "D1") + assert m is not None + assert m.home_goals == 3 + assert m.away_goals == 0 + # 无数据字段保持 None,不伪造 + assert m.home_shots is None + assert m.home_xg is None + assert m.home_possession is None + + def test_unknown_status_drops(self): + """未知状态 → 丢弃(None)。""" + raw = { + "event_date": "2026-09-12T19:00:00+00:00", + "status": "weird_status", + "home_team": "A", + "away_team": "B", + } + assert normalize_bzzoiro(raw, "E0") is None + + def test_invalid_date_drops(self): + """无效日期 → 丢弃(None)。""" + raw = { + "event_date": "not-a-date", + "status": "finished", + "home_team": "A", + "away_team": "B", + "home_goals": 1, + "away_goals": 0, + } + assert normalize_bzzoiro(raw, "E0") is None + + def test_same_team_drops(self): + """主客队同名(规范化后) → 丢弃(None)。""" + raw = { + "event_date": "2026-09-12T19:00:00+00:00", + "status": "finished", + "home_team": "Same FC", + "away_team": "Same FC", + "home_goals": 1, + "away_goals": 0, + } + assert normalize_bzzoiro(raw, "E0") is None + + +# ── 真实响应校验(占位,待替换后取消 skip) ───────────────────────── + +@pytest.mark.skip(reason="待提供真实 bzzoiro event 响应后替换 fixture 并取消 skip") +def test_real_response_matches_fixture_structure(): + """真实响应应能被 fixture 结构覆盖(字段名一致)。""" + # 真实响应粘贴于此,验证 normalize_bzzoiro 解析成功 + real_response = {} + if not real_response: + pytest.skip("未提供真实响应") + m = normalize_bzzoiro(real_response, "E0") + assert m is not None diff --git a/tests/test_health_ready.py b/tests/test_health_ready.py new file mode 100644 index 0000000..6e2abdf --- /dev/null +++ b/tests/test_health_ready.py @@ -0,0 +1,71 @@ +"""验证就绪探针:数据库不可用时 /health/ready 返回 503 而非 200。 + +运行方式(在宿主机上): + python tests/test_health_ready.py + +脚本经本地 8000 端口直调 API,通过启停 postgres 容器验证: + - 健康时返回 HTTP 200 + - postgres 停止后返回 HTTP 503(不再误报 200) + - postgres 恢复后回到 200 +""" +from __future__ import annotations + +import json +import subprocess +import sys +import time +import urllib.request + + +BASE = "http://localhost:8000" + + +def api_status() -> tuple[int, dict]: + try: + with urllib.request.urlopen(f"{BASE}/health/ready", timeout=5) as r: + return r.status, json.loads(r.read()) + except urllib.error.HTTPError as e: + return e.code, json.loads(e.read()) + + +def compose(*args: str) -> None: + subprocess.run(["docker", "compose", *args], check=False, capture_output=True) + + +def wait_for(target: int, timeout: int = 30) -> bool: + deadline = time.time() + timeout + while time.time() < deadline: + try: + code, _ = api_status() + if code == target: + return True + except Exception: + pass + time.sleep(1) + return False + + +def main() -> int: + code, _ = api_status() + if code != 200: + print(f"FAIL: 初始状态期望 200,得到 {code}"); return 1 + print(f"PASS: 健康时 HTTP 200") + + compose("stop", "postgres") + try: + if not wait_for(503, timeout=30): + print("FAIL: postgres 停止后未返回 503"); return 1 + print("PASS: postgres 停止后 HTTP 503(就绪探针正确拒绝)") + finally: + compose("start", "postgres") + + if not wait_for(200, timeout=30): + print("FAIL: postgres 恢复后未回到 200"); return 1 + print("PASS: postgres 恢复后 HTTP 200") + + print("ALL PASS") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_matches_cursor.py b/tests/test_matches_cursor.py index 64fa0e5..0df9791 100644 --- a/tests/test_matches_cursor.py +++ b/tests/test_matches_cursor.py @@ -1,89 +1,25 @@ -"""回归测试: 比赛列表游标分页方向修复。 +"""验证: matches 游标翻页方向正确且无重复 id。 -验证: - - status=scheduled 时,游标条件为「大于」(ASC 方向) - - 其它 status 时,游标条件为「小于」(DESC 方向) - - 无 cursor 时行为不变 +scheduled 升序游标条件必须为 > 而非 <;翻页返回的 id 集合无重复。 +端到端验证脚本(容器内运行,通过 nginx 代理): + python - <<'PY' + import urllib.parse, urllib.request, json + base = "http://localhost:3000/api/v1/matches?league=E0&status=scheduled&limit=50&cursor=" + seen = set(); cursor = None; pages = 0 + while True: + url = base + ("" if cursor is None else urllib.parse.quote(cursor, safe="")) + d = json.load(urllib.request.urlopen(url)) + ids = [m["id"] for m in d["items"]] + dup = seen.intersection(ids) + assert not dup, f"页{pages}出现重复id: {dup}" + seen.update(ids); pages += 1 + if not d["has_more"] or not d["next_cursor"]: break + cursor = d["next_cursor"] + import subprocess + total = int(subprocess.check_output( + ["psql","-U","football","-d","football","-tAc", + "SELECT count(*) FROM matches WHERE match_status='scheduled' AND league_id=(SELECT id FROM leagues WHERE code='E0')"])) + assert len(seen) == total, f"翻页得{len(seen)}条,库中{total}条" + print(f"PASS: {pages}页共{len(seen)}条,无重复") + PY """ -from __future__ import annotations - -from datetime import datetime, timezone - -import pytest -from sqlalchemy import select - -from src.db.models import Match - - -class TestCursorPaginationDirection: - """验证游标条件方向与排序方向一致。""" - - def _build_query(self, status=None, cursor=None): - """复现 list_matches 的查询构造逻辑,返回 where 条件列表。""" - q = select(Match) - - if cursor: - last_date_str, last_id_str = cursor.split("|", 1) - last_date = datetime.fromisoformat(last_date_str) - last_id = int(last_id_str) - if status == "scheduled": - q = q.where( - (Match.match_date > last_date) | - ((Match.match_date == last_date) & (Match.id > last_id)) - ) - else: - q = q.where( - (Match.match_date < last_date) | - ((Match.match_date == last_date) & (Match.id < last_id)) - ) - - if status: - q = q.where(Match.match_status == status) - - if status == "scheduled": - order = (Match.match_date.asc(), Match.id.asc()) - else: - order = (Match.match_date.desc(), Match.id.desc()) - - return q.order_by(*order) - - def test_scheduled_uses_greater_than(self): - """scheduled + cursor: 应使用 > 条件(ASC 方向)。""" - q = self._build_query( - status="scheduled", - cursor="2026-01-15T15:00:00|100" - ) - sql = str(q) - assert ">" in sql, f"scheduled 游标应使用 >,SQL: {sql}" - assert "<" not in sql or "match_date <" not in sql, f"不应出现 < 条件" - - def test_other_status_uses_less_than(self): - """finished + cursor: 应使用 < 条件(DESC 方向)。""" - q = self._build_query( - status="finished", - cursor="2026-01-15T15:00:00|100" - ) - sql = str(q) - assert "<" in sql, f"finished 游标应使用 <,SQL: {sql}" - assert "match_date >" not in sql, f"不应出现 > 条件" - - def test_no_cursor_no_direction(self): - """无 cursor 时不应有游标条件。""" - q = self._build_query(status="scheduled", cursor=None) - sql = str(q) - # 应无 match_date 比较条件(只有 status filter) - assert "match_date >" not in sql - assert "match_date <" not in sql - - def test_scheduled_order_is_asc(self): - """scheduled 排序应为 ASC。""" - q = self._build_query(status="scheduled", cursor=None) - sql = str(q) - assert "ASC" in sql, f"scheduled 应 ASC 排序,SQL: {sql}" - assert "DESC" not in sql, f"不应出现 DESC" - - def test_finished_order_is_desc(self): - """finished 排序应为 DESC。""" - q = self._build_query(status="finished", cursor=None) - sql = str(q) - assert "DESC" in sql, f"finished 应 DESC 排序,SQL: {sql}" diff --git a/tests/test_security_check.py b/tests/test_security_check.py new file mode 100644 index 0000000..162890e --- /dev/null +++ b/tests/test_security_check.py @@ -0,0 +1,128 @@ +"""单测:生产环境启动安全校验。 + +覆盖: + - production 缺 SECRET_KEY → 阻断(sys.exit) + - production 缺鉴权 → 阻断 + - production 全配置 → 通过 + - development 缺配置 → 仅警告(不退出) + - DATABASE_URL 弱密码 → production 阻断 / development 仅警告 +""" +from __future__ import annotations + +import asyncio +import logging +from unittest.mock import AsyncMock, patch + +import pytest + +from src.core.config import Settings +from src.core.security_check import ( + _WEAK_SECRET_KEYS, + _WEAK_DB_PATTERNS, + assert_security_on_startup, + validate_security, +) + + +def _base_settings(**overrides) -> Settings: + """构造测试用 Settings,默认模拟一个"已合规"的基线。""" + defaults = dict( + APP_ENV="production", + SECRET_KEY="aSwLuw2mqoQdSKUfB3eFVfW2Tv7VnJRRixMxOwZZi5M=", + ADMIN_PASSWORD="", + ADMIN_API_KEY="", + DATABASE_URL="postgresql+asyncpg://user:StrongP@ssw0rd@db:5432/prod", + REQUIRE_ADMIN_AUTH=False, + ) + defaults.update(overrides) + return Settings(**defaults) + + +class TestValidateSecurity: + """validate_security 逻辑。""" + + @pytest.mark.asyncio + async def test_production_fully_configured_passes(self): + with patch("src.core.security_check.settings", _base_settings()), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + result = await validate_security() + assert result["ok"] is True + assert result["errors"] == [] + + @pytest.mark.asyncio + async def test_production_missing_secret_key_fails(self): + with patch("src.core.security_check.settings", _base_settings(SECRET_KEY="")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + result = await validate_security() + assert result["ok"] is False + assert any("SECRET_KEY" in e for e in result["errors"]) + + @pytest.mark.asyncio + async def test_production_weak_secret_key_fails(self): + with patch("src.core.security_check.settings", _base_settings(SECRET_KEY="changeme")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + result = await validate_security() + assert result["ok"] is False + assert any("SECRET_KEY" in e for e in result["errors"]) + + @pytest.mark.asyncio + async def test_production_missing_auth_fails(self): + with patch("src.core.security_check.settings", _base_settings()), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=False)): + result = await validate_security() + assert result["ok"] is False + assert any("鉴权" in e or "ADMIN" in e for e in result["errors"]) + + @pytest.mark.asyncio + async def test_production_weak_db_password_fails(self): + with patch("src.core.security_check.settings", + _base_settings(DATABASE_URL="postgresql+asyncpg://football:football@localhost:5432/football")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + result = await validate_security() + assert result["ok"] is False + assert any("DATABASE_URL" in e or "弱密码" in e for e in result["errors"]) + + @pytest.mark.asyncio + async def test_development_missing_config_only_warns(self): + """development:缺 SECRET_KEY/鉴权 → errors 存在但弱 DB 密码不进 errors。""" + with patch("src.core.security_check.settings", + _base_settings(APP_ENV="development", SECRET_KEY="", ADMIN_API_KEY="", + DATABASE_URL="postgresql+asyncpg://football:football@localhost:5432/football")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=False)): + result = await validate_security() + # development 下:弱 DB 密码只在 warnings,不会升级到 errors + assert result["ok"] is False # 仍有 errors(缺密钥 + 缺鉴权) + assert any("DATABASE_URL" in w for w in result["warnings"]) + + +class TestAssertSecurityOnStartup: + """assert_security_on_startup 退出行为。""" + + @pytest.mark.asyncio + async def test_production_failure_exits(self): + with patch("src.core.security_check.settings", _base_settings(SECRET_KEY="")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + with pytest.raises(SystemExit) as exc: + await assert_security_on_startup() + assert exc.value.code == 1 + + @pytest.mark.asyncio + async def test_production_pass_does_not_exit(self): + with patch("src.core.security_check.settings", _base_settings()), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + # 不应抛异常 / 退出 + await assert_security_on_startup() + + @pytest.mark.asyncio + async def test_development_failure_does_not_exit(self): + with patch("src.core.security_check.settings", + _base_settings(APP_ENV="development", SECRET_KEY="")), \ + patch("src.core.security_check._auth_configured", AsyncMock(return_value=True)): + # development 即使有问题也不退出 + await assert_security_on_startup() + + +def test_weak_secret_keys_list_not_empty(): + """防御性:弱密钥表应包含常见弱值。""" + assert "changeme" in _WEAK_SECRET_KEYS + assert "football:football@" in _WEAK_DB_PATTERNS