refactor(dashboard): switch a NiceGUI, rimuovi legacy Streamlit

NiceGUI è la dashboard ufficiale (port 8080, dark/neon palette, 3 pagine:
/, /convergence, /genomes). La porta è ora parametrica via env
SWARM_DASHBOARD_PORT, letta in ui.run() — Docker la usa anche per
healthcheck e label Traefik.

docker-compose.yml: entrypoint del servizio dashboard cambiato da
streamlit a python -m multi_swarm.dashboard.nicegui_app. Default porta
8080 ovunque (.env, .env.example, compose, healthcheck).

Rimossi i file legacy della vecchia GUI Streamlit:
- src/multi_swarm/dashboard/streamlit_app.py
- src/multi_swarm/dashboard/aquarium.py (helper canvas HTML5)
- src/multi_swarm/dashboard/pages/{01_overview,02_ga_convergence,
  03_genomes,04_aquarium}.py
- tests/integration/test_streamlit_smoke.py

pyproject.toml: rimossa la dep streamlit; uv.lock rigenerato (10 deps
transitive eliminate: pydeck, watchdog, jsonschema, pillow, ecc.).
README aggiornato (architettura, comando dashboard, sezione Dashboard
ora descrive NiceGUI con riferimento al deploy Docker via Traefik).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-14 12:15:11 +00:00
parent 8e5efde219
commit 03f723f7fc
13 changed files with 24 additions and 1569 deletions
+2 -2
View File
@@ -26,8 +26,8 @@ DB_PATH=./runs.db
# Docker / Traefik (usati SOLO da docker-compose.yml) # Docker / Traefik (usati SOLO da docker-compose.yml)
# Dominio base: traefik espone la dashboard su swarm.${DOMAIN_NAME} # Dominio base: traefik espone la dashboard su swarm.${DOMAIN_NAME}
DOMAIN_NAME=tielogic.xyz DOMAIN_NAME=tielogic.xyz
# Porta interna della Streamlit dashboard (Traefik fa il TLS davanti) # Porta interna della NiceGUI dashboard (Traefik fa il TLS davanti)
SWARM_DASHBOARD_PORT=8501 SWARM_DASHBOARD_PORT=8080
# Paper-trading runner — override del command nel compose (opzionali) # Paper-trading runner — override del command nel compose (opzionali)
PAPER_RUN_NAME=phase3-papertrade-prod PAPER_RUN_NAME=phase3-papertrade-prod
+10 -15
View File
@@ -75,17 +75,11 @@ src/multi_swarm/
├── orchestrator/ ├── orchestrator/
│ └── run.py End-to-end pipeline + persistence │ └── run.py End-to-end pipeline + persistence
└── dashboard/ └── dashboard/
├── streamlit_app.py Hub multipage ├── nicegui_app.py NiceGUI dashboard (overview / convergence / genomes)
── data.py Lettura runs.db per le pagine ── data.py Lettura runs.db per le pagine
├── aquarium.py Helper canvas HTML5 (fish data + JS template)
└── pages/
├── 01_overview.py Run + metriche aggregate
├── 02_ga_convergence.py Fitness convergence + entropy plot
├── 03_genomes.py Top-10 + ispezione system_prompt
└── 04_aquarium.py Acquario 2D con click → info + lineage
``` ```
Stack: Python 3.13, uv, pytest+pytest-mock+responses, openai SDK (verso OpenRouter), requests+tenacity, pandas+numpy+scipy, sqlmodel+sqlite, streamlit+plotly. Stack: Python 3.13, uv, pytest+pytest-mock+responses, openai SDK (verso OpenRouter), requests+tenacity, pandas+numpy+scipy, sqlmodel+sqlite, nicegui+plotly.
## Setup ## Setup
@@ -150,17 +144,18 @@ uv run python scripts/run_phase1.py \
--population-size 20 --n-generations 10 --population-size 20 --n-generations 10
# Dashboard # Dashboard
DB_PATH=./runs.db uv run streamlit run src/multi_swarm/dashboard/streamlit_app.py DB_PATH=./runs.db uv run python -m multi_swarm.dashboard.nicegui_app
``` ```
## Dashboard ## Dashboard
Streamlit multipage su `http://localhost:8501` (override con `--server.port`): NiceGUI dashboard (dark/neon palette) su `http://localhost:8080` (override con env `SWARM_DASHBOARD_PORT`):
- **Overview**: lista runs, status, costo, metriche aggregate evaluations (parse success %, top fitness, median). - **Overview** (`/`): lista runs, status, costo, metriche aggregate evaluations (parse success %, top fitness, median).
- **GA Convergence**: fitness median/max/p90 per generazione, entropy con hline a soglia gate (0.5). - **GA Convergence** (`/convergence`): fitness median/max/p90 per generazione, entropy con hline a soglia gate (0.5).
- **Genomes**: top-10 ordinati per fitness, click su row per ispezione system_prompt + raw_text JSON strategy. - **Genomes** (`/genomes`): top-K ordinati per fitness, click su riga per ispezione system_prompt + raw_text JSON strategy.
- **Aquarium**: visualizzazione 2D canvas HTML5 con un pesce per agente; dimensione ∝ fitness, colore per cognitive_style, halo sui top-3, click su pesce → panel info completo + lineage BFS (parents → grandparents → ...).
In produzione gira dentro Docker dietro Traefik su `https://swarm.${DOMAIN_NAME}` — vedi `docker-compose.yml`.
## Costi tipici Phase 1 ## Costi tipici Phase 1
+5 -9
View File
@@ -81,20 +81,16 @@ services:
- ./data:/app/data:ro - ./data:/app/data:ro
- ./series:/app/series:ro - ./series:/app/series:ro
entrypoint: entrypoint:
- streamlit - python
- run - -m
- /app/src/multi_swarm/dashboard/streamlit_app.py - multi_swarm.dashboard.nicegui_app
- --server.address=0.0.0.0
- --server.port=${SWARM_DASHBOARD_PORT:-8501}
- --server.headless=true
- --browser.gatherUsageStats=false
command: [] command: []
healthcheck: healthcheck:
test: test:
- "CMD" - "CMD"
- "python" - "python"
- "-c" - "-c"
- "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"SWARM_DASHBOARD_PORT\",\"8501\")}/_stcore/health', timeout=3).close()" - "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"SWARM_DASHBOARD_PORT\",\"8080\")}/', timeout=3).close()"
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
@@ -106,5 +102,5 @@ services:
- traefik.http.routers.multi-swarm-dashboard.tls=true - traefik.http.routers.multi-swarm-dashboard.tls=true
- traefik.http.routers.multi-swarm-dashboard.entrypoints=websecure - traefik.http.routers.multi-swarm-dashboard.entrypoints=websecure
- traefik.http.routers.multi-swarm-dashboard.tls.certresolver=mytlschallenge - traefik.http.routers.multi-swarm-dashboard.tls.certresolver=mytlschallenge
- "traefik.http.services.multi-swarm-dashboard.loadbalancer.server.port=${SWARM_DASHBOARD_PORT:-8501}" - "traefik.http.services.multi-swarm-dashboard.loadbalancer.server.port=${SWARM_DASHBOARD_PORT:-8080}"
- com.centurylinklabs.watchtower.enable=true - com.centurylinklabs.watchtower.enable=true
-1
View File
@@ -16,7 +16,6 @@ dependencies = [
"requests>=2.32", "requests>=2.32",
"tenacity>=9.0", "tenacity>=9.0",
"pyyaml>=6.0", "pyyaml>=6.0",
"streamlit>=1.40",
"plotly>=5.24", "plotly>=5.24",
"pyarrow>=18.0", "pyarrow>=18.0",
"nicegui>=3.11.1", "nicegui>=3.11.1",
-590
View File
@@ -1,590 +0,0 @@
"""Aquarium 2D visualization helpers.
Builds fish records (with full genome attributes + ancestor lineage) and
renders a self-contained HTML/JS canvas animation, embeddable in Streamlit
via ``streamlit.components.v1.html``. Includes a click handler that opens
an info panel showing genome details and BFS ancestor levels.
"""
from __future__ import annotations
import json
import math
from typing import Any
import pandas as pd # type: ignore[import-untyped]
# Color palette per cognitive style. Default fallback for unknown styles is grey.
STYLE_COLORS: dict[str, str] = {
"physicist": "#4cc9f0",
"biologist": "#52b788",
"historian": "#e76f51",
"meteorologist": "#ffd166",
"ecologist": "#a78bfa",
"engineer": "#fb6f92",
}
DEFAULT_COLOR: str = "#94a3b8"
def _is_nan(v: Any) -> bool:
try:
return bool(pd.isna(v))
except (TypeError, ValueError):
return False
def _safe_float(v: Any, default: float = 0.0) -> float:
if v is None or _is_nan(v):
return default
try:
return float(v)
except (TypeError, ValueError):
return default
def _safe_int(v: Any, default: int = 0) -> int:
if v is None or _is_nan(v):
return default
try:
return int(v)
except (TypeError, ValueError):
return default
def _safe_str(v: Any, default: str = "") -> str:
if v is None or _is_nan(v):
return default
return str(v)
def _safe_list(v: Any) -> list[Any]:
if v is None:
return []
if isinstance(v, list):
return list(v)
# pandas may store python lists in object cells; if it's e.g. a numpy array,
# falling back to list() is fine. NaN scalar is excluded by _is_nan.
if _is_nan(v):
return []
try:
return list(v)
except TypeError:
return []
def build_lineage_index(
genomes_df: pd.DataFrame, evals_df: pd.DataFrame
) -> dict[str, dict[str, Any]]:
"""Build ``{genome_id: attrs}`` for every genome in the run.
``genomes_df`` must come from ``genomes_df(repo, run_id)`` (no gen filter):
columns include ``id``, ``generation_idx``, ``system_prompt``,
``feature_access``, ``temperature``, ``top_p``, ``model_tier``,
``lookback_window``, ``cognitive_style``, ``parent_ids``, ``generation``.
``evals_df`` must come from ``evaluations_df(repo, run_id)``: columns
include ``genome_id``, ``fitness``, ``dsr``, ``sharpe``, ``max_dd``,
``n_trades``.
"""
if genomes_df.empty:
return {}
if evals_df is None or evals_df.empty:
merged = genomes_df.copy()
for col in ("fitness", "dsr", "sharpe", "max_dd", "n_trades"):
if col not in merged.columns:
merged[col] = 0.0 if col != "n_trades" else 0
else:
merged = genomes_df.merge(
evals_df,
left_on="id",
right_on="genome_id",
how="left",
suffixes=("", "_eval"),
)
index: dict[str, dict[str, Any]] = {}
for _, row in merged.iterrows():
gid = _safe_str(row.get("id"))
if not gid:
continue
# ``generation`` is the genome's evolutionary generation (from payload).
# If absent, fall back to ``generation_idx`` (column added by the
# repository). Defensive: both may be missing in edge cases.
gen_val: Any = row.get("generation")
if gen_val is None or _is_nan(gen_val):
gen_val = row.get("generation_idx", 0)
index[gid] = {
"id": gid,
"generation": _safe_int(gen_val, 0),
"fitness": _safe_float(row.get("fitness"), 0.0),
"dsr": _safe_float(row.get("dsr"), 0.0),
"sharpe": _safe_float(row.get("sharpe"), 0.0),
"max_dd": _safe_float(row.get("max_dd"), 0.0),
"n_trades": _safe_int(row.get("n_trades"), 0),
"cognitive_style": _safe_str(row.get("cognitive_style"), ""),
"system_prompt": _safe_str(row.get("system_prompt"), ""),
"temperature": _safe_float(row.get("temperature"), 0.0),
"lookback_window": _safe_int(row.get("lookback_window"), 0),
"feature_access": _safe_list(row.get("feature_access")),
"model_tier": _safe_str(row.get("model_tier"), ""),
"parent_ids": _safe_list(row.get("parent_ids")),
}
return index
def trace_ancestors(
genome_id: str,
lineage_index: dict[str, dict[str, Any]],
max_levels: int = 5,
) -> list[list[dict[str, Any]]]:
"""BFS over ``parent_ids`` returning levels of ancestors.
``levels[0]`` = direct parents, ``levels[1]`` = grandparents, etc. Each
entry is a small dict (no ``system_prompt``, to keep JSON payload light):
``{id, generation, fitness, cognitive_style}``. Cycles are guarded via a
``seen`` set; missing parents (not in this run) are stubbed with sentinel
values so the lineage display still renders the relationship.
"""
levels: list[list[dict[str, Any]]] = []
root = lineage_index.get(genome_id, {})
current_ids: list[str] = list(root.get("parent_ids", []))
seen: set[str] = {genome_id}
for _ in range(max_levels):
if not current_ids:
break
level_entries: list[dict[str, Any]] = []
next_ids: list[str] = []
for pid in current_ids:
if pid in seen:
continue
seen.add(pid)
entry = lineage_index.get(pid)
if entry is None:
level_entries.append(
{
"id": pid,
"generation": -1,
"fitness": 0.0,
"cognitive_style": "",
}
)
continue
level_entries.append(
{
"id": entry["id"],
"generation": entry["generation"],
"fitness": entry["fitness"],
"cognitive_style": entry["cognitive_style"],
}
)
next_ids.extend(entry.get("parent_ids", []))
if not level_entries:
break
levels.append(level_entries)
current_ids = next_ids
return levels
def build_fish_dataset(
active_df: pd.DataFrame,
lineage_index: dict[str, dict[str, Any]] | None = None,
max_lineage_levels: int = 5,
) -> list[dict[str, Any]]:
"""Build full fish records for each active genome.
For every row in ``active_df`` the matching entry in ``lineage_index`` is
looked up by ``genome_id`` (or ``id``) and attached together with the BFS
ancestor levels. Rows whose id is not in the index are skipped.
Backward-compat: if ``lineage_index`` is ``None`` (legacy call site, e.g.
test fixtures with simple merged DataFrames) we synthesize a minimal
lineage from ``active_df`` itself so the function still returns useful
fish records.
"""
if active_df.empty:
return []
if lineage_index is None:
# Legacy path: build a tiny index from the active df only.
synth: dict[str, dict[str, Any]] = {}
for _, row in active_df.iterrows():
gid = _safe_str(row.get("genome_id") or row.get("id"))
if not gid:
continue
fitness_val = _safe_float(row.get("fitness"), float("nan"))
if math.isnan(fitness_val):
continue
synth[gid] = {
"id": gid,
"generation": _safe_int(row.get("generation"), 0),
"fitness": fitness_val,
"dsr": _safe_float(row.get("dsr"), 0.0),
"sharpe": _safe_float(row.get("sharpe"), 0.0),
"max_dd": _safe_float(row.get("max_dd"), 0.0),
"n_trades": _safe_int(row.get("n_trades"), 0),
"cognitive_style": _safe_str(row.get("cognitive_style"), "unknown"),
"system_prompt": _safe_str(row.get("system_prompt"), ""),
"temperature": _safe_float(row.get("temperature"), 0.0),
"lookback_window": _safe_int(row.get("lookback_window"), 0),
"feature_access": _safe_list(row.get("feature_access")),
"model_tier": _safe_str(row.get("model_tier"), ""),
"parent_ids": _safe_list(row.get("parent_ids")),
}
lineage_index = synth
fish: list[dict[str, Any]] = []
for _, row in active_df.iterrows():
gid = _safe_str(row.get("genome_id") or row.get("id"))
if not gid:
continue
attrs = lineage_index.get(gid)
if attrs is None:
continue
if math.isnan(attrs.get("fitness", 0.0)):
continue
ancestors = trace_ancestors(gid, lineage_index, max_lineage_levels)
record = {**attrs, "ancestors": ancestors}
fish.append(record)
return fish
def build_aquarium_html(
fish: list[dict[str, Any]],
canvas_w: int = 1000,
canvas_h: int = 600,
) -> str:
"""Build the self-contained HTML/JS string for the aquarium canvas.
The output embeds a click handler: tapping a fish opens an info panel
(top-right of the canvas) showing its genome attributes and BFS ancestor
levels. Labels are no longer rendered on the canvas itself.
"""
fish_json = json.dumps(fish)
palette_json = json.dumps(STYLE_COLORS)
default_color = DEFAULT_COLOR
# All braces inside <style>/<script> are escaped to literals using {{ }}
# so we can use Python f-string substitution for the few JSON payloads.
return f"""
<div style="position:relative;width:100%;height:{canvas_h}px;">
<canvas id="aquarium" width="{canvas_w}" height="{canvas_h}"
style="width:100%;height:{canvas_h}px;border-radius:12px;
background:linear-gradient(180deg,#0a2540 0%,#1a4d80 100%);
display:block;cursor:pointer;"></canvas>
<div id="fish-info-panel" style="
position:absolute;
top:12px;
right:12px;
width:340px;
max-height:580px;
overflow-y:auto;
background:rgba(8,16,32,0.92);
color:#e2e8f0;
border-radius:10px;
padding:14px 16px;
font-family:system-ui,-apple-system,sans-serif;
font-size:12px;
line-height:1.5;
border:1px solid rgba(255,255,255,0.1);
backdrop-filter:blur(6px);
-webkit-backdrop-filter:blur(6px);
display:none;
z-index:10;
">
<div id="fish-info-content"></div>
<button id="fish-info-close" style="
position:absolute;top:8px;right:10px;
background:transparent;color:#94a3b8;border:none;
cursor:pointer;font-size:16px;
">&times;</button>
</div>
</div>
<script>
(function() {{
const FISH_DATA = {fish_json};
const STYLE_COLORS = {palette_json};
const DEFAULT_COLOR = {json.dumps(default_color)};
const canvas = document.getElementById('aquarium');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
const panel = document.getElementById('fish-info-panel');
const panelContent = document.getElementById('fish-info-content');
const closeBtn = document.getElementById('fish-info-close');
if (closeBtn) {{
closeBtn.addEventListener('click', function() {{
panel.style.display = 'none';
}});
}}
// Normalize fitness for sizing.
let maxFit = 0.0;
for (const f of FISH_DATA) {{
if (typeof f.fitness === 'number' && f.fitness > maxFit) maxFit = f.fitness;
}}
function lerp(a, b, t) {{ return a + (b - a) * t; }}
function radiusFor(fitness) {{
if (maxFit <= 0) return 8;
const t = Math.max(0.05, Math.min(1.0, fitness / maxFit));
return lerp(8, 40, t);
}}
function colorFor(style) {{
return STYLE_COLORS[style] || DEFAULT_COLOR;
}}
// Init fish state. Each entry keeps a reference to the original data dict
// so the click handler can show full attributes + ancestors.
const fishState = FISH_DATA.map(function(f, idx) {{
const r = radiusFor(f.fitness);
return {{
data: f,
color: colorFor(f.cognitive_style),
radius: r,
x: Math.random() * (W - 2 * r) + r,
y: Math.random() * (H - 2 * r) + r,
vx: (Math.random() - 0.5) * 1.5,
vy: (Math.random() - 0.5) * 1.0,
rank: idx,
}};
}});
// Bubbles for ambience.
const N_BUBBLES = 25;
const bubbles = Array.from({{length: N_BUBBLES}}, function() {{ return {{
x: Math.random() * W,
y: Math.random() * H,
r: 1 + Math.random() * 3,
vy: 0.3 + Math.random() * 0.7,
}}; }});
function drawBubble(b) {{
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.fill();
}}
function updateBubble(b) {{
b.y -= b.vy;
if (b.y < -10) {{
b.y = H + 5;
b.x = Math.random() * W;
}}
}}
function drawFish(f) {{
const facingLeft = f.vx < 0;
ctx.save();
ctx.translate(f.x, f.y);
if (facingLeft) ctx.scale(-1, 1);
// Halo for top-3 fish.
if (f.rank < 3) {{
const grad = ctx.createRadialGradient(0, 0, f.radius * 0.5, 0, 0, f.radius * 2.0);
grad.addColorStop(0, f.color + 'aa');
grad.addColorStop(1, f.color + '00');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, f.radius * 2.0, 0, Math.PI * 2);
ctx.fill();
}}
// Body (ellipse).
ctx.fillStyle = f.color;
ctx.beginPath();
ctx.ellipse(0, 0, f.radius, f.radius * 0.6, 0, 0, Math.PI * 2);
ctx.fill();
// Tail.
ctx.beginPath();
ctx.moveTo(-f.radius, 0);
ctx.lineTo(-f.radius * 1.6, -f.radius * 0.5);
ctx.lineTo(-f.radius * 1.6, f.radius * 0.5);
ctx.closePath();
ctx.fill();
// Eye.
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(f.radius * 0.45, -f.radius * 0.15, Math.max(1.5, f.radius * 0.12), 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(f.radius * 0.50, -f.radius * 0.15, Math.max(0.8, f.radius * 0.06), 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}}
function updateFish(f) {{
f.vx += (Math.random() - 0.5) * 0.05;
f.vy += (Math.random() - 0.5) * 0.05;
const speed = Math.hypot(f.vx, f.vy);
const maxSpeed = 1.5;
if (speed > maxSpeed) {{
f.vx = (f.vx / speed) * maxSpeed;
f.vy = (f.vy / speed) * maxSpeed;
}}
f.x += f.vx;
f.y += f.vy;
if (f.x < f.radius) {{ f.x = f.radius; f.vx = -f.vx; }}
if (f.x > W - f.radius) {{ f.x = W - f.radius; f.vx = -f.vx; }}
if (f.y < f.radius) {{ f.y = f.radius; f.vy = -f.vy; }}
if (f.y > H - f.radius) {{ f.y = H - f.radius; f.vy = -f.vy; }}
}}
function frame() {{
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
ctx.lineWidth = 1;
for (let i = 0; i < 6; i++) {{
const y = (H / 6) * i + (Date.now() / 50 % (H / 6));
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
}}
for (const b of bubbles) {{
updateBubble(b);
drawBubble(b);
}}
for (const f of fishState) {{
updateFish(f);
drawFish(f);
}}
requestAnimationFrame(frame);
}}
// CLICK HANDLER: hit-test in canvas pixel space (account for CSS scaling).
canvas.addEventListener('click', function(e) {{
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const cx = (e.clientX - rect.left) * scaleX;
const cy = (e.clientY - rect.top) * scaleY;
let best = null;
let bestDist = Infinity;
for (const f of fishState) {{
const dx = cx - f.x;
const dy = cy - f.y;
const d = Math.sqrt(dx * dx + dy * dy);
const hit = Math.max(f.radius + 6, 14);
if (d < hit && d < bestDist) {{
best = f;
bestDist = d;
}}
}}
if (best) showFishInfo(best);
}});
const ROW_STYLE = 'display:flex;justify-content:space-between;'
+ 'padding:2px 0;border-bottom:1px solid rgba(255,255,255,0.05);';
const PROMPT_STYLE = 'margin-top:10px;padding:8px;'
+ 'background:rgba(255,255,255,0.04);border-radius:6px;'
+ 'font-size:11px;font-style:italic;color:#cbd5e1;';
const ANC_HEAD_STYLE = 'margin:14px 0 6px 0;color:#94a3b8;'
+ 'text-transform:uppercase;font-size:10px;letter-spacing:1px;';
const ANC_ROW_STYLE = 'display:flex;align-items:center;padding:4px 6px;'
+ 'margin-bottom:2px;background:rgba(255,255,255,0.03);'
+ 'border-radius:4px;border-left:3px solid ';
const NO_ANC_STYLE = 'margin-top:10px;font-size:10px;color:#64748b;';
const DASH = '\\u2014';
function metricRow(label, value) {{
return '<div style="' + ROW_STYLE + '">'
+ '<span style="color:#94a3b8;">' + label + '</span>'
+ '<span style="color:#e2e8f0;">' + value + '</span></div>';
}}
function escapeHtml(s) {{
const div = document.createElement('div');
div.appendChild(document.createTextNode(String(s)));
return div.innerHTML;
}}
function fmt(v, dp) {{
if (v === null || v === undefined || typeof v !== 'number' || isNaN(v)) {{
return DASH;
}}
return v.toFixed(dp);
}}
function showFishInfo(fish) {{
const data = fish.data;
const styleColor = STYLE_COLORS[data.cognitive_style] || DEFAULT_COLOR;
let html = '';
const idShort = String(data.id || '').slice(0, 8);
html += '<h4 style="margin:0 0 10px 0;color:' + styleColor + ';">';
html += escapeHtml(idShort)
+ ' <span style="color:#94a3b8;font-weight:normal;font-size:11px;">'
+ 'gen ' + escapeHtml(data.generation) + '</span>';
html += '</h4>';
html += metricRow('fitness', fmt(data.fitness, 3));
html += metricRow('DSR', fmt(data.dsr, 3));
html += metricRow('Sharpe', fmt(data.sharpe, 3));
html += metricRow('max DD', fmt(data.max_dd, 3));
const trades = data.n_trades == null ? 0 : data.n_trades;
html += metricRow('trades', escapeHtml(trades));
html += metricRow('style', escapeHtml(data.cognitive_style || DASH));
html += metricRow('tier', escapeHtml(data.model_tier || DASH));
html += metricRow('temp', fmt(data.temperature, 2));
const lookback = data.lookback_window == null ? DASH : data.lookback_window;
html += metricRow('lookback', escapeHtml(lookback));
const feats = (data.feature_access || []).join(', ');
html += metricRow('features', escapeHtml(feats || DASH));
if (data.system_prompt) {{
html += '<div style="' + PROMPT_STYLE + '">'
+ escapeHtml(data.system_prompt) + '</div>';
}}
if (data.ancestors && data.ancestors.length > 0) {{
html += '<h5 style="' + ANC_HEAD_STYLE + '">Discendenza</h5>';
data.ancestors.forEach(function(level, idx) {{
html += '<div style="margin-bottom:8px;">';
html += '<div style="font-size:10px;color:#64748b;margin-bottom:4px;">'
+ 'Gen N\\u2212' + (idx + 1) + '</div>';
level.forEach(function(ancestor) {{
const c = STYLE_COLORS[ancestor.cognitive_style] || DEFAULT_COLOR;
const aShort = String(ancestor.id || '').slice(0, 8);
html += '<div style="' + ANC_ROW_STYLE + c + ';">';
html += '<code style="color:' + c + ';font-size:10px;">'
+ escapeHtml(aShort) + '</code>';
const af = ancestor.fitness;
const fitTxt = (typeof af === 'number' && !isNaN(af))
? af.toFixed(2) : DASH;
html += '<span style="margin-left:auto;color:#94a3b8;font-size:10px;">'
+ 'fit ' + fitTxt + '</span>';
html += '</div>';
}});
html += '</div>';
}});
}} else {{
html += '<div style="' + NO_ANC_STYLE + '">'
+ 'Genoma di prima generazione (no ancestors)</div>';
}}
panelContent.innerHTML = html;
panel.style.display = 'block';
}}
if (fishState.length === 0) {{
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Acquario vuoto: nessun genoma da mostrare.', W / 2, H / 2);
}} else {{
requestAnimationFrame(frame);
}}
}})();
</script>
"""
+1 -1
View File
@@ -868,7 +868,7 @@ def main() -> None:
app.on_startup(lambda: print(f"DB: {Path(DB_PATH).resolve()}")) app.on_startup(lambda: print(f"DB: {Path(DB_PATH).resolve()}"))
ui.run( ui.run(
host="0.0.0.0", host="0.0.0.0",
port=8080, port=int(os.environ.get("SWARM_DASHBOARD_PORT", "8080")),
title="Multi-Swarm Dashboard", title="Multi-Swarm Dashboard",
reload=False, reload=False,
show=False, show=False,
@@ -1,84 +0,0 @@
from __future__ import annotations
from datetime import datetime
import streamlit as st
from multi_swarm.dashboard.data import (
evaluations_df,
generations_df,
get_repo,
get_run_overview,
list_runs_df,
)
st.title("Overview")
db_path = st.session_state.get("db_path", "./runs.db")
repo = get_repo(db_path)
runs = list_runs_df(repo)
if runs.empty:
st.info("Nessuna run nel database. Esegui `scripts/run_phase1.py` per generarne una.")
st.stop()
st.subheader("Tutte le run")
st.dataframe(runs[["id", "name", "started_at", "completed_at", "status", "total_cost_usd"]])
selected = st.selectbox("Seleziona run per dettaglio", runs["id"].tolist())
overview = get_run_overview(repo, selected)
# --- Progress live ---
cfg = overview["config"]
pop_size = int(cfg.get("population_size", 0))
n_gens = int(cfg.get("n_generations", 0))
evals = evaluations_df(repo, selected)
gens = generations_df(repo, selected)
evals_done = len(evals)
evals_total = max(pop_size * n_gens, 1)
gens_done = int(gens["completed_at"].notna().sum()) if not gens.empty else 0
status_emoji = {"running": "🟢", "completed": "", "failed": ""}.get(overview["status"], "")
top_fit = float(evals["fitness"].max()) if not evals.empty else float("nan")
st.subheader(f"{status_emoji} Progresso run")
st.progress(
min(gens_done / max(n_gens, 1), 1.0),
text=f"Generations: {gens_done}/{n_gens}",
)
st.progress(
min(evals_done / evals_total, 1.0),
text=f"Evaluations: {evals_done}/{evals_total} ({100*evals_done/evals_total:.1f}%)",
)
pcol1, pcol2, pcol3 = st.columns(3)
pcol1.metric("Top fitness", f"{top_fit:.4f}" if evals_done else "")
pcol2.metric("Median fitness", f"{evals['fitness'].median():.4f}" if evals_done else "")
pcol3.metric("Cost so far", f"${overview['total_cost_usd']:.4f}")
ref_col1, ref_col2 = st.columns([1, 4])
if ref_col1.button("🔄 Refresh"):
st.rerun()
ref_col2.caption(f"Last update: {datetime.now().strftime('%H:%M:%S')}")
st.divider()
col1, col2, col3, col4 = st.columns(4)
col1.metric("Status", overview["status"])
col2.metric("Cost (USD)", f"{overview['total_cost_usd']:.4f}")
col3.metric("Started", overview["started_at"])
col4.metric("Completed", overview["completed_at"] or "")
st.subheader("Statistiche evaluations")
col5, col6, col7, col8 = st.columns(4)
if not evals.empty:
parse_success = 100 * (evals["parse_error"].isna().sum() / len(evals))
col5.metric("Evaluations totali", len(evals))
col6.metric("Parse success %", f"{parse_success:.1f}%")
col7.metric("Top fitness", f"{evals['fitness'].max():.3f}")
col8.metric("Median fitness", f"{evals['fitness'].median():.3f}")
else:
col5.metric("Evaluations totali", 0)
st.subheader("Config")
st.json(overview["config"])
@@ -1,68 +0,0 @@
from __future__ import annotations
import plotly.graph_objects as go # type: ignore[import-untyped]
import streamlit as st
from multi_swarm.dashboard.data import generations_df, get_repo, list_runs_df
st.title("GA Convergence")
db_path = st.session_state.get("db_path", "./runs.db")
repo = get_repo(db_path)
runs = list_runs_df(repo)
if runs.empty:
st.info("Nessuna run.")
st.stop()
selected = st.selectbox("Run", runs["id"].tolist())
gens = generations_df(repo, selected)
if gens.empty:
st.warning("Nessuna generazione registrata per questa run.")
st.stop()
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=gens["generation_idx"],
y=gens["fitness_median"],
name="median",
mode="lines+markers",
)
)
fig.add_trace(
go.Scatter(
x=gens["generation_idx"],
y=gens["fitness_max"],
name="max",
mode="lines+markers",
)
)
fig.add_trace(
go.Scatter(
x=gens["generation_idx"],
y=gens["fitness_p90"],
name="p90",
mode="lines+markers",
)
)
fig.update_layout(
xaxis_title="generation",
yaxis_title="fitness",
title="Fitness convergence",
)
st.plotly_chart(fig, use_container_width=True)
st.subheader("Entropy")
fig2 = go.Figure()
fig2.add_trace(go.Scatter(x=gens["generation_idx"], y=gens["entropy"], mode="lines+markers"))
fig2.add_hline(y=0.5, line_dash="dash", annotation_text="gate threshold (0.5)")
fig2.update_layout(
xaxis_title="generation",
yaxis_title="entropy",
title="Diversity (fitness entropy)",
)
st.plotly_chart(fig2, use_container_width=True)
st.subheader("Tabella generazioni")
st.dataframe(gens)
@@ -1,72 +0,0 @@
from __future__ import annotations
import streamlit as st
from multi_swarm.dashboard.data import (
evaluations_df,
genomes_df,
get_repo,
list_runs_df,
)
st.title("Genomes")
db_path = st.session_state.get("db_path", "./runs.db")
repo = get_repo(db_path)
runs = list_runs_df(repo)
if runs.empty:
st.info("Nessuna run.")
st.stop()
selected = st.selectbox("Run", runs["id"].tolist())
evals = evaluations_df(repo, selected)
genomes = genomes_df(repo, selected)
if evals.empty:
st.warning("Nessuna evaluation.")
st.stop()
merged = evals.merge(
genomes, left_on="genome_id", right_on="id", how="left", suffixes=("", "_g")
)
top = merged.sort_values("fitness", ascending=False).head(10)
st.subheader("Top-10 genomi (per fitness)")
display_cols = [
"genome_id",
"fitness",
"dsr",
"sharpe",
"max_dd",
"n_trades",
"cognitive_style",
"temperature",
"lookback_window",
"feature_access",
]
existing = [c for c in display_cols if c in top.columns]
st.dataframe(top[existing])
st.subheader("Ispezione genoma")
gid = st.selectbox("Seleziona genome_id", top["genome_id"].tolist())
row = merged[merged["genome_id"] == gid].iloc[0]
col1, col2 = st.columns(2)
with col1:
st.metric("fitness", f"{row['fitness']:.3f}")
st.metric("DSR", f"{row['dsr']:.3f}")
st.metric("Sharpe", f"{row['sharpe']:.3f}")
with col2:
st.metric("max DD", f"{row['max_dd']:.3f}")
st.metric("trades", int(row["n_trades"]))
st.metric("style", str(row.get("cognitive_style", "")))
st.subheader("System prompt")
st.code(row.get("system_prompt", ""))
st.subheader("Raw LLM output")
st.code(row.get("raw_text", ""))
if row.get("parse_error"):
st.error(f"Parse error: {row['parse_error']}")
@@ -1,87 +0,0 @@
from __future__ import annotations
import streamlit as st
import streamlit.components.v1 as components
from multi_swarm.dashboard.aquarium import (
STYLE_COLORS,
build_aquarium_html,
build_fish_dataset,
build_lineage_index,
)
from multi_swarm.dashboard.data import (
evaluations_df,
genomes_df,
get_repo,
list_runs_df,
)
st.title("Aquarium 2D")
st.caption(
"Pesci colorati per stile cognitivo, dimensione proporzionale a fitness. "
"Click su un pesce per dettaglio + discendenza."
)
db_path = st.session_state.get("db_path", "./runs.db")
repo = get_repo(db_path)
runs = list_runs_df(repo)
if runs.empty:
st.info("Nessuna run nel database.")
st.stop()
selected_run = st.selectbox("Run", runs["id"].tolist())
# Fetch ALL genomes of the run (no gen filter): needed to build the lineage
# index across generations. The active set is filtered afterwards.
all_genomes = genomes_df(repo, selected_run)
all_evals = evaluations_df(repo, selected_run)
if all_genomes.empty:
st.warning("Nessun genoma per questa run.")
st.stop()
available_gens = sorted(all_genomes["generation_idx"].unique().tolist())
selected_gen = st.selectbox(
"Generazione",
available_gens,
index=len(available_gens) - 1, # default ultima
)
active_genomes = all_genomes[all_genomes["generation_idx"] == selected_gen]
active_evals = (
all_evals[all_evals["genome_id"].isin(active_genomes["id"])]
if not all_evals.empty
else all_evals
)
if not active_evals.empty:
active_merged = active_genomes.merge(
active_evals,
left_on="id",
right_on="genome_id",
how="left",
suffixes=("", "_eval"),
)
else:
active_merged = active_genomes.copy()
active_merged["genome_id"] = active_merged["id"]
lineage = build_lineage_index(all_genomes, all_evals)
fish = build_fish_dataset(active_merged, lineage, max_lineage_levels=5)
if not fish:
st.warning("Nessun agente attivo in questa generazione.")
st.stop()
st.caption(f"{len(fish)} agenti in generazione {selected_gen}")
html_str = build_aquarium_html(fish, canvas_w=1000, canvas_h=600)
components.html(html_str, height=620, scrolling=False)
with st.expander("Legenda colori"):
legend_md = "\n".join(
f"- <span style='color:{color};font-weight:bold;'>&#9679;</span> "
f"`{style}`"
for style, color in STYLE_COLORS.items()
)
st.markdown(legend_md, unsafe_allow_html=True)
@@ -1,22 +0,0 @@
from __future__ import annotations
import os
from pathlib import Path
import streamlit as st
st.set_page_config(page_title="Multi-Swarm Phase 1", layout="wide")
st.title("Multi-Swarm Coevolutivo — Phase 1 dashboard")
st.markdown(
"""
Naviga le pagine nel menu a sinistra:
- **Overview**: ultima run e stato globale.
- **GA Convergence**: fitness per generazione.
- **Genomes**: top-K genomi e ispezione qualitativa.
- **Aquarium**: visualizzazione 2D dei genomi come pesci animati.
"""
)
db_path = os.environ.get("DB_PATH", "./runs.db")
st.session_state["db_path"] = db_path
st.caption(f"DB path: `{Path(db_path).resolve()}`")
-310
View File
@@ -1,310 +0,0 @@
import importlib
import pandas as pd
def test_streamlit_app_imports():
importlib.import_module("multi_swarm.dashboard.data")
def test_dashboard_data_helpers_signatures():
from multi_swarm.dashboard import data
assert hasattr(data, "list_runs_df")
assert hasattr(data, "generations_df")
assert hasattr(data, "evaluations_df")
assert hasattr(data, "genomes_df")
def test_aquarium_helper_builds_html_with_click_handler():
from multi_swarm.dashboard.aquarium import build_aquarium_html
fish = [
{
"id": "abc123",
"fitness": 0.8,
"cognitive_style": "physicist",
"n_trades": 30,
"dsr": 0.7,
"sharpe": 1.2,
"max_dd": 0.1,
"system_prompt": "test",
"temperature": 0.9,
"lookback_window": 200,
"feature_access": ["close"],
"model_tier": "C",
"generation": 1,
"parent_ids": [],
"ancestors": [],
}
]
html = build_aquarium_html(fish, canvas_w=800, canvas_h=400)
assert "canvas" in html
assert "abc123" in html # fish id present in JSON payload
assert "addEventListener('click'" in html
assert "fish-info-panel" in html
assert "showFishInfo" in html
assert "Discendenza" in html
assert "requestAnimationFrame" in html
def test_aquarium_build_fish_dataset_legacy_path():
from multi_swarm.dashboard.aquarium import build_fish_dataset
df = pd.DataFrame(
[
{
"genome_id": "low",
"fitness": 0.1,
"cognitive_style": "physicist",
"n_trades": 1,
"dsr": 0.0,
},
{
"genome_id": "high",
"fitness": 0.9,
"cognitive_style": "biologist",
"n_trades": 10,
"dsr": 0.5,
},
]
)
out = build_fish_dataset(df)
ids = {f["id"] for f in out}
assert ids == {"low", "high"}
high = next(f for f in out if f["id"] == "high")
assert high["cognitive_style"] == "biologist"
assert high["ancestors"] == []
def test_aquarium_build_fish_dataset_drops_nan_fitness():
from multi_swarm.dashboard.aquarium import build_fish_dataset
df = pd.DataFrame(
[
{
"genome_id": "ok",
"fitness": 0.4,
"cognitive_style": "historian",
"n_trades": 2,
"dsr": 0.1,
},
{
"genome_id": "bad",
"fitness": float("nan"),
"cognitive_style": "ecologist",
"n_trades": 0,
"dsr": 0.0,
},
]
)
out = build_fish_dataset(df)
assert len(out) == 1
assert out[0]["id"] == "ok"
def test_aquarium_empty_input_returns_empty():
from multi_swarm.dashboard.aquarium import build_aquarium_html, build_fish_dataset
assert build_fish_dataset(pd.DataFrame()) == []
html = build_aquarium_html([], canvas_w=400, canvas_h=200)
assert "canvas" in html
assert "Acquario vuoto" in html
def test_build_lineage_index_returns_dict_keyed_by_id():
from multi_swarm.dashboard.aquarium import build_lineage_index
genomes = pd.DataFrame(
[
{
"id": "g1",
"generation_idx": 0,
"generation": 0,
"system_prompt": "x",
"feature_access": ["close"],
"temperature": 0.9,
"top_p": 0.95,
"model_tier": "C",
"lookback_window": 100,
"cognitive_style": "physicist",
"parent_ids": [],
},
{
"id": "g2",
"generation_idx": 1,
"generation": 1,
"system_prompt": "y",
"feature_access": ["close", "volume"],
"temperature": 1.0,
"top_p": 0.95,
"model_tier": "C",
"lookback_window": 200,
"cognitive_style": "biologist",
"parent_ids": ["g1"],
},
]
)
evals = pd.DataFrame(
[
{
"genome_id": "g1",
"fitness": 0.5,
"dsr": 0.6,
"sharpe": 1.2,
"max_dd": 0.1,
"n_trades": 30,
"parse_error": None,
"raw_text": "",
},
{
"genome_id": "g2",
"fitness": 0.7,
"dsr": 0.8,
"sharpe": 1.5,
"max_dd": 0.05,
"n_trades": 40,
"parse_error": None,
"raw_text": "",
},
]
)
idx = build_lineage_index(genomes, evals)
assert "g1" in idx and "g2" in idx
assert idx["g2"]["parent_ids"] == ["g1"]
assert idx["g2"]["fitness"] == 0.7
assert idx["g1"]["cognitive_style"] == "physicist"
assert idx["g2"]["feature_access"] == ["close", "volume"]
def test_trace_ancestors_walks_levels():
from multi_swarm.dashboard.aquarium import trace_ancestors
idx = {
"child": {
"id": "child",
"parent_ids": ["p1", "p2"],
"fitness": 0.8,
"generation": 2,
"cognitive_style": "physicist",
},
"p1": {
"id": "p1",
"parent_ids": ["gp1"],
"fitness": 0.5,
"generation": 1,
"cognitive_style": "biologist",
},
"p2": {
"id": "p2",
"parent_ids": [],
"fitness": 0.3,
"generation": 1,
"cognitive_style": "engineer",
},
"gp1": {
"id": "gp1",
"parent_ids": [],
"fitness": 0.2,
"generation": 0,
"cognitive_style": "historian",
},
}
levels = trace_ancestors("child", idx, max_levels=5)
assert len(levels) == 2
assert {a["id"] for a in levels[0]} == {"p1", "p2"}
assert {a["id"] for a in levels[1]} == {"gp1"}
def test_trace_ancestors_handles_cycles():
from multi_swarm.dashboard.aquarium import trace_ancestors
# Pathological cycle: a <-> b. Should terminate cleanly.
idx = {
"a": {
"id": "a",
"parent_ids": ["b"],
"fitness": 0.1,
"generation": 1,
"cognitive_style": "physicist",
},
"b": {
"id": "b",
"parent_ids": ["a"],
"fitness": 0.2,
"generation": 0,
"cognitive_style": "biologist",
},
}
levels = trace_ancestors("a", idx, max_levels=5)
# a -> b at level 0; b's only parent is a, already seen -> stop.
assert len(levels) == 1
assert levels[0][0]["id"] == "b"
def test_trace_ancestors_no_parents_returns_empty():
from multi_swarm.dashboard.aquarium import trace_ancestors
idx = {
"solo": {
"id": "solo",
"parent_ids": [],
"fitness": 0.4,
"generation": 0,
"cognitive_style": "engineer",
},
}
assert trace_ancestors("solo", idx) == []
def test_build_fish_dataset_attaches_ancestors():
from multi_swarm.dashboard.aquarium import build_fish_dataset, build_lineage_index
genomes = pd.DataFrame(
[
{
"id": "p",
"generation_idx": 0,
"generation": 0,
"system_prompt": "p",
"feature_access": ["close"],
"temperature": 0.8,
"top_p": 0.9,
"model_tier": "C",
"lookback_window": 100,
"cognitive_style": "physicist",
"parent_ids": [],
},
{
"id": "c",
"generation_idx": 1,
"generation": 1,
"system_prompt": "c",
"feature_access": ["close"],
"temperature": 0.8,
"top_p": 0.9,
"model_tier": "C",
"lookback_window": 120,
"cognitive_style": "biologist",
"parent_ids": ["p"],
},
]
)
evals = pd.DataFrame(
[
{"genome_id": "p", "fitness": 0.3, "dsr": 0.0, "sharpe": 0.0,
"max_dd": 0.0, "n_trades": 0},
{"genome_id": "c", "fitness": 0.6, "dsr": 0.0, "sharpe": 0.0,
"max_dd": 0.0, "n_trades": 0},
]
)
lineage = build_lineage_index(genomes, evals)
active = genomes[genomes["generation_idx"] == 1].merge(
evals, left_on="id", right_on="genome_id", how="left"
)
fish = build_fish_dataset(active, lineage)
assert len(fish) == 1
assert fish[0]["id"] == "c"
assert len(fish[0]["ancestors"]) == 1
assert fish[0]["ancestors"][0][0]["id"] == "p"
Generated
+6 -308
View File
@@ -111,22 +111,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
] ]
[[package]]
name = "altair"
version = "6.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "jsonschema" },
{ name = "narwhals" },
{ name = "packaging" },
{ name = "typing-extensions", marker = "python_full_version < '3.15'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3a/1e/365a9144db3254f86f1b974660b9ede1e9a38c9dc0730e4a9b1192eec5d6/altair-6.1.0.tar.gz", hash = "sha256:dda699216cf85b040d968ae5a569ad45957616811e38760a85e5118269daca67", size = 765519, upload-time = "2026-04-21T13:08:46.44Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/63/5dacc8d8306c715088b897a479e551bc0779fd2f0f26c97fec5e36542b4e/altair-6.1.0-py3-none-any.whl", hash = "sha256:fdf5fd939512e5b2fc4441c82dfd2635e706defbd037db0ac429ef5ddce66c3b", size = 796996, upload-time = "2026-04-21T13:08:48.549Z" },
]
[[package]] [[package]]
name = "annotated-doc" name = "annotated-doc"
version = "0.0.4" version = "0.0.4"
@@ -226,24 +210,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" },
] ]
[[package]]
name = "blinker"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
]
[[package]]
name = "cachetools"
version = "7.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ff/e2/85f227594656000ff4d8adadae91a21f536d4a84c6c716a86bd6685874be/cachetools-7.1.1.tar.gz", hash = "sha256:27bdf856d68fd3c71c26c01b5edc312124ed427524d1ddb31aa2b7746fe20d4b", size = 40202, upload-time = "2026-05-03T20:00:29.391Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/0f/f897abe4ea0a8c408ae65c8c83bffab4936ad65d6032d4fb4cd35bbdc3ee/cachetools-7.1.1-py3-none-any.whl", hash = "sha256:0335cd7a0952d2b22327441fb0628139e234c565559eeb91a8a4ac7551c5353d", size = 16775, upload-time = "2026-05-03T20:00:27.857Z" },
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2026.4.22" version = "2026.4.22"
@@ -525,30 +491,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
] ]
[[package]]
name = "gitdb"
version = "4.0.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "smmap" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
]
[[package]]
name = "gitpython"
version = "3.1.50"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gitdb" },
]
sdist = { url = "https://files.pythonhosted.org/packages/33/f6/354ae6491228b5eb40e10d89c4d13c651fe1cf7556e35ebdded50cff57ce/gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc", size = 219798, upload-time = "2026-05-06T04:01:26.571Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" },
]
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.5.0" version = "3.5.0"
@@ -558,7 +500,9 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" },
{ url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" },
{ url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" },
{ url = "https://files.pythonhosted.org/packages/6a/15/a643b4ecd09969e30b8a150d5919960caae0abe4f5af75ab040b1ab85e78/greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d", size = 623234, upload-time = "2026-04-27T13:02:40.611Z" },
{ url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" },
{ url = "https://files.pythonhosted.org/packages/77/18/3b13d5ef1275b0ffaf933b05efa21408ac4ca95823c7411d79682e4fdcff/greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae", size = 425243, upload-time = "2026-04-27T13:05:15.689Z" },
{ url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" },
{ url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" },
{ url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" },
@@ -566,7 +510,9 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" },
{ url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" },
{ url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" },
{ url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086, upload-time = "2026-04-27T13:02:42.314Z" },
{ url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" },
{ url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799, upload-time = "2026-04-27T13:05:17.094Z" },
{ url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" },
{ url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" },
{ url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" },
@@ -574,7 +520,9 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" },
{ url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" },
{ url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" },
{ url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015, upload-time = "2026-04-27T13:02:43.973Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" },
{ url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275, upload-time = "2026-04-27T13:05:18.28Z" },
{ url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" },
{ url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" },
{ url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" },
@@ -741,33 +689,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" },
] ]
[[package]]
name = "jsonschema"
version = "4.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
]
[[package]] [[package]]
name = "librt" name = "librt"
version = "0.10.0" version = "0.10.0"
@@ -989,7 +910,6 @@ dependencies = [
{ name = "requests" }, { name = "requests" },
{ name = "scipy" }, { name = "scipy" },
{ name = "sqlmodel" }, { name = "sqlmodel" },
{ name = "streamlit" },
{ name = "tenacity" }, { name = "tenacity" },
{ name = "yfinance" }, { name = "yfinance" },
] ]
@@ -1020,7 +940,6 @@ requires-dist = [
{ name = "requests", specifier = ">=2.32" }, { name = "requests", specifier = ">=2.32" },
{ name = "scipy", specifier = ">=1.14" }, { name = "scipy", specifier = ">=1.14" },
{ name = "sqlmodel", specifier = ">=0.0.22" }, { name = "sqlmodel", specifier = ">=0.0.22" },
{ name = "streamlit", specifier = ">=1.40" },
{ name = "tenacity", specifier = ">=9.0" }, { name = "tenacity", specifier = ">=9.0" },
{ name = "yfinance", specifier = ">=1.3.0" }, { name = "yfinance", specifier = ">=1.3.0" },
] ]
@@ -1395,64 +1314,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/d4/2cf83b5fb7fae8939ed596009465c6de0b8fafa70abd486ed9d192e457f7/peewee-4.0.5-py3-none-any.whl", hash = "sha256:05bf687660f04f925bcb7d744e5af826ca526322f2fad894bfa4f1d0f3bcaf50", size = 144479, upload-time = "2026-04-23T21:17:28.061Z" }, { url = "https://files.pythonhosted.org/packages/ef/d4/2cf83b5fb7fae8939ed596009465c6de0b8fafa70abd486ed9d192e457f7/peewee-4.0.5-py3-none-any.whl", hash = "sha256:05bf687660f04f925bcb7d744e5af826ca526322f2fad894bfa4f1d0f3bcaf50", size = 144479, upload-time = "2026-04-23T21:17:28.061Z" },
] ]
[[package]]
name = "pillow"
version = "12.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
{ url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
{ url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
{ url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
{ url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
{ url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
{ url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
{ url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
{ url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
{ url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
{ url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
{ url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
{ url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
{ url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
{ url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
{ url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
{ url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
{ url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
{ url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
{ url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
{ url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
{ url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
{ url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.9.6" version = "4.9.6"
@@ -1706,19 +1567,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" },
] ]
[[package]]
name = "pydeck"
version = "0.9.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/df/4e9e7f20f8034a37c6571c93809f6d22388c39978c98d174d656c1a18fd2/pydeck-0.9.2.tar.gz", hash = "sha256:c10d9035e81ead6385264cac8d19402471f6866a15ca1f7df1400f52142bcf87", size = 5849672, upload-time = "2026-04-16T18:30:30.089Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/24/b30ee7d723100fd822de1bb4c0adea62f3419884a75a536f35f355d1e7c0/pydeck-0.9.2-py2.py3-none-any.whl", hash = "sha256:8213dfeacc5f6bfe6825f61c8ee34e3850e8a31fc43924379ec98edb34a75b25", size = 11305615, upload-time = "2026-04-16T18:30:28.133Z" },
]
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.20.0" version = "2.20.0"
@@ -1873,19 +1721,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
] ]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.33.1" version = "2.33.1"
@@ -1928,72 +1763,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
] ]
[[package]]
name = "rpds-py"
version = "0.30.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
{ url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
{ url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
{ url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
{ url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
{ url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
{ url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
{ url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
{ url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
{ url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
{ url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
{ url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
{ url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
{ url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
{ url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
{ url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
{ url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
{ url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
{ url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
{ url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
{ url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
{ url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
{ url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
{ url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
{ url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
{ url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
{ url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
{ url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
{ url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
{ url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
{ url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
{ url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
{ url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
{ url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
{ url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
{ url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
{ url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
{ url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
{ url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
{ url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
{ url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
{ url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
{ url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
{ url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
{ url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
{ url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
{ url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
{ url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
{ url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
{ url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
{ url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
{ url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
{ url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
{ url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.12" version = "0.15.12"
@@ -2091,15 +1860,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
] ]
[[package]]
name = "smmap"
version = "5.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" },
]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@@ -2183,41 +1943,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
] ]
[[package]]
name = "streamlit"
version = "1.57.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altair" },
{ name = "anyio" },
{ name = "blinker" },
{ name = "cachetools" },
{ name = "click" },
{ name = "gitpython" },
{ name = "httptools" },
{ name = "itsdangerous" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pandas" },
{ name = "pillow" },
{ name = "protobuf" },
{ name = "pyarrow" },
{ name = "pydeck" },
{ name = "python-multipart" },
{ name = "requests" },
{ name = "starlette" },
{ name = "tenacity" },
{ name = "toml" },
{ name = "typing-extensions" },
{ name = "uvicorn" },
{ name = "watchdog", marker = "sys_platform != 'darwin'" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/f8/b2daf7a5f8ae15527daf94406e771bb6075e958a01c3dde9eba79dc3c9a3/streamlit-1.57.0.tar.gz", hash = "sha256:0b028d305c1a1a757071b2c9504966787602842fc8af6e873795ca58d2b4d12f", size = 8678859, upload-time = "2026-04-28T22:13:32.238Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d8/1a/3ca2293d8552bacea3e67e9600d2d1df7df4a325059769ad83d91c279595/streamlit-1.57.0-py3-none-any.whl", hash = "sha256:0d1d41972aeade5637dbb0e7f0eefa5312272f85304923d240a1b1f0475249c8", size = 9194216, upload-time = "2026-04-28T22:13:29.624Z" },
]
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "9.1.4" version = "9.1.4"
@@ -2239,15 +1964,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" },
] ]
[[package]]
name = "toml"
version = "0.10.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
]
[[package]] [[package]]
name = "tqdm" name = "tqdm"
version = "4.67.3" version = "4.67.3"
@@ -2361,24 +2077,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
] ]
[[package]]
name = "watchdog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]] [[package]]
name = "watchfiles" name = "watchfiles"
version = "1.1.1" version = "1.1.1"