From 072632e2202045c030ca3b74be9271eb0f089d20 Mon Sep 17 00:00:00 2001 From: Jae Date: Thu, 27 Nov 2025 22:08:41 -0600 Subject: [PATCH 1/5] feat(ingestion): PR1 - Add GTFS Ingestion Stack (gtfs-rt-pipeline) --- eta_prediction/README.md | 59 + eta_prediction/gtfs-rt-pipeline/.env.example | 14 + eta_prediction/gtfs-rt-pipeline/README.md | 123 ++ .../gtfs-rt-pipeline/analytics/README.md | 56 + .../gtfs-rt-pipeline/analytics/run_exports.sh | 46 + .../analytics/sql/sch_nextday_check.sql | 8 + .../sql/stops_per_route_distribution.sql | 17 + .../sql/stops_per_trip_and_route.sql | 10 + .../analytics/sql/trips_per_route.sql | 19 + .../sql/vehicle_poll_time_per_route.sql | 24 + .../sql/vehicle_poll_time_per_trip.sql | 24 + .../analytics/sql/vp_and_tu_per_route.sql | 20 + .../analytics/sql/vp_and_tu_per_trip.sql | 20 + .../sql/vp_dupes_and_out_of_order.sql | 35 + .../analytics/sql/vp_gap_hist.sql | 10 + .../analytics/sql/vp_gap_ranges.sql | 13 + .../analytics/sql/vp_gaps_per_day_summary.sql | 20 + .../analytics/sql/vp_gaps_per_vehicle_day.sql | 12 + .../sql/vp_missing_gps_by_vehicle.sql | 8 + .../analytics/sql/vp_missing_gps_counts.sql | 8 + .../analytics/sql/vp_per_day.sql | 7 + .../analytics/sql/vp_per_feed.sql | 7 + .../analytics/sql/vp_time_range.sql | 4 + .../analytics/sql/vp_trip_switches.sql | 13 + .../analytics/sql/vp_tripid_consistency.sql | 9 + .../analytics/sql/vp_tripid_nulls.sql | 6 + .../gtfs-rt-pipeline/diagnostics.py | 136 ++ .../gtfs-rt-pipeline/eta_vp_sample.parquet | Bin 0 -> 820078 bytes .../gtfs-rt-pipeline/ingestproj/__init__.py | 6 + .../gtfs-rt-pipeline/ingestproj/asgi.py | 16 + .../gtfs-rt-pipeline/ingestproj/celery.py | 27 + .../gtfs-rt-pipeline/ingestproj/settings.py | 79 + .../gtfs-rt-pipeline/ingestproj/urls.py | 22 + .../gtfs-rt-pipeline/ingestproj/wsgi.py | 16 + eta_prediction/gtfs-rt-pipeline/manage.py | 22 + .../gtfs-rt-pipeline/pyproject.toml | 31 + .../gtfs-rt-pipeline/rt_pipeline/__init__.py | 0 .../gtfs-rt-pipeline/rt_pipeline/admin.py | 52 + .../gtfs-rt-pipeline/rt_pipeline/apps.py | 6 + .../management/commands/build_eta_sample.py | 308 +++ .../gtfs-rt-pipeline/rt_pipeline/models.py | 94 + .../gtfs-rt-pipeline/rt_pipeline/tasks.py | 219 ++ .../gtfs-rt-pipeline/rt_pipeline/tests.py | 3 + .../gtfs-rt-pipeline/rt_pipeline/views.py | 3 + .../gtfs-rt-pipeline/sch_pipeline/admin.py | 306 +++ .../fixtures/initial_providers.json | 17 + .../sch_pipeline/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/import_gtfs.py | 283 +++ .../gtfs-rt-pipeline/sch_pipeline/models.py | 1085 ++++++++++ .../gtfs-rt-pipeline/sch_pipeline/tasks.py | 32 + .../gtfs-rt-pipeline/sch_pipeline/utils.py | 17 + eta_prediction/gtfs-rt-pipeline/uv.lock | 1532 ++++++++++++++ eta_prediction/pyproject.toml | 34 + eta_prediction/uv.lock | 1819 +++++++++++++++++ 55 files changed, 6757 insertions(+) create mode 100644 eta_prediction/README.md create mode 100644 eta_prediction/gtfs-rt-pipeline/.env.example create mode 100644 eta_prediction/gtfs-rt-pipeline/README.md create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/README.md create mode 100755 eta_prediction/gtfs-rt-pipeline/analytics/run_exports.sh create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/sch_nextday_check.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_route_distribution.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_trip_and_route.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/trips_per_route.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_route.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_trip.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_route.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_trip.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_dupes_and_out_of_order.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_hist.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_ranges.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_day_summary.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_vehicle_day.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_by_vehicle.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_counts.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_day.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_feed.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_time_range.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_trip_switches.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_consistency.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_nulls.sql create mode 100644 eta_prediction/gtfs-rt-pipeline/diagnostics.py create mode 100644 eta_prediction/gtfs-rt-pipeline/eta_vp_sample.parquet create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/__init__.py create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/asgi.py create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/celery.py create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/settings.py create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/urls.py create mode 100644 eta_prediction/gtfs-rt-pipeline/ingestproj/wsgi.py create mode 100755 eta_prediction/gtfs-rt-pipeline/manage.py create mode 100644 eta_prediction/gtfs-rt-pipeline/pyproject.toml create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/__init__.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/admin.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/apps.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/management/commands/build_eta_sample.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/models.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/tasks.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/tests.py create mode 100644 eta_prediction/gtfs-rt-pipeline/rt_pipeline/views.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/admin.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/fixtures/initial_providers.json create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/__init__.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/__init__.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/import_gtfs.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/models.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/tasks.py create mode 100644 eta_prediction/gtfs-rt-pipeline/sch_pipeline/utils.py create mode 100644 eta_prediction/gtfs-rt-pipeline/uv.lock create mode 100644 eta_prediction/pyproject.toml create mode 100644 eta_prediction/uv.lock diff --git a/eta_prediction/README.md b/eta_prediction/README.md new file mode 100644 index 0000000..291a5b5 --- /dev/null +++ b/eta_prediction/README.md @@ -0,0 +1,59 @@ +# GTFS-RT Tools + +Utilities for exploring **GTFS-Realtime** feeds such as Vehicle Positions, Trip Updates, and Alerts. +This repository is part of experiments with the **bUCR Realtime feeds** and other open transit data sources. + +--- + +## Installation + +For installation and dependency management we are using [uv](https://github.com/astral-sh/uv), a fast drop-in replacement for pip and venv. + +1. **Install uv** (if you don’t already have it): + + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + +2. **Clone this repository**: + + ```bash + git clone https://github.com/simovilab/gtfs-django.git + cd eta-prediction + ``` + +3. **Install dependencies** from `pyproject.toml`: + + ```bash + uv sync + ``` + +This will create a virtual environment and install: + +- [`gtfs-realtime-bindings`](https://github.com/MobilityData/gtfs-realtime-bindings) (protobuf definitions for GTFS-RT) +- [`requests`](https://docs.python-requests.org/) + +--- + +## Usage + +Example: fetch and print the first 10 vehicle positions. + +```bash +uv run gtfs_rt_bindings_VP.py +``` +--- + +## Switching Feeds + +Inside each script, change the `URL` variable to any GTFS-RT VehiclePositions endpoint. + +Examples: +- bUCR (default): + ``` + https://databus.bucr.digital/feed/realtime/vehicle_positions.pb + ``` +- MBTA: + ``` + https://cdn.mbta.com/realtime/VehiclePositions.pb + ``` diff --git a/eta_prediction/gtfs-rt-pipeline/.env.example b/eta_prediction/gtfs-rt-pipeline/.env.example new file mode 100644 index 0000000..101bfa1 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/.env.example @@ -0,0 +1,14 @@ +DJANGO_SECRET_KEY=change-me +DJANGO_DEBUG=True +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 + +DATABASE_URL=postgresql://gtfs:gtfs@localhost:5432/gtfs + +REDIS_URL=redis://localhost:6379/0 + +GTFS_SCHEDULE_ZIP_URL=https://cdn.mbta.com/MBTA_GTFS.zip + +FEED_NAME=mbta +GTFSRT_VEHICLE_POSITIONS_URL=https://cdn.mbta.com/realtime/VehiclePositions.pb +GTFSRT_TRIP_UPDATES_URL=https://cdn.mbta.com/realtime/TripUpdates.pb +POLL_SECONDS=15 \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/README.md b/eta_prediction/gtfs-rt-pipeline/README.md new file mode 100644 index 0000000..ec217a5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/README.md @@ -0,0 +1,123 @@ +# 🚍 GTFS-RT Ingestion Pipeline + +A Django + Celery + Redis workflow for ingesting **GTFS-Realtime feeds** (Vehicle Positions and Trip Updaes protobufs `.pb`) into **PostgreSQL**. +--- + +## ✨ Features +- Periodic fetch of GTFS-RT feeds (VP + TU) +- Deduplication via SHA-256 (`RawMessage` model) +- Idempotent parse & upsert into normalized tables +- Namespaced feed labels (e.g. `mbta:VP`, `mbta:TU`) +- Admin UI for browsing ingested rows +- Tested with MBTA live feeds (15s polling) + +--- + +## πŸ› οΈ Stack +- **Django** β†’ ORM, admin, migrations +- **Celery** β†’ task queue, periodic tasks +- **Redis** β†’ broker + result backend +- **PostgreSQL** β†’ durable storage +- **uv** β†’ Python project/env manager (`pyproject.toml`) +- **gtfs-realtime-bindings** β†’ protobuf parsing +- **requests** β†’ HTTP client + +--- + +## πŸ“‚ Project Structure +``` +gtfs-rt-pipeline/ +β”œβ”€ pyproject.toml # deps +β”œβ”€ .env.example # DB, Redis, feeds +β”œβ”€ manage.py +β”œβ”€ ingestproj/ # Django project +β”‚ β”œβ”€ settings.py # DB + Celery config +β”‚ β”œβ”€ celery.py # Celery app +└─ rt_pipeline/ # Django app + β”œβ”€ models.py # RawMessage, VehiclePosition, TripUpdate + β”œβ”€ tasks.py # fetch β†’ parse β†’ upsert + β”œβ”€ admin.py # models registered for web UI + └─ migrations/ +``` + +--- + +## βš™οΈ Setup + +1. **Install deps** + ```bash + uv sync + ``` + +2. **Configure environment** (`.env`) + ```env + # --- Django --- + DJANGO_SECRET_KEY=change-me + DJANGO_DEBUG=True + DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 + + # --- Postgres --- + DATABASE_URL=postgresql://gtfs:gtfs@localhost:5432/gtfs + + # --- Redis (Celery broker/backend) --- + REDIS_URL=redis://localhost:6379/0 + + # --- Feed config --- + FEED_NAME=mbta + GTFSRT_VEHICLE_POSITIONS_URL=https://cdn.mbta.com/realtime/VehiclePositions.pb + GTFSRT_TRIP_UPDATES_URL=https://cdn.mbta.com/realtime/TripUpdates.pb + POLL_SECONDS=15 + + # --- HTTP --- + HTTP_CONNECT_TIMEOUT=3 + HTTP_READ_TIMEOUT=5 + ``` + +3. **Run services** + ```bash + # Start Redis + Postgres + redis-server & + postgres -D /usr/local/var/postgres & + + # Django + python manage.py migrate + python manage.py createsuperuser + python manage.py runserver + + # Celery + celery -A ingestproj worker -Q fetch,upsert -l INFO + celery -A ingestproj beat -l INFO + ``` + +--- + +## πŸ”„ How It Works + +1. **Fetch task** (`fetch_vehicle_positions` / `fetch_trip_updates`) + - Downloads `.pb` feed β†’ computes hash β†’ dedup in `RawMessage`. + - If new β†’ enqueues parse task. + +2. **Parse task** (`parse_and_upsert_*`) + - Converts protobuf into rows. + - Bulk upserts into `VehiclePosition` or `TripUpdate`. + - Idempotent thanks to unique constraints. + +--- + +## πŸ” Inspecting Data + +**Django Admin** +```bash +python manage.py runserver +``` +Go to `localhost:8000/admin` β†’ log in with your superuser β†’ browse RawMessages, Vehicle Positions, and Trip Updates. + +--- + +## πŸ“Š Current Status +βœ… End-to-end pipeline works: +- Live MBTA data ingested every ~15s +- Deduplication enforced +- VP & TU both flowing into Postgres +- Admin UI enabled for quick inspection + diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/README.md b/eta_prediction/gtfs-rt-pipeline/analytics/README.md new file mode 100644 index 0000000..3c89bf5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/README.md @@ -0,0 +1,56 @@ +# Analytics SQL Scripts + +This directory contains diagnostic and exploratory SQL queries for the GTFS-RT + Schedule database. +The included `run_exports.sh` script automatically executes all queries and exports their results to CSV files in the `exports/` folder. + +## Usage + +From `.../gtfs-rt-pipeline/`, + +```bash +./analytics/run_exports.sh +``` +- Loads `DATABASE_URL` from `.env` if not set. +- Cleans and executes each `.sql` file in `sql/`. +- Writes results as CSVs under `exports/`. + +--- + +## Query Descriptions + +### Data Coverage & Volume +- **vp_per_feed.sql** β€” VehiclePosition counts per feed/day. +- **vp_per_day.sql** β€” Daily VehiclePosition counts. +- **vp_time_range.sql** β€” First and last timestamps with total coverage span. +- **trips_per_route.sql** β€” Scheduled trip count per route. + +### Temporal Consistency & Gaps +- **vp_gap_hist.sql** β€” Histogram of polling intervals between VehiclePositions. +- **vp_gap_ranges.sql** β€” Gaps >60s between consecutive VehiclePositions. +- **vp_gaps_per_vehicle_day.sql** β€” Max reporting gap per vehicle/day (>60s). +- **vp_gaps_per_day_summary.sql** β€” Avg/max gaps and vehicle counts per day. +- **vehicle_poll_time_per_route.sql** β€” Avg/min/max polling interval per route. +- **vehicle_poll_time_per_trip.sql** β€” Avg/min/max polling interval per trip. + +### Trip & Route Structure +- **stops_per_route_distribution.sql** β€” Avg/min/max stops per trip by route. +- **stops_per_trip_and_route.sql** β€” Stop counts per trip and route. +- **sch_nextday_check.sql** β€” Detects stop times extending past midnight. + +### Trip ID & Vehicle Consistency +- **vp_tripid_nulls.sql** β€” Percentage of VehiclePositions missing `trip_id`. +- **vp_tripid_consistency.sql** β€” Distinct trips per vehicle/day (consistency check). +- **vp_trip_switches.sql** β€” Trip change events per vehicle/day. + +### GPS Completeness & Quality +- **vp_missing_gps_counts.sql** β€” Global count of rows missing lat/lon. +- **vp_missing_gps_by_vehicle.sql** β€” Missing GPS counts per vehicle. +- **vp_dupes_and_out_of_order.sql** β€” Detects duplicate or out-of-order timestamps. + +### Housekeeping +- **vp_and_tu_per_route.sql** β€” Counts of VehiclePositions vs TripUpdates per route. +- **vp_and_tu_per_trip.sql** β€” Counts of VehiclePositions vs TripUpdates per trip. + +--- + +**Output:** All query results are exported as CSV files in the `exports/` directory. diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/run_exports.sh b/eta_prediction/gtfs-rt-pipeline/analytics/run_exports.sh new file mode 100755 index 0000000..5427ace --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/run_exports.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Load .env if needed +if [ -z "${DATABASE_URL:-}" ]; then + ENV_FILE="$(dirname "$0")/../.env" + [ -f "$ENV_FILE" ] && { set -a; source "$ENV_FILE"; set +a; } || { + echo "DATABASE_URL not set and .env not found" >&2; exit 1; } +fi + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SQL_DIR="$ROOT/sql" +OUT_DIR="$ROOT/exports" +mkdir -p "$OUT_DIR" + +clean_sql() { + # strip block comments, line comments, and trailing semicolon; squash newlines + perl -0777 -pe 's{/\*.*?\*/}{}gs; s/(?m)--.*$//g;' "$1" \ + | tr '\n' ' ' \ + | sed -E 's/;[[:space:]]*$//' +} + +copy() { + local name="$1"; local file="$2"; local out="$OUT_DIR/${name}.csv" + echo "-> ${name}" + local query; query="$(clean_sql "$file")" + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "\copy (${query}) TO '${out}' CSV HEADER" +} + +# copy "vp_time_range" "$SQL_DIR/vp_time_range.sql" +# copy "vp_tripid_nulls" "$SQL_DIR/vp_tripid_nulls.sql" +# copy "vp_tripid_consistency" "$SQL_DIR/vp_tripid_consistency.sql" +# copy "vp_trip_switches" "$SQL_DIR/vp_trip_switches.sql" +# copy "vp_gaps_per_vehicle_day" "$SQL_DIR/vp_gaps_per_vehicle_day.sql" +# copy "vp_gaps_per_day_summary" "$SQL_DIR/vp_gaps_per_day_summary.sql" +# copy "vp_gap_hist" "$SQL_DIR/vp_gap_hist.sql" +# copy "vp_gap_ranges" "$SQL_DIR/vp_gap_ranges.sql" +# copy "sch_nextday_check" "$SQL_DIR/sch_nextday_check.sql" + +# Loop over all .sql files in the SQL_DIR +for file in "$SQL_DIR"/*.sql; do + name="$(basename "$file" .sql)" + copy "$name" "$file" +done + +echo "βœ… CSVs written to $OUT_DIR" \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/sch_nextday_check.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/sch_nextday_check.sql new file mode 100644 index 0000000..400f159 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/sch_nextday_check.sql @@ -0,0 +1,8 @@ +SELECT + COUNT(*) FILTER (WHERE arrival_time >= '24:00:00') AS arrival_nextday, + COUNT(*) FILTER (WHERE departure_time >= '24:00:00') AS departure_nextday, + COUNT(*) AS total_rows, + ROUND(100.0 * COUNT(*) FILTER (WHERE arrival_time >= '24:00:00') / COUNT(*), 2) AS pct_arrival_nextday, + ROUND(100.0 * COUNT(*) FILTER (WHERE departure_time >= '24:00:00') / COUNT(*), 2) AS pct_departure_nextday +FROM sch_pipeline_stoptime; +-- This query checks for stop times with arrival or departure times indicating service on the next day (i.e., times >= 24:00:00). \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_route_distribution.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_route_distribution.sql new file mode 100644 index 0000000..f8505f8 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_route_distribution.sql @@ -0,0 +1,17 @@ +-- stops_per_route_distribution.sql +WITH trip_counts AS ( + SELECT st.trip_id, COUNT(*) AS cnt + FROM sch_pipeline_stoptime st + GROUP BY st.trip_id +) +SELECT + r.route_id AS route_code, + ROUND(AVG(tc.cnt), 2) AS avg_stops_per_trip, + MIN(tc.cnt) AS min_stops, + MAX(tc.cnt) AS max_stops, + COUNT(*) AS trips_count +FROM trip_counts tc +JOIN sch_pipeline_trip t ON tc.trip_id = t.trip_id +JOIN sch_pipeline_route r ON t.route_id = r.route_id +GROUP BY r.route_id +ORDER BY r.route_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_trip_and_route.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_trip_and_route.sql new file mode 100644 index 0000000..dbf6784 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/stops_per_trip_and_route.sql @@ -0,0 +1,10 @@ +-- stops_per_trip_and_route.sql +SELECT + r.route_id AS route_code, + t.trip_id AS trip_code, + COUNT(*) AS n_stops +FROM sch_pipeline_stoptime st +JOIN sch_pipeline_trip t ON st.trip_id = t.trip_id +JOIN sch_pipeline_route r ON t.route_id = r.route_id +GROUP BY r.route_id, t.trip_id +ORDER BY r.route_id, t.trip_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/trips_per_route.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/trips_per_route.sql new file mode 100644 index 0000000..3d52eb9 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/trips_per_route.sql @@ -0,0 +1,19 @@ +-- -- Trips per route (by provider) +-- SELECT +-- feed_name, +-- route_id, +-- COUNT(*) AS trips_count +-- FROM sch_pipeline_trip +-- GROUP BY feed_name, route_id +-- ORDER BY feed_name, route_id; + +SELECT + t.route_id, + r.route_short_name, + r.route_long_name, + COUNT(*) AS trips_count +FROM sch_pipeline_trip t +LEFT JOIN sch_pipeline_route r + ON r.route_id = t.route_id +GROUP BY t.route_id, r.route_short_name, r.route_long_name +ORDER BY t.route_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_route.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_route.sql new file mode 100644 index 0000000..82eecd5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_route.sql @@ -0,0 +1,24 @@ +-- Per VEHICLE & ROUTE +WITH deltas AS ( + SELECT + feed_name, + vehicle_id, + route_id, + EXTRACT(EPOCH FROM ts - LAG(ts) OVER ( + PARTITION BY feed_name, vehicle_id, route_id + ORDER BY ts + )) AS delta_s + FROM rt_pipeline_vehicleposition +) +SELECT + feed_name, + vehicle_id, + route_id, + ROUND(AVG(delta_s)::numeric, 2) AS avg_poll_s, + MIN(delta_s) AS min_poll_s, + MAX(delta_s) AS max_poll_s, + COUNT(*) AS samples +FROM deltas +WHERE delta_s IS NOT NULL AND delta_s > 0 +GROUP BY feed_name, vehicle_id, route_id +ORDER BY feed_name, vehicle_id, route_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_trip.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_trip.sql new file mode 100644 index 0000000..201c2a3 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vehicle_poll_time_per_trip.sql @@ -0,0 +1,24 @@ +-- Per VEHICLE & TRIP +WITH deltas AS ( + SELECT + feed_name, + vehicle_id, + trip_id, + EXTRACT(EPOCH FROM ts - LAG(ts) OVER ( + PARTITION BY feed_name, vehicle_id, trip_id + ORDER BY ts + )) AS delta_s + FROM rt_pipeline_vehicleposition +) +SELECT + feed_name, + vehicle_id, + trip_id, + ROUND(AVG(delta_s)::numeric, 2) AS avg_poll_s, + MIN(delta_s) AS min_poll_s, + MAX(delta_s) AS max_poll_s, + COUNT(*) AS samples +FROM deltas +WHERE delta_s IS NOT NULL AND delta_s > 0 +GROUP BY feed_name, vehicle_id, trip_id +ORDER BY feed_name, vehicle_id, trip_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_route.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_route.sql new file mode 100644 index 0000000..6716b6f --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_route.sql @@ -0,0 +1,20 @@ +-- Per ROUTE: counts of VehiclePositions vs TripUpdates (by provider) +WITH vp AS ( + SELECT feed_name, route_id, COUNT(*) AS vp_count + FROM rt_pipeline_vehicleposition + GROUP BY feed_name, route_id +), +tu AS ( + SELECT feed_name, route_id, COUNT(*) AS tu_count + FROM rt_pipeline_tripupdate + GROUP BY feed_name, route_id +) +SELECT + COALESCE(vp.feed_name, tu.feed_name) AS feed_name, + COALESCE(vp.route_id, tu.route_id) AS route_id, + vp.vp_count, + tu.tu_count +FROM vp +FULL OUTER JOIN tu + ON vp.feed_name = tu.feed_name AND vp.route_id = tu.route_id +ORDER BY feed_name, route_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_trip.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_trip.sql new file mode 100644 index 0000000..908d476 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_and_tu_per_trip.sql @@ -0,0 +1,20 @@ +-- Per TRIP: counts of VehiclePositions vs TripUpdates (by provider) +WITH vp AS ( + SELECT feed_name, trip_id, COUNT(*) AS vp_count + FROM rt_pipeline_vehicleposition + GROUP BY feed_name, trip_id +), +tu AS ( + SELECT feed_name, trip_id, COUNT(*) AS tu_count + FROM rt_pipeline_tripupdate + GROUP BY feed_name, trip_id +) +SELECT + COALESCE(vp.feed_name, tu.feed_name) AS feed_name, + COALESCE(vp.trip_id, tu.trip_id) AS trip_id, + vp.vp_count, + tu.tu_count +FROM vp +FULL OUTER JOIN tu + ON vp.feed_name = tu.feed_name AND vp.trip_id = tu.trip_id +ORDER BY feed_name, trip_id; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_dupes_and_out_of_order.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_dupes_and_out_of_order.sql new file mode 100644 index 0000000..e994f15 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_dupes_and_out_of_order.sql @@ -0,0 +1,35 @@ +-- vp_dupes_and_out_of_order.sql +-- Replace :vehicle_id with your target id (or remove the WHERE to scan all). +WITH ordered AS ( + SELECT + vehicle_id, + ts, + LAG(ts) OVER (PARTITION BY vehicle_id ORDER BY ts) AS prev_ts + FROM rt_pipeline_vehicleposition +-- WHERE vehicle_id = :vehicle_id +) +SELECT + 'DUPLICATE_TS' AS issue, + vehicle_id, + ts AS current_ts, + prev_ts +FROM ( + SELECT + vehicle_id, + ts, + COUNT(*) OVER (PARTITION BY vehicle_id, ts) AS ts_ct, + prev_ts + FROM ordered +) x +WHERE ts_ct > 1 + +UNION ALL + +SELECT + 'OUT_OF_ORDER' AS issue, + vehicle_id, + ts AS current_ts, + prev_ts +FROM ordered +WHERE prev_ts IS NOT NULL AND ts < prev_ts +ORDER BY issue, vehicle_id, current_ts; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_hist.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_hist.sql new file mode 100644 index 0000000..2dbc5cd --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_hist.sql @@ -0,0 +1,10 @@ +WITH deltas AS ( + SELECT EXTRACT(EPOCH FROM ts - LAG(ts) OVER (PARTITION BY vehicle_id ORDER BY ts)) AS delta_s + FROM rt_pipeline_vehicleposition +) +SELECT ROUND(delta_s) AS delta_s_rounded, COUNT(*) AS n +FROM deltas +WHERE delta_s IS NOT NULL +GROUP BY 1 +ORDER BY 1; +-- This query generates a histogram of time gaps (in seconds) between consecutive vehicle position reports. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_ranges.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_ranges.sql new file mode 100644 index 0000000..f04c587 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gap_ranges.sql @@ -0,0 +1,13 @@ +WITH ordered AS ( + SELECT vehicle_id, ts, LAG(ts) OVER (PARTITION BY vehicle_id ORDER BY ts) AS prev_ts + FROM rt_pipeline_vehicleposition +) +SELECT vehicle_id, + prev_ts AS gap_start, + ts AS gap_end, + ROUND(EXTRACT(EPOCH FROM ts - prev_ts))::int AS gap_s +FROM ordered +WHERE prev_ts IS NOT NULL + AND ts - prev_ts > INTERVAL '60 seconds' +ORDER BY gap_s DESC; +-- This query identifies gaps greater than 60 seconds between consecutive vehicle position reports, showing the start and end times of each gap along with its duration in seconds. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_day_summary.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_day_summary.sql new file mode 100644 index 0000000..ca06c57 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_day_summary.sql @@ -0,0 +1,20 @@ +WITH deltas AS ( + SELECT DATE(ts) AS svc_day, vehicle_id, + EXTRACT(EPOCH FROM ts - LAG(ts) OVER (PARTITION BY vehicle_id ORDER BY ts)) AS delta_s + FROM rt_pipeline_vehicleposition +), +per_day AS ( + SELECT svc_day, AVG(delta_s) AS avg_gap_s, MAX(delta_s) AS max_gap_s + FROM deltas + WHERE delta_s IS NOT NULL + GROUP BY svc_day +) +SELECT + pd.svc_day, + (SELECT COUNT(DISTINCT vehicle_id) FROM rt_pipeline_vehicleposition t WHERE DATE(t.ts)=pd.svc_day) AS vehicles, + (SELECT COUNT(*) FROM rt_pipeline_vehicleposition t WHERE DATE(t.ts)=pd.svc_day) AS rows, + ROUND(pd.avg_gap_s)::int AS avg_gap_s, + ROUND(pd.max_gap_s)::int AS max_gap_s +FROM per_day pd +ORDER BY pd.svc_day DESC; +-- This query summarizes average and maximum gaps between vehicle position reports per service day, along with counts of distinct vehicles and total rows. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_vehicle_day.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_vehicle_day.sql new file mode 100644 index 0000000..1e9a131 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_gaps_per_vehicle_day.sql @@ -0,0 +1,12 @@ +WITH deltas AS ( + SELECT vehicle_id, DATE(ts) AS svc_day, + EXTRACT(EPOCH FROM ts - LAG(ts) OVER (PARTITION BY vehicle_id ORDER BY ts)) AS delta_s + FROM rt_pipeline_vehicleposition +) +SELECT vehicle_id, svc_day, MAX(delta_s) AS max_gap_s +FROM deltas +WHERE delta_s IS NOT NULL +GROUP BY 1,2 +HAVING MAX(delta_s) > 60 +ORDER BY max_gap_s DESC; +-- This query identifies vehicles that have gaps greater than 60 seconds between consecutive vehicle position reports on a given service day. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_by_vehicle.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_by_vehicle.sql new file mode 100644 index 0000000..3b02be9 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_by_vehicle.sql @@ -0,0 +1,8 @@ +-- vp_missing_gps_by_vehicle.sql +SELECT + vehicle_id, + COUNT(*) AS missing_rows +FROM rt_pipeline_vehicleposition +WHERE lat IS NULL OR lon IS NULL +GROUP BY vehicle_id +ORDER BY missing_rows DESC; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_counts.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_counts.sql new file mode 100644 index 0000000..dbb9dcf --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_missing_gps_counts.sql @@ -0,0 +1,8 @@ +-- vp_missing_gps_counts.sql +SELECT + COUNT(*) AS rows_with_missing_gps, + COUNT(*) FILTER (WHERE lat IS NULL) AS rows_missing_lat, + COUNT(*) FILTER (WHERE lon IS NULL) AS rows_missing_lon, + COUNT(DISTINCT vehicle_id) AS vehicles_with_missing_gps +FROM rt_pipeline_vehicleposition +WHERE lat IS NULL OR lon IS NULL; diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_day.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_day.sql new file mode 100644 index 0000000..dc97e27 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_day.sql @@ -0,0 +1,7 @@ +SELECT + DATE(ts) AS day, + COUNT(*) AS vp_count +FROM rt_pipeline_vehicleposition +GROUP BY 1 +ORDER BY 1; +-- Daily count of vehicle positions \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_feed.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_feed.sql new file mode 100644 index 0000000..92bf3b6 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_per_feed.sql @@ -0,0 +1,7 @@ +SELECT + feed_name, + DATE(ts) AS day, + COUNT(*) AS vp_count +FROM rt_pipeline_vehicleposition +GROUP BY 1,2 +ORDER BY 1,2; \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_time_range.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_time_range.sql new file mode 100644 index 0000000..e91f761 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_time_range.sql @@ -0,0 +1,4 @@ +SELECT MIN(ts) AS first_record, MAX(ts) AS last_record, (MAX(ts) - MIN(ts)) AS timespan +FROM rt_pipeline_vehicleposition; + +-- This query returns the first and last timestamps in the vehicle position table, along with the total timespan covered by the records. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_trip_switches.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_trip_switches.sql new file mode 100644 index 0000000..67465fa --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_trip_switches.sql @@ -0,0 +1,13 @@ +WITH ordered AS ( + SELECT vehicle_id, DATE(ts) AS svc_day, ts, trip_id, + LAG(trip_id) OVER (PARTITION BY vehicle_id, DATE(ts) ORDER BY ts) AS prev_trip + FROM rt_pipeline_vehicleposition +) +SELECT svc_day, vehicle_id, + SUM(CASE WHEN prev_trip IS NULL THEN 0 + WHEN trip_id IS DISTINCT FROM prev_trip THEN 1 + ELSE 0 END) AS real_trip_switches +FROM ordered +GROUP BY 1,2 +ORDER BY svc_day DESC, vehicle_id; +-- This query counts the number of times a vehicle switches trips on a given service day. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_consistency.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_consistency.sql new file mode 100644 index 0000000..ff20549 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_consistency.sql @@ -0,0 +1,9 @@ +WITH vp AS ( + SELECT vehicle_id, DATE(ts) AS svc_day, trip_id + FROM rt_pipeline_vehicleposition +) +SELECT vehicle_id, svc_day, COUNT(*) AS n_rows, COUNT(DISTINCT trip_id) AS distinct_trips +FROM vp +GROUP BY 1,2 +ORDER BY svc_day DESC, vehicle_id; +-- This query checks for consistency of trip_id values associated with each vehicle_id on a given service day. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_nulls.sql b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_nulls.sql new file mode 100644 index 0000000..a2e031b --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/analytics/sql/vp_tripid_nulls.sql @@ -0,0 +1,6 @@ +SELECT + COUNT(*) AS total_rows, + COUNT(*) FILTER (WHERE trip_id IS NULL) AS null_rows, + ROUND(100.0 * COUNT(*) FILTER (WHERE trip_id IS NULL) / COUNT(*), 2) AS null_pct +FROM rt_pipeline_vehicleposition; +-- This query checks for NULL values in the trip_id column of the vehicle position table. \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/diagnostics.py b/eta_prediction/gtfs-rt-pipeline/diagnostics.py new file mode 100644 index 0000000..791a897 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/diagnostics.py @@ -0,0 +1,136 @@ +""" +Debug helper to diagnose data availability issues. +Run with: python manage.py shell < debug_data.py +""" + +from django.db.models import Min, Max, Count, Q +from datetime import timedelta +from django.utils import timezone + + +import os +# init Django before importing models +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ingestproj.settings") +import django +django.setup() + + + +from sch_pipeline.models import StopTime, Stop, Route, Trip +from rt_pipeline.models import VehiclePosition, TripUpdate + +print("="*70) +print("GTFS-RT PIPELINE DATA DIAGNOSTICS") +print("="*70) + +# 1. Check Trip/Route data +print("\n1. SCHEDULE DATA (GTFS)") +print("-" * 70) +trip_count = Trip.objects.count() +route_count = Route.objects.count() +stop_count = Stop.objects.count() +stoptime_count = StopTime.objects.count() + +print(f" Trips: {trip_count:,}") +print(f" Routes: {route_count:,}") +print(f" Stops: {stop_count:,}") +print(f" StopTimes: {stoptime_count:,}") + +if route_count > 0: + top_routes = ( + Trip.objects + .values("route_id") + .annotate(trip_count=Count("id")) + .order_by("-trip_count")[:5] + ) + print(f"\n Top 5 routes by trip count:") + for r in top_routes: + print(f" {r['route_id']}: {r['trip_count']:,} trips") + +# 2. Check realtime data +print("\n2. REALTIME DATA (GTFS-RT)") +print("-" * 70) +vp_count = VehiclePosition.objects.count() +tu_count = TripUpdate.objects.count() + +print(f" VehiclePositions: {vp_count:,}") +print(f" TripUpdates: {tu_count:,}") + +if tu_count > 0: + tu_stats = TripUpdate.objects.aggregate( + min_ts=Min("ts"), + max_ts=Max("ts"), + unique_trips=Count("trip_id", distinct=True), + unique_stops=Count("stop_id", distinct=True), + ) + print(f"\n TripUpdate time range:") + print(f" Earliest: {tu_stats['min_ts']}") + print(f" Latest: {tu_stats['max_ts']}") + if tu_stats['min_ts'] and tu_stats['max_ts']: + span = tu_stats['max_ts'] - tu_stats['min_ts'] + print(f" Span: {span.days} days") + print(f" Unique trip_ids: {tu_stats['unique_trips']:,}") + print(f" Unique stop_ids: {tu_stats['unique_stops']:,}") + + # Check for start_date field + with_start_date = TripUpdate.objects.exclude(start_date__isnull=True).count() + print(f" TripUpdates with start_date: {with_start_date:,} ({100*with_start_date/tu_count:.1f}%)") + + # Check for arrival_time + with_arrival = TripUpdate.objects.exclude(arrival_time__isnull=True).count() + print(f" TripUpdates with arrival_time: {with_arrival:,} ({100*with_arrival/tu_count:.1f}%)") + +# 3. Check join potential +print("\n3. JOIN DIAGNOSTICS") +print("-" * 70) + +if trip_count > 0 and tu_count > 0: + # Check trip_id overlap + sample_trip_ids = set(Trip.objects.values_list("trip_id", flat=True)[:]) + tu_trip_ids = set(TripUpdate.objects.values_list("trip_id", flat=True).distinct()[:]) + overlap = sample_trip_ids & tu_trip_ids + + print(f" Sample trip_id overlap:") + print(f" Trip table (sample): {len(sample_trip_ids)}") + print(f" TripUpdate table (sample): {len(tu_trip_ids)}") + print(f" Overlap: {len(overlap)}") + + if overlap: + print(f" Example matching trip_ids: {list(overlap)[:3]}") + else: + print(f" ⚠️ NO OVERLAP - trip_ids don't match between tables!") + print(f" Trip examples: {list(sample_trip_ids)[:3]}") + print(f" TripUpdate examples: {list(tu_trip_ids)[:3]}") + +if stoptime_count > 0 and tu_count > 0: + # Check stop_id overlap + st_stop_ids = set(StopTime.objects.values_list("stop_id", flat=True).distinct()[:1000]) + tu_stop_ids = set(TripUpdate.objects.exclude(stop_id__isnull=True).values_list("stop_id", flat=True).distinct()[:1000]) + overlap = st_stop_ids & tu_stop_ids + + print(f"\n Stop_id overlap:") + print(f" StopTime table (sample): {len(st_stop_ids)}") + print(f" TripUpdate table (sample): {len(tu_stop_ids)}") + print(f" Overlap: {len(overlap)}") + + if not overlap and st_stop_ids and tu_stop_ids: + print(f" ⚠️ NO OVERLAP - stop_ids don't match!") + print(f" StopTime examples: {list(st_stop_ids)[:3]}") + print(f" TripUpdate examples: {list(tu_stop_ids)[:3]}") + +# 4. Sample query test +print("\n4. SAMPLE JOIN TEST") +print("-" * 70) + +if trip_count > 0 and tu_count > 0: + # Try to find one matching record + from django.db.models import OuterRef, Subquery, Exists + + # Get a trip_id that exists in both tables + tu_trip_ids = set(TripUpdate.objects.values_list("trip_id", flat=True).distinct()[:100]) + matching_trips = Trip.objects.filter(trip_id__in=tu_trip_ids)[:1] + + if matching_trips: + test_trip = matching_trips[0] + print(f" Test trip_id: {test_trip.trip_id}") + print(f" Route: {test_trip.route_id}") \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/eta_vp_sample.parquet b/eta_prediction/gtfs-rt-pipeline/eta_vp_sample.parquet new file mode 100644 index 0000000000000000000000000000000000000000..ebd8d88b4685c259b2f3282bab6f835086edd780 GIT binary patch literal 820078 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�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áUWE)@v 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 literal 0 HcmV?d00001 diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/__init__.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/__init__.py new file mode 100644 index 0000000..86bb0e5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, unicode_literals + +# This will make sure the app is always imported when Django starts +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/asgi.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/asgi.py new file mode 100644 index 0000000..ecc270b --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ingestproj project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ingestproj.settings') + +application = get_asgi_application() diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/celery.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/celery.py new file mode 100644 index 0000000..db5ceab --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/celery.py @@ -0,0 +1,27 @@ +import os +from celery import Celery +from celery.schedules import crontab +from django.conf import settings + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ingestproj.settings") +app = Celery("ingestproj") + +# All Celery config via Django settings/env +app.conf.broker_url = settings.REDIS_URL +app.conf.result_backend = settings.REDIS_URL +app.conf.task_acks_late = True +app.conf.worker_prefetch_multiplier = 4 +app.conf.task_routes = { + "rt_pipeline.tasks.fetch_vehicle_positions": {"queue": "fetch"}, + "rt_pipeline.tasks.parse_and_upsert_vehicle_positions": {"queue": "upsert"}, + "rt_pipeline.tasks.fetch_trip_updates": {"queue": "fetch"}, + "rt_pipeline.tasks.parse_and_upsert_trip_updates": {"queue": "upsert"}, + 'fetch-gtfs-schedule': { + 'task': 'gtfs_static.tasks.fetch_and_import_gtfs_schedule', + 'schedule': crontab(hour=3, minute=0), + 'options': {'queue': 'static'} + } +} +app.autodiscover_tasks() + + diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/settings.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/settings.py new file mode 100644 index 0000000..877b3c5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/settings.py @@ -0,0 +1,79 @@ +import environ, os +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent +env = environ.Env() +environ.Env.read_env(BASE_DIR / ".env") + +SECRET_KEY = env("DJANGO_SECRET_KEY", default="dev-key") +DEBUG = env.bool("DJANGO_DEBUG", default=True) +ALLOWED_HOSTS = [h.strip() for h in env("DJANGO_ALLOWED_HOSTS", default="*").split(",")] + +INSTALLED_APPS = [ + "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", + "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.gis", + "rt_pipeline", "sch_pipeline" +] + +# Admin/templates config (required for admin) +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], # you can add template dirs later if you need + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +# Silence the auto field warning (recommended) +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", +] + +ROOT_URLCONF = "ingestproj.urls" +WSGI_APPLICATION = "ingestproj.wsgi.application" + + +DATABASES = {"default": env.db("DATABASE_URL")} +DATABASES["default"]["ENGINE"] = "django.contrib.gis.db.backends.postgis" + + +STATIC_URL = "static/" + +# Celery via env (set in celery.py as well) +REDIS_URL = env("REDIS_URL") +FEED_NAME = env("FEED_NAME") +GTFSRT_VEHICLE_POSITIONS_URL = env("GTFSRT_VEHICLE_POSITIONS_URL") +GTFSRT_TRIP_UPDATES_URL = env("GTFSRT_TRIP_UPDATES_URL") +POLL_SECONDS = env.int("POLL_SECONDS", default=15) +HTTP_CONNECT_TIMEOUT = env.float("HTTP_CONNECT_TIMEOUT", default=3.0) +HTTP_READ_TIMEOUT = env.float("HTTP_READ_TIMEOUT", default=5.0) + +from celery.schedules import schedule + +# CELERY_BEAT_SCHEDULE = { +# # existing vehicle positions schedule +# "poll-vehicle-positions": { +# "task": "rt_pipeline.tasks.fetch_vehicle_positions", +# "schedule": schedule(run_every=POLL_SECONDS), +# }, +# # NEW β€” trip updates +# "poll-trip-updates": { +# "task": "rt_pipeline.tasks.fetch_trip_updates", +# "schedule": schedule(run_every=POLL_SECONDS), +# }, +# } diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/urls.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/urls.py new file mode 100644 index 0000000..d394a44 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for ingestproj project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/eta_prediction/gtfs-rt-pipeline/ingestproj/wsgi.py b/eta_prediction/gtfs-rt-pipeline/ingestproj/wsgi.py new file mode 100644 index 0000000..b64d2d2 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/ingestproj/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ingestproj project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ingestproj.settings') + +application = get_wsgi_application() diff --git a/eta_prediction/gtfs-rt-pipeline/manage.py b/eta_prediction/gtfs-rt-pipeline/manage.py new file mode 100755 index 0000000..3d2b818 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ingestproj.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/eta_prediction/gtfs-rt-pipeline/pyproject.toml b/eta_prediction/gtfs-rt-pipeline/pyproject.toml new file mode 100644 index 0000000..d706937 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/pyproject.toml @@ -0,0 +1,31 @@ +[project] +name = "gtfs-rt-pipeline" +version = "0.1.0" +description = "Django + Celery pipeline for GTFS-Realtime ingestion" +requires-python = ">=3.11" +dependencies = [ + "Django>=5.0", + "celery>=5.4", + "redis>=5.0", + "psycopg[binary,pool]>=3.2", + "django-environ>=0.11", + "python-dotenv>=1.0", + "requests>=2.32", + "gtfs-realtime-bindings>=1.0.0", + "numpy>=2.3.4", + "pandas>=2.3.3", + "fastparquet>=2024.11.0", + "scikit-learn>=1.7.2", +] + +[project.optional-dependencies] +dev = ["ipython", "black", "pytest", "pytest-django", "mypy"] + +[tool.black] +line-length = 100 +target-version = ["py311"] + +[dependency-groups] +dev = [ + "ipykernel>=7.1.0", +] diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/__init__.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/admin.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/admin.py new file mode 100644 index 0000000..9475d7d --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/admin.py @@ -0,0 +1,52 @@ +from django.contrib import admin +from .models import RawMessage, VehiclePosition, TripUpdate + +@admin.register(RawMessage) +class RawMessageAdmin(admin.ModelAdmin): + list_display = ("feed_name", "message_type", "fetched_at", "header_timestamp", "incrementality", "content_hash") + search_fields = ("feed_name", "content_hash") + list_filter = ("feed_name", "message_type", "incrementality") + date_hierarchy = "fetched_at" + ordering = ("-fetched_at",) + + def __str__(self): + return f"{self.feed_name}:{self.message_type} @ {self.fetched_at:%Y-%m-%d %H:%M:%S}" + +@admin.register(VehiclePosition) +class VehiclePositionAdmin(admin.ModelAdmin): + list_display = ("feed_name", "vehicle_id", "ts", "lat", "lon", "route_id", "trip_id", "speed", "current_stop_sequence", "raw_message") + search_fields = ("vehicle_id", "route_id", "trip_id", "feed_name") + list_filter = ("feed_name", "route_id") + date_hierarchy = "ts" + ordering = ("-ts",) + + def __str__(self): + return f"{self.feed_name}:{self.vehicle_id} @ {self.ts:%Y-%m-%d %H:%M:%S}" + +@admin.register(TripUpdate) +class TripUpdateAdmin(admin.ModelAdmin): + list_display = ( + "feed_name", + "trip_id", + "start_time", + "start_date", + "schedule_relationship", + "vehicle_id", + "route_id", + "ts", + "stop_id", + "stop_sequence", + "arrival_time", + "departure_time", + "arrival_delay", + "departure_delay", + "stu_schedule_relationship", + "raw_message" + ) + search_fields = ("trip_id", "route_id", "vehicle_id", "stop_id", "feed_name") + list_filter = ("feed_name", "route_id") + date_hierarchy = "ts" + ordering = ("-ts",) + + def __str__(self): + return f"{self.feed_name}:{self.trip_id} @ {self.ts:%Y-%m-%d %H:%M:%S}" \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/apps.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/apps.py new file mode 100644 index 0000000..c795fcc --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RtPipelineConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'rt_pipeline' diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/management/commands/build_eta_sample.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/management/commands/build_eta_sample.py new file mode 100644 index 0000000..903e7ed --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/management/commands/build_eta_sample.py @@ -0,0 +1,308 @@ +from __future__ import annotations + +import sys +from pathlib import Path +from datetime import timedelta, UTC + +from django.conf import settings +from django.core.management.base import BaseCommand +from django.utils import timezone + +# --- Make sibling "feature_engineering" importable (no packaging needed) --- +BASE_DIR = Path(getattr(settings, "BASE_DIR", Path(__file__).resolve().parents[3])) +ETA_PREDICTION_ROOT = BASE_DIR.parent +FEATURE_ENG_ROOT = ETA_PREDICTION_ROOT / "feature_engineering" + +if str(ETA_PREDICTION_ROOT) not in sys.path: + sys.path.insert(0, str(ETA_PREDICTION_ROOT)) + +try: + from feature_engineering.dataset_builder import build_vp_training_dataset, save_dataset + from sch_pipeline.utils import top_routes_by_scheduled_trips +except ImportError as e: + print(f"ERROR: Failed to import required modules: {e}") + print(f"ETA_PREDICTION_ROOT: {ETA_PREDICTION_ROOT}") + print(f"FEATURE_ENG_ROOT: {FEATURE_ENG_ROOT}") + print(f"sys.path: {sys.path[:3]}") + raise + + +class Command(BaseCommand): + help = "Build ETA training dataset from VehiclePosition data for the top-N busiest routes." + + def add_arguments(self, parser): + parser.add_argument( + "--top-routes", + type=int, + default=3, + help="Top N routes by scheduled trips (global)" + ) + parser.add_argument( + "--days", + type=int, + default=14, + help="Lookback window in days" + ) + parser.add_argument( + "--min-observations", + type=int, + default=10, + help="Min observations per stop" + ) + parser.add_argument( + "--distance-threshold", + type=float, + default=50.0, + help="Distance threshold (meters) to consider vehicle 'arrived' at stop" + ) + parser.add_argument( + "--max-stops-ahead", + type=int, + default=5, + help="Maximum number of upcoming stops to include per VP" + ) + parser.add_argument( + "--vp-sample-interval", + type=int, + default=30, + help="Sample VPs every N seconds per vehicle (0=no sampling, use all VPs)" + ) + parser.add_argument( + "--out", + type=str, + default="eta_vp_sample.parquet", + help="Output parquet path" + ) + parser.add_argument( + "--no-weather", + action="store_true", + help="Disable weather features" + ) + parser.add_argument( + "--route-ids", + type=str, + help="Comma-separated route IDs (overrides --top-routes)" + ) + parser.add_argument( + "--start-date", + type=str, + help="Start date (YYYY-MM-DD format, overrides --days)" + ) + parser.add_argument( + "--end-date", + type=str, + help="End date (YYYY-MM-DD format, overrides --days)" + ) + + def handle(self, *args, **opts): + n = opts["top_routes"] + days = opts["days"] + min_obs = opts["min_observations"] + distance_threshold = opts["distance_threshold"] + max_stops_ahead = opts["max_stops_ahead"] + # vp_sample_interval = opts["vp_sample_interval"] + out = "../datasets/" + opts["out"] + attach_weather = not opts["no_weather"] + manual_routes = opts.get("route_ids") + start_date_str = opts.get("start_date") + end_date_str = opts.get("end_date") + + # Determine date range + if start_date_str and end_date_str: + try: + start = timezone.datetime.strptime(start_date_str, "%Y-%m-%d").replace(tzinfo=UTC) + end = timezone.datetime.strptime(end_date_str, "%Y-%m-%d").replace(tzinfo=UTC) + except ValueError as e: + self.stdout.write( + self.style.ERROR(f"Invalid date format: {e}. Use YYYY-MM-DD") + ) + return + else: + # Default: use fixed date range or calculate from --days + # For testing, using fixed dates: + start = timezone.datetime(2025, 10, 8, 0, 0, tzinfo=UTC) + end = timezone.datetime(2025, 10, 9, 0, 0, tzinfo=UTC) + # Or calculate from days: + # end = timezone.now() + # start = end - timedelta(days=days) + + # Determine which routes to use + if manual_routes: + route_ids = [r.strip() for r in manual_routes.split(",")] + self.stdout.write( + self.style.NOTICE(f"Using manually specified routes: {', '.join(route_ids)}") + ) + else: + self.stdout.write( + self.style.NOTICE(f"Selecting top {n} routes by scheduled trips...") + ) + try: + route_ids = top_routes_by_scheduled_trips(n=n) + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Failed to get top routes: {e}") + ) + return + + if not route_ids: + self.stdout.write( + self.style.WARNING( + "No routes found. Is the schedule loaded? " + "Check: python manage.py shell -c 'from sch_pipeline.models import Trip; print(Trip.objects.count())'" + ) + ) + return + + self.stdout.write( + self.style.SUCCESS(f"Found routes: {', '.join(route_ids)}") + ) + + # Display configuration + self.stdout.write("\n" + "="*60) + self.stdout.write(self.style.NOTICE("Configuration:")) + self.stdout.write(f" Routes: {', '.join(route_ids)}") + self.stdout.write(f" Date range: {start.date()} to {end.date()}") + self.stdout.write(f" Distance threshold: {distance_threshold}m") + self.stdout.write(f" Max stops ahead: {max_stops_ahead}") + # # self.stdout.write(f" VP sample interval: {vp_sample_interval}s ({'all VPs' if vp_sample_interval == 0 else 'sampled'})") + self.stdout.write(f" Min observations/stop: {min_obs}") + self.stdout.write(f" Weather features: {'enabled' if attach_weather else 'disabled'}") + self.stdout.write(f" Output: {out}") + self.stdout.write("="*60 + "\n") + + # Check for VehiclePosition data + from rt_pipeline.models import VehiclePosition + vp_count = VehiclePosition.objects.filter( + ts__gte=start, + ts__lt=end + ).count() + + if vp_count == 0: + self.stdout.write( + self.style.WARNING( + f"No VehiclePosition data found in date range {start.date()} to {end.date()}\n" + "Check data availability:\n" + " python manage.py shell -c 'from rt_pipeline.models import VehiclePosition; " + "from django.db.models import Min, Max; " + "print(VehiclePosition.objects.aggregate(min=Min(\"ts\"), max=Max(\"ts\")))'" + ) + ) + return + else: + self.stdout.write( + self.style.SUCCESS(f"Found {vp_count:,} VehiclePosition records in date range") + ) + + # Build dataset + try: + self.stdout.write(self.style.NOTICE("\nBuilding dataset...")) + df = build_vp_training_dataset( + route_ids=route_ids, + start_date=start, + end_date=end, + distance_threshold=distance_threshold, + max_stops_ahead=max_stops_ahead, + # min_observations_per_stop=min_obs, + # vp_sample_interval_seconds=vp_sample_interval, + attach_weather=attach_weather, + ) + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Failed to build dataset: {e}") + ) + import traceback + self.stdout.write(traceback.format_exc()) + return + + if df.empty: + self.stdout.write( + self.style.WARNING( + "Resulting dataset is empty. Possible issues:\n" + " 1. No VehiclePosition data in the date range\n" + " 2. VPs not matching any trips with stop sequences\n" + " 3. Vehicles never came close enough to stops (try increasing --distance-threshold)\n" + " 4. All data filtered out by --min-observations threshold\n" + " 5. No future VPs available to detect arrivals (incomplete trips)\n" + "\nDebug queries:\n" + " - Check VP count: python manage.py shell -c 'from rt_pipeline.models import VehiclePosition; print(VehiclePosition.objects.count())'\n" + " - Check date range: python manage.py shell -c 'from rt_pipeline.models import VehiclePosition; from django.db.models import Min, Max; print(VehiclePosition.objects.aggregate(min=Min(\"ts\"), max=Max(\"ts\")))'\n" + " - Check StopTime data: python manage.py shell -c 'from sch_pipeline.models import StopTime, Stop; print(f\"StopTimes: {StopTime.objects.count()}, Stops with coords: {Stop.objects.exclude(stop_lat__isnull=True).count()}\")'\n" + "\nTry adjusting parameters:\n" + " - Increase --distance-threshold (current: {})m\n" + " - Reduce --min-observations (current: {})\n" + " - Increase --max-stops-ahead (current: {})\n" + " - Set --vp-sample-interval to 0 to use all VPs".format( + distance_threshold, min_obs, max_stops_ahead + ) + ) + ) + return + + # Display summary statistics + self.stdout.write("\n" + "="*60) + self.stdout.write(self.style.SUCCESS("Dataset Summary:")) + self.stdout.write(f" Total rows: {len(df):,}") + self.stdout.write(f" Unique trips: {df['trip_id'].nunique():,}") + self.stdout.write(f" Unique routes: {df['route_id'].nunique()}") + self.stdout.write(f" Unique vehicles: {df['vehicle_id'].nunique():,}") + self.stdout.write(f" Unique stops: {df['stop_id'].nunique():,}") + + if "time_to_arrival_seconds" in df.columns: + tta_stats = df["time_to_arrival_seconds"].describe() + self.stdout.write(f"\n Time-to-arrival statistics:") + self.stdout.write(f" Mean: {tta_stats['mean']:.1f}s ({tta_stats['mean']/60:.1f} min)") + self.stdout.write(f" Median: {tta_stats['50%']:.1f}s ({tta_stats['50%']/60:.1f} min)") + self.stdout.write(f" Std: {tta_stats['std']:.1f}s") + self.stdout.write(f" Min: {tta_stats['min']:.1f}s") + self.stdout.write(f" Max: {tta_stats['max']:.1f}s ({tta_stats['max']/60:.1f} min)") + + if "distance_to_stop" in df.columns: + dist_stats = df["distance_to_stop"].describe() + self.stdout.write(f"\n Distance-to-stop statistics:") + self.stdout.write(f" Mean: {dist_stats['mean']:.1f}m") + self.stdout.write(f" Median: {dist_stats['50%']:.1f}m") + self.stdout.write(f" Min: {dist_stats['min']:.1f}m") + self.stdout.write(f" Max: {dist_stats['max']:.1f}m") + + if "current_speed_kmh" in df.columns: + speed_stats = df[df["current_speed_kmh"] > 0]["current_speed_kmh"].describe() + if not speed_stats.empty: + self.stdout.write(f"\n Speed statistics (km/h):") + self.stdout.write(f" Mean: {speed_stats['mean']:.1f}") + self.stdout.write(f" Median: {speed_stats['50%']:.1f}") + + missing = df.isnull().sum() + if missing.any(): + self.stdout.write(f"\n Missing values:") + for col, count in missing[missing > 0].items(): + pct = 100 * count / len(df) + self.stdout.write(f" {col}: {count:,} ({pct:.1f}%)") + + self.stdout.write("="*60 + "\n") + + # Save dataset + try: + save_dataset(df, out) + self.stdout.write( + self.style.SUCCESS(f"βœ“ Successfully saved to {out}") + ) + + # Provide guidance on next steps + self.stdout.write("\n" + self.style.NOTICE("Next steps:")) + self.stdout.write(" 1. Inspect the dataset: ") + self.stdout.write(f" import pandas as pd; df = pd.read_parquet('{out}'); df.head()") + self.stdout.write(" 2. Check feature distributions and correlations") + self.stdout.write(" 3. Train a model predicting 'time_to_arrival_seconds' from:") + self.stdout.write(" - distance_to_stop") + self.stdout.write(" - current_speed_kmh") + self.stdout.write(" - temporal features (hour, is_peak_hour, etc.)") + self.stdout.write(" - operational features (headway_seconds)") + self.stdout.write(" - weather features (if enabled)") + + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Failed to save dataset: {e}") + ) + import traceback + self.stdout.write(traceback.format_exc()) + return \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/models.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/models.py new file mode 100644 index 0000000..38efd8b --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/models.py @@ -0,0 +1,94 @@ +from django.db import models +import uuid + +class RawMessage(models.Model): + # Message type choices + MESSAGE_TYPE_VEHICLE_POSITIONS = 'VP' + MESSAGE_TYPE_TRIP_UPDATES = 'TU' + MESSAGE_TYPE_CHOICES = [ + (MESSAGE_TYPE_VEHICLE_POSITIONS, 'Vehicle Positions'), + (MESSAGE_TYPE_TRIP_UPDATES, 'Trip Updates'), + ] + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + feed_name = models.TextField() + message_type = models.CharField(max_length=3, choices=MESSAGE_TYPE_CHOICES) + fetched_at = models.DateTimeField(auto_now_add=True) + header_timestamp = models.DateTimeField(null=True, blank=True) + incrementality = models.TextField(null=True, blank=True) + content = models.BinaryField() + content_hash = models.CharField(max_length=64) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["feed_name", "message_type", "content_hash"], + name="uq_feed_type_hash" + ) + ] + indexes = [ + models.Index(fields=["feed_name", "message_type", "-fetched_at"]), + ] + +class VehiclePosition(models.Model): + feed_name = models.TextField() + vehicle_id = models.TextField() + ts = models.DateTimeField() + lat = models.FloatField(null=True, blank=True) + lon = models.FloatField(null=True, blank=True) + bearing = models.FloatField(null=True, blank=True) + speed = models.FloatField(null=True, blank=True) + route_id = models.TextField(null=True, blank=True) + trip_id = models.TextField(null=True, blank=True) + current_stop_sequence = models.IntegerField(null=True, blank=True) + raw_message = models.ForeignKey(RawMessage, null=True, on_delete=models.SET_NULL) + ingested_at = models.DateTimeField(auto_now_add=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["feed_name", "vehicle_id", "ts"], name="uq_vp_natkey" + ) + ] + indexes = [ + models.Index(fields=["vehicle_id", "-ts"]), + models.Index(fields=["route_id", "-ts"]), + ] + +class TripUpdate(models.Model): + feed_name = models.TextField() + + # trip_update header-level + ts = models.DateTimeField(null=True, blank=True) # entity.trip_update.timestamp (UTC) + trip_id = models.TextField(null=True, blank=True) + route_id = models.TextField(null=True, blank=True) + start_time = models.TextField(null=True, blank=True) + start_date = models.TextField(null=True, blank=True) + schedule_relationship = models.TextField(null=True, blank=True) # header-level SR + + vehicle_id = models.TextField(null=True, blank=True) + + stop_sequence = models.IntegerField(null=True, blank=True) + stop_id = models.TextField(null=True, blank=True) + arrival_delay = models.IntegerField(null=True, blank=True) + arrival_time = models.DateTimeField(null=True, blank=True) # UTC + departure_delay = models.IntegerField(null=True, blank=True) + departure_time = models.DateTimeField(null=True, blank=True) # UTC + stu_schedule_relationship = models.TextField(null=True, blank=True) + + raw_message = models.ForeignKey('RawMessage', null=True, on_delete=models.SET_NULL) + ingested_at = models.DateTimeField(auto_now_add=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["feed_name", "trip_id", "ts", "stop_sequence"], + name="uq_tu_natkey", + ) + ] + indexes = [ + models.Index(fields=["trip_id", "-ts"]), + models.Index(fields=["route_id", "-ts"]), + models.Index(fields=["vehicle_id", "-ts"]), + models.Index(fields=["stop_id", "-ts"]), + ] \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tasks.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tasks.py new file mode 100644 index 0000000..31d36b6 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tasks.py @@ -0,0 +1,219 @@ +from celery import shared_task +from django.conf import settings +import requests +from google.transit import gtfs_realtime_pb2 +from datetime import datetime, timezone +from django.db import transaction, IntegrityError +from .models import RawMessage, VehiclePosition, TripUpdate +import hashlib + +def _sha256_hex(b: bytes) -> str: return hashlib.sha256(b).hexdigest() +def _to_ts(sec): return datetime.fromtimestamp(sec, tz=timezone.utc) if sec else None +def _now(): return datetime.now(timezone.utc) + +# Vehicle Positions tasks +@shared_task(bind=True, autoretry_for=(requests.RequestException,), retry_backoff=True, retry_kwargs={"max_retries": 5}) +def fetch_vehicle_positions(self): + url = settings.GTFSRT_VEHICLE_POSITIONS_URL + r = requests.get(url, timeout=(settings.HTTP_CONNECT_TIMEOUT, settings.HTTP_READ_TIMEOUT)) + r.raise_for_status() + if not r.content: + return {"skipped": True} + + h = _sha256_hex(r.content) + + # Parse header early + feed = gtfs_realtime_pb2.FeedMessage() + feed.ParseFromString(r.content) + header_ts = _to_ts(feed.header.timestamp) if feed.HasField("header") else None + inc = None + if feed.HasField("header"): + inc_val = feed.header.incrementality # this is an int + try: + # Map int -> enum name (e.g., 0 -> "FULL_DATASET", 1 -> "DIFFERENTIAL") + inc = gtfs_realtime_pb2.FeedHeader.Incrementality.Name(inc_val) + except Exception: + # Fallback: store the raw int as string if mapping not available + inc = str(inc_val) + + try: + with transaction.atomic(): + obj = RawMessage.objects.create( + feed_name=settings.FEED_NAME, + message_type=RawMessage.MESSAGE_TYPE_VEHICLE_POSITIONS, + header_timestamp=header_ts, + incrementality=inc, + content=r.content, + content_hash=h, + ) + except IntegrityError: + # duplicate blob + existing = RawMessage.objects.filter( + feed_name=settings.FEED_NAME, + message_type=RawMessage.MESSAGE_TYPE_VEHICLE_POSITIONS, + content_hash=h + ).first() + return {"created": False, "raw_message_id": str(existing.id) if existing else None} + + parse_and_upsert_vehicle_positions.delay(str(obj.id)) + return {"created": True, "raw_message_id": str(obj.id)} + +@shared_task(bind=True) +def parse_and_upsert_vehicle_positions(self, raw_message_id: str): + raw = RawMessage.objects.filter(id=raw_message_id).only("content").first() + if not raw: return {"error": "raw_not_found"} + + feed = gtfs_realtime_pb2.FeedMessage() + feed.ParseFromString(bytes(raw.content)) + + rows = [] + for ent in feed.entity: + if not ent.HasField("vehicle"): + continue + v = ent.vehicle + vh_id = (v.vehicle.id or v.vehicle.label or ent.id or "unknown").strip() + ts = _to_ts(v.timestamp) or _now() + lat = v.position.latitude if v.HasField("position") else None + lon = v.position.longitude if v.HasField("position") else None + bearing = v.position.bearing if v.HasField("position") else None + speed = v.position.speed if v.HasField("position") else None + route_id = v.trip.route_id if v.HasField("trip") else None + trip_id = v.trip.trip_id if v.HasField("trip") else None + css = v.current_stop_sequence if v.HasField("current_stop_sequence") else None + + rows.append(VehiclePosition( + feed_name=settings.FEED_NAME, vehicle_id=vh_id, ts=ts, lat=lat, lon=lon, + bearing=bearing, speed=speed, route_id=route_id, trip_id=trip_id, + current_stop_sequence=css, raw_message_id=raw_message_id + )) + + if not rows: + return {"inserted": 0} + + # Bulk insert with conflict handling (Django 5+) + # We'll try bulk_create(ignore_conflicts=True) then optional updates + inserted = 0 + try: + VehiclePosition.objects.bulk_create(rows, ignore_conflicts=True, batch_size=2000) + inserted = len(rows) # approximate; conflicts ignored + except Exception as e: + return {"error": str(e)} + + return {"inserted": inserted} + +# Trip Updates tasks +@shared_task(bind=True, autoretry_for=(requests.RequestException,), retry_backoff=True, retry_kwargs={"max_retries": 5}) +def fetch_trip_updates(self): + """Download TU .pb and store a RawMessage. Enqueue parse task only for NEW content.""" + url = getattr(settings, "GTFSRT_TRIP_UPDATES_URL", None) + if not url: + return {"error": "GTFSRT_TRIP_UPDATES_URL not configured"} + + resp = requests.get(url, timeout=20) + resp.raise_for_status() + raw_bytes = resp.content + content_hash = _sha256_hex(raw_bytes) + + feed = gtfs_realtime_pb2.FeedMessage() + feed.ParseFromString(raw_bytes) + + header_ts = _to_ts(getattr(feed.header, "timestamp", None)) + incr = str(getattr(feed.header, "incrementality", "")) if hasattr(feed.header, "incrementality") else None + + try: + with transaction.atomic(): + raw = RawMessage.objects.create( + feed_name=settings.FEED_NAME, + message_type=RawMessage.MESSAGE_TYPE_TRIP_UPDATES, + content_hash=content_hash, + header_timestamp=header_ts, + incrementality=incr, + content=raw_bytes + ) + except IntegrityError: + # duplicate blob + existing = RawMessage.objects.filter( + feed_name=settings.FEED_NAME, + message_type=RawMessage.MESSAGE_TYPE_TRIP_UPDATES, + content_hash=content_hash + ).first() + return {"created": False, "raw_message_id": str(existing.id) if existing else None} + + # only parse if this payload is new + parse_and_upsert_trip_updates.delay(str(raw.id)) + return {"created": True, "raw_message_id": str(raw.id), "hash": content_hash} + +@shared_task(bind=True) +def parse_and_upsert_trip_updates(self, raw_id: str): + """Parse a stored RawMessage (TU) into TripUpdate rows (one per StopTimeUpdate).""" + raw = RawMessage.objects.get(id=raw_id) + feed = gtfs_realtime_pb2.FeedMessage() + feed.ParseFromString(bytes(raw.content)) + + rows = [] + for ent in feed.entity: + if not ent.HasField("trip_update"): + continue + tu = ent.trip_update + tu_ts = _to_ts(getattr(tu, "timestamp", None)) + + trip = getattr(tu, "trip", None) + veh = getattr(tu, "vehicle", None) + + trip_id = getattr(trip, "trip_id", None) or None + route_id = getattr(trip, "route_id", None) or None + start_time = getattr(trip, "start_time", None) or None + start_date = getattr(trip, "start_date", None) or None + header_sr = str(getattr(trip, "schedule_relationship", "")) if trip else None + vehicle_id = getattr(veh, "id", None) or None + + for stu in getattr(tu, "stop_time_update", []): + stop_sequence = getattr(stu, "stop_sequence", None) + stop_id = getattr(stu, "stop_id", None) or None + + arr = getattr(stu, "arrival", None) + dep = getattr(stu, "departure", None) + + arrival_delay = getattr(arr, "delay", None) if arr else None + arrival_time = _to_ts(getattr(arr, "time", None)) if arr and getattr(arr, "time", None) else None + departure_delay = getattr(dep, "delay", None) if dep else None + departure_time = _to_ts(getattr(dep, "time", None)) if dep and getattr(dep, "time", None) else None + stu_sr = str(getattr(stu, "schedule_relationship", "")) if stu else None + + rows.append(TripUpdate( + feed_name=settings.FEED_NAME, + ts=tu_ts, + trip_id=trip_id, + route_id=route_id, + start_time=start_time, + start_date=start_date, + schedule_relationship=header_sr, + vehicle_id=vehicle_id, + stop_sequence=stop_sequence, + stop_id=stop_id, + arrival_delay=arrival_delay, + arrival_time=arrival_time, + departure_delay=departure_delay, + departure_time=departure_time, + stu_schedule_relationship=stu_sr, + raw_message=raw, + )) + + if rows: + TripUpdate.objects.bulk_create(rows, ignore_conflicts=True) + return {"parsed_rows": len(rows)} + +# ---- Celery Beat schedule ---- +from celery.schedules import schedule +from ingestproj.celery import app as celery_app + +celery_app.conf.beat_schedule = { + "poll-vehicle-positions": { + "task": "rt_pipeline.tasks.fetch_vehicle_positions", + "schedule": schedule(run_every=settings.POLL_SECONDS), + }, + "poll-trip-updates": { + "task": "rt_pipeline.tasks.fetch_trip_updates", + "schedule": schedule(run_every=settings.POLL_SECONDS), + }, +} \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tests.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/eta_prediction/gtfs-rt-pipeline/rt_pipeline/views.py b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/rt_pipeline/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/admin.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/admin.py new file mode 100644 index 0000000..3a314be --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/admin.py @@ -0,0 +1,306 @@ +from django.contrib import admin +from django.contrib.gis.admin import GISModelAdmin +from .models import ( + GTFSProvider, Feed, Agency, Stop, Route, Calendar, CalendarDate, + Shape, GeoShape, Trip, StopTime, FareAttribute, FareRule, + FeedInfo, RouteStop, TripDuration, TripTime +) + + +@admin.register(GTFSProvider) +class GTFSProviderAdmin(admin.ModelAdmin): + list_display = ('code', 'name', 'timezone', 'is_active') + list_filter = ('is_active',) + search_fields = ('code', 'name') + fieldsets = ( + ('Basic Information', { + 'fields': ('code', 'name', 'description', 'website', 'timezone', 'is_active') + }), + ('Feed URLs', { + 'fields': ( + 'schedule_url', + 'trip_updates_url', + 'vehicle_positions_url', + 'service_alerts_url' + ) + }), + ) + + +@admin.register(Feed) +class FeedAdmin(admin.ModelAdmin): + list_display = ('feed_id', 'gtfs_provider', 'is_current', 'retrieved_at') + list_filter = ('is_current', 'gtfs_provider') + search_fields = ('feed_id',) + readonly_fields = ('retrieved_at',) + date_hierarchy = 'retrieved_at' + + +@admin.register(Agency) +class AgencyAdmin(admin.ModelAdmin): + list_display = ('agency_name', 'agency_id', 'feed', 'agency_timezone') + list_filter = ('feed', 'agency_timezone') + search_fields = ('agency_id', 'agency_name') + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'agency_id', 'agency_name') + }), + ('Contact Information', { + 'fields': ('agency_url', 'agency_phone', 'agency_email', 'agency_fare_url') + }), + ('Regional Settings', { + 'fields': ('agency_timezone', 'agency_lang') + }), + ) + + +@admin.register(Stop) +class StopAdmin(GISModelAdmin): + list_display = ('stop_id', 'stop_name', 'feed', 'stop_lat', 'stop_lon', 'location_type') + list_filter = ('feed', 'location_type', 'wheelchair_boarding') + search_fields = ('stop_id', 'stop_name', 'stop_code') + readonly_fields = ('stop_point',) + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'stop_id', 'stop_code', 'stop_name') + }), + ('Location', { + 'fields': ('stop_lat', 'stop_lon', 'stop_point', 'zone_id') + }), + ('Type & Hierarchy', { + 'fields': ('location_type', 'parent_station') + }), + ('Accessibility', { + 'fields': ('wheelchair_boarding', 'platform_code') + }), + ('Amenities', { + 'fields': ('shelter', 'bench', 'lit', 'bay', 'device_charging_station'), + 'classes': ('collapse',) + }), + ('Additional Info', { + 'fields': ('stop_desc', 'stop_url', 'stop_heading', 'stop_timezone'), + 'classes': ('collapse',) + }), + ) + gis_widget_kwargs = { + 'attrs': { + 'default_zoom': 12, + } + } + + +@admin.register(Route) +class RouteAdmin(admin.ModelAdmin): + list_display = ('route_short_name', 'route_long_name', 'route_type', 'feed', '_agency') + list_filter = ('feed', 'route_type', '_agency') + search_fields = ('route_id', 'route_short_name', 'route_long_name') + readonly_fields = ('_agency',) + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'route_id', 'agency_id', '_agency') + }), + ('Names', { + 'fields': ('route_short_name', 'route_long_name', 'route_desc') + }), + ('Display', { + 'fields': ('route_type', 'route_color', 'route_text_color', 'route_sort_order') + }), + ('Additional', { + 'fields': ('route_url',), + 'classes': ('collapse',) + }), + ) + + +@admin.register(Calendar) +class CalendarAdmin(admin.ModelAdmin): + list_display = ('service_id', 'feed', 'start_date', 'end_date', 'weekday_summary') + list_filter = ('feed', 'start_date', 'end_date') + search_fields = ('service_id',) + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'service_id') + }), + ('Service Days', { + 'fields': ( + ('monday', 'tuesday', 'wednesday', 'thursday'), + ('friday', 'saturday', 'sunday') + ) + }), + ('Date Range', { + 'fields': ('start_date', 'end_date') + }), + ) + + def weekday_summary(self, obj): + days = [] + if obj.monday: days.append('Mon') + if obj.tuesday: days.append('Tue') + if obj.wednesday: days.append('Wed') + if obj.thursday: days.append('Thu') + if obj.friday: days.append('Fri') + if obj.saturday: days.append('Sat') + if obj.sunday: days.append('Sun') + return ', '.join(days) if days else 'No days' + weekday_summary.short_description = 'Service Days' + + +@admin.register(CalendarDate) +class CalendarDateAdmin(admin.ModelAdmin): + list_display = ('service_id', 'date', 'exception_type', 'holiday_name', 'feed') + list_filter = ('feed', 'exception_type', 'date') + search_fields = ('service_id', 'holiday_name') + date_hierarchy = 'date' + readonly_fields = ('_service',) + + +@admin.register(Shape) +class ShapeAdmin(admin.ModelAdmin): + list_display = ('shape_id', 'shape_pt_sequence', 'feed', 'shape_pt_lat', 'shape_pt_lon') + list_filter = ('feed',) + search_fields = ('shape_id',) + ordering = ('shape_id', 'shape_pt_sequence') + + +@admin.register(GeoShape) +class GeoShapeAdmin(GISModelAdmin): + list_display = ('shape_id', 'feed', 'shape_name', 'has_altitude') + list_filter = ('feed', 'has_altitude') + search_fields = ('shape_id', 'shape_name') + readonly_fields = ('geometry',) + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'shape_id') + }), + ('Metadata', { + 'fields': ('shape_name', 'shape_desc', 'shape_from', 'shape_to', 'has_altitude') + }), + ('Geometry', { + 'fields': ('geometry',) + }), + ) + + +@admin.register(Trip) +class TripAdmin(admin.ModelAdmin): + list_display = ('trip_id', 'route_id', 'service_id', 'trip_headsign', 'direction_id', 'feed') + list_filter = ('feed', 'direction_id', 'wheelchair_accessible', 'bikes_allowed') + search_fields = ('trip_id', 'trip_headsign', 'route_id') + readonly_fields = ('_route', '_service', 'geoshape') + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'trip_id', 'route_id', '_route') + }), + ('Service', { + 'fields': ('service_id', '_service', 'direction_id') + }), + ('Display', { + 'fields': ('trip_headsign', 'trip_short_name') + }), + ('Shape & Block', { + 'fields': ('shape_id', 'geoshape', 'block_id'), + 'classes': ('collapse',) + }), + ('Accessibility', { + 'fields': ('wheelchair_accessible', 'bikes_allowed') + }), + ) + + +@admin.register(StopTime) +class StopTimeAdmin(admin.ModelAdmin): + list_display = ('trip_id', 'stop_sequence', 'stop_id', 'arrival_time', 'departure_time', 'feed') + list_filter = ('feed', 'pickup_type', 'drop_off_type', 'timepoint') + search_fields = ('trip_id', 'stop_id') + readonly_fields = ('_trip', '_stop') + ordering = ('trip_id', 'stop_sequence') + fieldsets = ( + ('References', { + 'fields': ('feed', 'trip_id', '_trip', 'stop_id', '_stop') + }), + ('Sequence', { + 'fields': ('stop_sequence',) + }), + ('Times', { + 'fields': ('arrival_time', 'departure_time', 'timepoint') + }), + ('Pickup/Drop-off', { + 'fields': ('pickup_type', 'drop_off_type', 'stop_headsign') + }), + ('Distance', { + 'fields': ('shape_dist_traveled',), + 'classes': ('collapse',) + }), + ) + + +@admin.register(FareAttribute) +class FareAttributeAdmin(admin.ModelAdmin): + list_display = ('fare_id', 'price', 'currency_type', 'payment_method', 'feed') + list_filter = ('feed', 'currency_type', 'payment_method', 'transfers') + search_fields = ('fare_id',) + readonly_fields = ('_agency',) + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'fare_id', 'agency_id', '_agency') + }), + ('Price', { + 'fields': ('price', 'currency_type', 'payment_method') + }), + ('Transfers', { + 'fields': ('transfers', 'transfer_duration') + }), + ) + + +@admin.register(FareRule) +class FareRuleAdmin(admin.ModelAdmin): + list_display = ('fare_id', 'route_id', 'origin_id', 'destination_id', 'feed') + list_filter = ('feed',) + search_fields = ('fare_id', 'route_id') + readonly_fields = ('_fare', '_route') + + +@admin.register(FeedInfo) +class FeedInfoAdmin(admin.ModelAdmin): + list_display = ('feed_publisher_name', 'feed_version', 'feed', 'feed_start_date', 'feed_end_date') + list_filter = ('feed',) + search_fields = ('feed_publisher_name', 'feed_version') + fieldsets = ( + ('Identification', { + 'fields': ('feed', 'feed_publisher_name', 'feed_publisher_url') + }), + ('Version & Dates', { + 'fields': ('feed_version', 'feed_start_date', 'feed_end_date') + }), + ('Language & Contact', { + 'fields': ('feed_lang', 'feed_contact_email', 'feed_contact_url') + }), + ) + + +@admin.register(RouteStop) +class RouteStopAdmin(admin.ModelAdmin): + list_display = ('route_id', 'stop_id', 'stop_sequence', 'direction_id', 'timepoint', 'feed') + list_filter = ('feed', 'direction_id', 'timepoint') + search_fields = ('route_id', 'stop_id') + readonly_fields = ('_route', '_shape', '_stop') + ordering = ('route_id', 'shape_id', 'stop_sequence') + + +@admin.register(TripDuration) +class TripDurationAdmin(admin.ModelAdmin): + list_display = ('route_id', 'service_id', 'start_time', 'end_time', 'stretch', 'stretch_duration', 'feed') + list_filter = ('feed',) + search_fields = ('route_id', 'service_id') + readonly_fields = ('_route', '_shape', '_service') + ordering = ('route_id', 'start_time', 'stretch') + + +@admin.register(TripTime) +class TripTimeAdmin(admin.ModelAdmin): + list_display = ('trip_id', 'stop_id', 'stop_sequence', 'departure_time', 'feed') + list_filter = ('feed',) + search_fields = ('trip_id', 'stop_id') + readonly_fields = ('_trip', '_stop') + ordering = ('trip_id', 'stop_sequence') \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/fixtures/initial_providers.json b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/fixtures/initial_providers.json new file mode 100644 index 0000000..ba0264a --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/fixtures/initial_providers.json @@ -0,0 +1,17 @@ +[ + { + "model": "sch_pipeline.gtfsprovider", + "pk": 1, + "fields": { + "code": "mbta", + "name": "Massachusetts Bay Transportation Authority", + "description": "Boston area transit authority", + "website": "https://www.mbta.com", + "timezone": "America/New_York", + "is_active": true, + "schedule_url": "https://cdn.mbta.com/MBTA_GTFS.zip", + "vehicle_positions_url": "https://cdn.mbta.com/realtime/VehiclePositions.pb", + "trip_updates_url": "https://cdn.mbta.com/realtime/TripUpdates.pb" + } + } +] \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/__init__.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/__init__.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/import_gtfs.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/import_gtfs.py new file mode 100644 index 0000000..2f6b9f9 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/management/commands/import_gtfs.py @@ -0,0 +1,283 @@ +import os +import io +import csv +import zipfile +from datetime import datetime +from decimal import Decimal + +import requests +from django.core.management.base import BaseCommand +from django.db import transaction +from django.contrib.gis.geos import Point, LineString + +from sch_pipeline.models import ( + Feed, Agency, Stop, Route, Calendar, CalendarDate, + Shape, GeoShape, Trip, StopTime, GTFSProvider +) + + +class Command(BaseCommand): + help = 'Download and import GTFS static schedule data' + + def add_arguments(self, parser): + parser.add_argument( + '--url', + type=str, + help='GTFS zip URL (overrides .env)', + ) + parser.add_argument( + '--provider-id', + type=int, + default=1, + help='GTFSProvider ID to associate with this feed', + ) + + def handle(self, *args, **options): + url = options['url'] or os.getenv('GTFS_SCHEDULE_ZIP_URL') + provider_id = options['provider_id'] + + if not url: + self.stdout.write(self.style.ERROR('No GTFS URL provided')) + return + + self.stdout.write(f'Downloading {url}...') + + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + except Exception as e: + self.stdout.write(self.style.ERROR(f'Download failed: {e}')) + return + + # Create feed ID + feed_id = f"{os.getenv('FEED_NAME', 'gtfs')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + self.stdout.write(f'Importing as feed: {feed_id}') + + try: + with zipfile.ZipFile(io.BytesIO(response.content)) as zf: + importer = GTFSImporter(feed_id, provider_id, zf, self.stdout) + importer.import_all() + except Exception as e: + self.stdout.write(self.style.ERROR(f'Import failed: {e}')) + raise + + self.stdout.write(self.style.SUCCESS(f'Successfully imported {feed_id}')) + + +class GTFSImporter: + def __init__(self, feed_id, provider_id, zipfile, stdout): + self.feed_id = feed_id + self.provider_id = provider_id + self.zipfile = zipfile + self.stdout = stdout + self.feed = None + + def import_all(self): + """Import in dependency order""" + with transaction.atomic(): + self.import_feed() + self.import_agencies() + self.import_stops() + self.import_routes() + self.import_calendar() + self.import_calendar_dates() + self.import_shapes() + self.import_trips() + self.import_stop_times() + + def import_feed(self): + self.stdout.write('Creating Feed...') + provider = GTFSProvider.objects.get(provider_id=self.provider_id) + self.feed = Feed.objects.create( + feed_id=self.feed_id, + gtfs_provider=provider, + is_current=True + ) + + def import_agencies(self): + self.stdout.write('Importing agencies...') + with self.zipfile.open('agency.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + agencies = [] + for row in reader: + agencies.append(Agency( + feed=self.feed, + agency_id=row.get('agency_id', ''), + agency_name=row['agency_name'], + agency_url=row['agency_url'], + agency_timezone=row['agency_timezone'], + agency_lang=row.get('agency_lang', ''), + agency_phone=row.get('agency_phone', ''), + agency_fare_url=row.get('agency_fare_url', ''), + agency_email=row.get('agency_email', ''), + )) + Agency.objects.bulk_create(agencies, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(agencies)} agencies') + + def import_stops(self): + self.stdout.write('Importing stops...') + with self.zipfile.open('stops.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + stops = [] + for row in reader: + lat = Decimal(row['stop_lat']) if row.get('stop_lat') else None + lon = Decimal(row['stop_lon']) if row.get('stop_lon') else None + + stops.append(Stop( + feed=self.feed, + stop_id=row['stop_id'], + stop_code=row.get('stop_code', ''), + stop_name=row['stop_name'], + stop_lat=lat, + stop_lon=lon, + stop_point=Point(float(lon), float(lat)) if lat and lon else None, + location_type=int(row.get('location_type', 0)), + parent_station=row.get('parent_station', ''), + wheelchair_boarding=int(row.get('wheelchair_boarding', 0)), + )) + Stop.objects.bulk_create(stops, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(stops)} stops') + + def import_routes(self): + self.stdout.write('Importing routes...') + with self.zipfile.open('routes.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + routes = [] + for row in reader: + routes.append(Route( + feed=self.feed, + route_id=row['route_id'], + agency_id=row.get('agency_id', ''), + route_short_name=row.get('route_short_name', ''), + route_long_name=row.get('route_long_name', ''), + route_type=int(row.get('route_type', 3)), + route_color=row.get('route_color', ''), + )) + Route.objects.bulk_create(routes, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(routes)} routes') + + def import_calendar(self): + self.stdout.write('Importing calendar...') + try: + with self.zipfile.open('calendar.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + calendars = [] + for row in reader: + calendars.append(Calendar( + feed=self.feed, + service_id=row['service_id'], + monday=row['monday'] == '1', + tuesday=row['tuesday'] == '1', + wednesday=row['wednesday'] == '1', + thursday=row['thursday'] == '1', + friday=row['friday'] == '1', + saturday=row['saturday'] == '1', + sunday=row['sunday'] == '1', + start_date=datetime.strptime(row['start_date'], '%Y%m%d').date(), + end_date=datetime.strptime(row['end_date'], '%Y%m%d').date(), + )) + Calendar.objects.bulk_create(calendars, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(calendars)} calendar entries') + except KeyError: + self.stdout.write(' No calendar.txt found (optional)') + + def import_calendar_dates(self): + self.stdout.write('Importing calendar dates...') + try: + with self.zipfile.open('calendar_dates.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + dates = [] + for row in reader: + dates.append(CalendarDate( + feed=self.feed, + service_id=row['service_id'], + date=datetime.strptime(row['date'], '%Y%m%d').date(), + exception_type=int(row['exception_type']), + )) + CalendarDate.objects.bulk_create(dates, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(dates)} calendar date exceptions') + except KeyError: + self.stdout.write(' No calendar_dates.txt found (optional)') + + def import_shapes(self): + self.stdout.write('Importing shapes...') + try: + with self.zipfile.open('shapes.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + shapes = [] + for row in reader: + shapes.append(Shape( + feed=self.feed, + shape_id=row['shape_id'], + shape_pt_lat=Decimal(row['shape_pt_lat']), + shape_pt_lon=Decimal(row['shape_pt_lon']), + shape_pt_sequence=int(row['shape_pt_sequence']), + )) + Shape.objects.bulk_create(shapes, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(shapes)} shape points') + except KeyError: + self.stdout.write(' No shapes.txt found (optional)') + + def import_trips(self): + self.stdout.write('Importing trips...') + with self.zipfile.open('trips.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + trips = [] + for row in reader: + trips.append(Trip( + feed=self.feed, + route_id=row['route_id'], + service_id=row['service_id'], + trip_id=row['trip_id'], + trip_headsign=row.get('trip_headsign', ''), + direction_id=int(row.get('direction_id', 0)), + shape_id=row.get('shape_id', ''), + wheelchair_accessible=int(row.get('wheelchair_accessible', 0)), + bikes_allowed=int(row.get('bikes_allowed', 0)), + )) + Trip.objects.bulk_create(trips, batch_size=1000, ignore_conflicts=True) + self.stdout.write(f' Imported {len(trips)} trips') + + def import_stop_times(self): + self.stdout.write('Importing stop times (this may take a while)...') + with self.zipfile.open('stop_times.txt') as f: + reader = csv.DictReader(io.TextIOWrapper(f, 'utf-8-sig')) + stop_times = [] + count = 0 + for row in reader: + stop_times.append(StopTime( + feed=self.feed, + trip_id=row['trip_id'], + stop_id=row['stop_id'], + stop_sequence=int(row['stop_sequence']), + arrival_time=self._parse_time(row.get('arrival_time')), + departure_time=self._parse_time(row.get('departure_time')), + pickup_type=int(row.get('pickup_type', 0)), + drop_off_type=int(row.get('drop_off_type', 0)), + )) + + if len(stop_times) >= 5000: + StopTime.objects.bulk_create(stop_times, batch_size=5000, ignore_conflicts=True) + count += len(stop_times) + self.stdout.write(f' {count} stop times...', ending='\r') + stop_times = [] + + if stop_times: + StopTime.objects.bulk_create(stop_times, batch_size=5000, ignore_conflicts=True) + count += len(stop_times) + + self.stdout.write(f' Imported {count} stop times') + + def _parse_time(self, time_str): + """Parse GTFS time format (HH:MM:SS, may be >24 hours)""" + if not time_str: + return None + try: + h, m, s = time_str.split(':') + # GTFS allows times > 24 hours (e.g., 25:30:00 for 1:30 AM next day) + # Django TimeField can't handle this, so we cap at 23:59:59 + hours = min(int(h), 23) + return f"{hours:02d}:{m}:{s}" + except: + return None \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/models.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/models.py new file mode 100644 index 0000000..35965a7 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/models.py @@ -0,0 +1,1085 @@ +import re +from django.db.models import UniqueConstraint +from django.core.exceptions import ValidationError +from django.contrib.gis.db import models +from django.contrib.gis.geos import Point + + +def validate_no_spaces_or_special_symbols(value): + if re.search(r"[^a-zA-Z0-9_]", value): + raise ValidationError( + "Este campo no puede contener espacios ni sΓ­mbolos especiales, solamente letras, nΓΊmeros y guiones bajos." + ) + + +class GTFSProvider(models.Model): + """A provider provides transportation services GTFS data. + + It might or might not be the same as the agency in the GTFS feed. A GTFS provider can serve multiple agencies. + """ + + provider_id = models.BigAutoField(primary_key=True) + code = models.CharField( + max_length=31, + help_text="CΓ³digo (tΓ­picamente el acrΓ³nimo) de la empresa. No debe tener espacios ni sΓ­mbolos especiales.", + validators=[validate_no_spaces_or_special_symbols], + ) + name = models.CharField(max_length=255, help_text="Nombre de la empresa.") + description = models.TextField( + blank=True, null=True, help_text="DescripciΓ³n de la instituciΓ³n o empresa." + ) + website = models.URLField( + blank=True, null=True, help_text="Sitio web de la empresa." + ) + schedule_url = models.URLField( + blank=True, + null=True, + help_text="URL del suministro (Feed) de GTFS Schedule (.zip).", + ) + trip_updates_url = models.URLField( + blank=True, + null=True, + help_text="URL del suministro (FeedMessage) de la entidad GTFS Realtime TripUpdates (.pb).", + ) + vehicle_positions_url = models.URLField( + blank=True, + null=True, + help_text="URL del suministro (FeedMessage) de la entidad GTFS Realtime VehiclePositions (.pb).", + ) + service_alerts_url = models.URLField( + blank=True, + null=True, + help_text="URL del suministro (FeedMessage) de la entidad GTFS Realtime ServiceAlerts (.pb).", + ) + timezone = models.CharField( + max_length=63, + help_text="Zona horaria del proveedor de datos (asume misma zona horaria para todas las agencias). Ejemplo: America/Costa_Rica.", + ) + is_active = models.BooleanField( + default=False, + help_text="ΒΏEstΓ‘ activo el proveedor de datos? Si no, no se importarΓ‘n los datos de este proveedor.", + ) + + def __str__(self): + return f"{self.name} ({self.code})" + + +# ------------- +# GTFS Schedule +# ------------- + + +class Feed(models.Model): + feed_id = models.CharField(max_length=100, primary_key=True, unique=True) + gtfs_provider = models.ForeignKey( + GTFSProvider, on_delete=models.SET_NULL, blank=True, null=True + ) + http_etag = models.CharField(max_length=1023, blank=True, null=True) + http_last_modified = models.DateTimeField(blank=True, null=True) + is_current = models.BooleanField(blank=True, null=True) + retrieved_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.feed_id + + +class Agency(models.Model): + """One or more transit agencies that provide the data in this feed. + Maps to agency.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, to_field="feed_id", on_delete=models.CASCADE) + agency_id = models.CharField( + max_length=255, + blank=True, + help_text="Identificador ΓΊnico de la agencia de transportes.", + ) + agency_name = models.CharField( + max_length=255, help_text="Nombre completo de la agencia de transportes." + ) + agency_url = models.URLField(help_text="URL de la agencia de transportes.") + agency_timezone = models.CharField( + max_length=255, help_text="Zona horaria de la agencia de transportes." + ) + agency_lang = models.CharField( + max_length=2, blank=True, help_text="CΓ³digo ISO 639-1 de idioma primario." + ) + agency_phone = models.CharField( + max_length=127, blank=True, null=True, help_text="NΓΊmero de telΓ©fono." + ) + agency_fare_url = models.URLField( + blank=True, null=True, help_text="URL para la compra de tiquetes en lΓ­nea." + ) + agency_email = models.EmailField( + max_length=254, + blank=True, + null=True, + help_text="Correo electrΓ³nico de servicio al cliente.", + ) + + class Meta: + constraints = [ + UniqueConstraint(fields=["feed", "agency_id"], name="unique_agency_in_feed") + ] + verbose_name = "agency" + verbose_name_plural = "agencies" + + def __str__(self): + return self.agency_name + + +class Stop(models.Model): + """Individual locations where vehicles pick up or drop off riders. + Maps to stops.txt in the GTFS feed. + """ + + STOP_HEADING_CHOICES = ( + ("N", "norte"), + ("NE", "noreste"), + ("E", "este"), + ("SE", "sureste"), + ("S", "sur"), + ("SW", "suroeste"), + ("W", "oeste"), + ("NW", "noroeste"), + ) + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + stop_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la parada." + ) + stop_code = models.CharField( + max_length=255, blank=True, null=True, help_text="CΓ³digo de la parada." + ) + stop_name = models.CharField(max_length=255, help_text="Nombre de la parada.") + stop_heading = models.CharField( + max_length=2, blank=True, null=True, choices=STOP_HEADING_CHOICES + ) + stop_desc = models.TextField( + blank=True, null=True, help_text="DescripciΓ³n de la parada." + ) + stop_lat = models.DecimalField( + max_digits=9, + decimal_places=6, + blank=True, + null=True, + help_text="Latitud de la parada.", + ) + stop_lon = models.DecimalField( + max_digits=9, + decimal_places=6, + blank=True, + null=True, + help_text="Longitud de la parada.", + ) + stop_point = models.PointField( + blank=True, null=True, help_text="Punto georreferenciado de la parada." + ) + zone_id = models.CharField( + max_length=255, + blank=True, + null=True, + help_text="Identificador de la zona tarifaria.", + ) + stop_url = models.URLField(blank=True, null=True, help_text="URL de la parada.") + location_type = models.PositiveIntegerField( + blank=True, + null=True, + help_text="Tipo de parada.", + choices=( + (0, "Parada o plataforma"), + (1, "EstaciΓ³n"), + (2, "Entrada o salida"), + (3, "Nodo genΓ©rico"), + (4, "Área de abordaje"), + ), + ) + parent_station = models.CharField( + max_length=255, blank=True, help_text="EstaciΓ³n principal." + ) + stop_timezone = models.CharField( + max_length=255, blank=True, help_text="Zona horaria de la parada." + ) + wheelchair_boarding = models.PositiveIntegerField( + blank=True, + null=True, + help_text="Acceso para sillas de ruedas.", + choices=((0, "No especificado"), (1, "Accesible"), (2, "No accesible")), + ) + platform_code = models.CharField( + max_length=255, blank=True, help_text="CΓ³digo de la plataforma." + ) + shelter = models.BooleanField(blank=True, null=True, help_text="Con techo.") + bench = models.BooleanField( + blank=True, null=True, help_text="Con banco para sentarse." + ) + lit = models.BooleanField(blank=True, null=True, help_text="Con iluminaciΓ³n.") + bay = models.BooleanField(blank=True, null=True, help_text="Con bahΓ­a para el bus.") + device_charging_station = models.BooleanField( + blank=True, + null=True, + help_text="Con estaciΓ³n de carga de dispositivos mΓ³viles.", + ) + + class Meta: + constraints = [ + UniqueConstraint(fields=["feed", "stop_id"], name="unique_stop_in_feed") + ] + + # Build stop_point or stop_lat and stop_lon + def save(self, *args, **kwargs): + if self.stop_point: + self.stop_lat = self.stop_point.y + self.stop_lon = self.stop_point.x + else: + self.stop_point = Point(self.stop_lon, self.stop_lat) + super(Stop, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.stop_id}: {self.stop_name}" + + +class Route(models.Model): + """A group of trips that are displayed to riders as a single service. + Maps to routes.txt in the GTFS feed. + + _agency is a field to store the Agency object related to the route. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + route_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la ruta." + ) + agency_id = models.CharField(max_length=200) + _agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) + route_short_name = models.CharField( + max_length=63, blank=True, null=True, help_text="Nombre corto de la ruta." + ) + route_long_name = models.CharField( + max_length=255, blank=True, null=True, help_text="Nombre largo de la ruta." + ) + route_desc = models.TextField( + blank=True, null=True, help_text="DescripciΓ³n de la ruta." + ) + route_type = models.PositiveIntegerField( + choices=( + (0, "TranvΓ­a o tren ligero"), + (1, "SubterrΓ‘neo o metro"), + (2, "Ferrocarril"), + (3, "Bus"), + (4, "Ferry"), + (5, "TelefΓ©rico"), + (6, "GΓ³ndola"), + (7, "Funicular"), + ), + default=3, + help_text="Modo de transporte pΓΊblico.", + ) + route_url = models.URLField( + blank=True, null=True, help_text="URL de la ruta en el sitio web de la agencia." + ) + route_color = models.CharField( + max_length=6, + blank=True, + null=True, + help_text="Color que representa la ruta en formato hexadecimal.", + ) + route_text_color = models.CharField( + max_length=6, + blank=True, + null=True, + help_text="Color del texto que representa la ruta en formato hexadecimal.", + ) + route_sort_order = models.PositiveIntegerField(blank=True, null=True) + + class Meta: + constraints = [ + UniqueConstraint(fields=["feed", "route_id"], name="unique_route_in_feed") + ] + + def save(self, *args, **kwargs): + self._agency = Agency.objects.get(feed=self.feed, agency_id=self.agency_id) + super(Route, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.route_short_name}: {self.route_long_name}" + + +class Calendar(models.Model): + """Dates for service IDs using a weekly schedule. + Maps to calendar.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + service_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico del servicio." + ) + monday = models.BooleanField(help_text="Lunes") + tuesday = models.BooleanField(help_text="Martes") + wednesday = models.BooleanField(help_text="MiΓ©rcoles") + thursday = models.BooleanField(help_text="Jueves") + friday = models.BooleanField(help_text="Viernes") + saturday = models.BooleanField(help_text="SΓ‘bado") + sunday = models.BooleanField(help_text="Domingo") + start_date = models.DateField(help_text="Fecha de inicio del servicio.") + end_date = models.DateField(help_text="Fecha de finalizaciΓ³n del servicio.") + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "service_id"], name="unique_service_in_feed" + ) + ] + + def __str__(self): + return self.service_id + + +class CalendarDate(models.Model): + """Exceptions for the service IDs defined in the calendar.txt file. + Maps to calendar_dates.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + service_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico del servicio." + ) + _service = models.ForeignKey( + Calendar, on_delete=models.CASCADE, blank=True, null=True + ) + date = models.DateField(help_text="Fecha de excepciΓ³n.") + exception_type = models.PositiveIntegerField( + choices=((1, "Agregar"), (2, "Eliminar")), help_text="Tipo de excepciΓ³n." + ) + holiday_name = models.CharField( + max_length=255, + default="Feriado", + help_text="Nombre de la festividad o feriado.", + ) + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "service_id", "date"], name="unique_date_in_feed" + ) + ] + + def save(self, *args, **kwargs): + self._service = Calendar.objects.get(feed=self.feed, service_id=self.service_id) + super(CalendarDate, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.holiday_name} ({self.service_id})" + + +class Shape(models.Model): + """Rules for drawing lines on a map to represent a transit organization's routes. + Maps to shapes.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + shape_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la trayectoria." + ) + shape_pt_lat = models.DecimalField( + max_digits=9, + decimal_places=6, + help_text="Latitud de un punto en la trayectoria.", + ) + shape_pt_lon = models.DecimalField( + max_digits=9, + decimal_places=6, + help_text="Longitud de un punto en la trayectoria.", + ) + shape_pt_sequence = models.PositiveIntegerField( + help_text="Secuencia del punto en la trayectoria." + ) + shape_dist_traveled = models.DecimalField( + max_digits=6, + decimal_places=4, + blank=True, + null=True, + help_text="Distancia recorrida en la trayectoria.", + ) + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "shape_id", "shape_pt_sequence"], + name="unique_shape_in_feed", + ) + ] + + def __str__(self): + return f"{self.shape_id}: {self.shape_pt_sequence}" + + +class GeoShape(models.Model): + """Rules for drawing lines on a map to represent a transit organization's routes. + Maps to shapes.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + shape_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la trayectoria." + ) + geometry = models.LineStringField( + help_text="Trayectoria de la ruta.", + # dim=3, # To store 3D coordinates (x, y, z) + ) + shape_name = models.CharField(max_length=255, blank=True, null=True) + shape_desc = models.TextField(blank=True, null=True) + shape_from = models.CharField(max_length=255, blank=True, null=True) + shape_to = models.CharField(max_length=255, blank=True, null=True) + has_altitude = models.BooleanField( + help_text="Indica si la trayectoria tiene datos de altitud", default=False + ) + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "shape_id"], name="unique_geoshape_in_feed" + ) + ] + + def __str__(self): + return self.shape_id + + +class Trip(models.Model): + """Trips for each route. A trip is a sequence of two or more stops that occurs at specific time. + Maps to trips.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + route_id = models.CharField(max_length=200) + _route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + service_id = models.CharField(max_length=200) + _service = models.ForeignKey( + Calendar, on_delete=models.CASCADE, blank=True, null=True + ) + trip_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico del viaje." + ) + trip_headsign = models.CharField( + max_length=255, blank=True, null=True, help_text="Destino del viaje." + ) + trip_short_name = models.CharField( + max_length=255, blank=True, null=True, help_text="Nombre corto del viaje." + ) + direction_id = models.PositiveIntegerField( + choices=((0, "En un sentido"), (1, "En el otro")), + help_text="DirecciΓ³n del viaje.", + ) + block_id = models.CharField( + max_length=255, blank=True, null=True, help_text="Identificador del bloque." + ) + shape_id = models.CharField(max_length=255, blank=True, null=True) + geoshape = models.ForeignKey( + GeoShape, on_delete=models.SET_NULL, blank=True, null=True + ) + wheelchair_accessible = models.PositiveIntegerField( + choices=((0, "No especificado"), (1, "Accesible"), (2, "No accesible")), + help_text="ΒΏTiene acceso para sillas de ruedas?", + ) + bikes_allowed = models.PositiveIntegerField( + choices=((0, "No especificado"), (1, "Permitido"), (2, "No permitido")), + help_text="ΒΏ Es permitido llevar bicicletas?", + ) + + class Meta: + constraints = [ + UniqueConstraint(fields=["feed", "trip_id"], name="unique_trip_in_feed") + ] + + def save(self, *args, **kwargs): + self._route = Route.objects.get(feed=self.feed, route_id=self.route_id) + self._service = Calendar.objects.get(feed=self.feed, service_id=self.service_id) + super(Trip, self).save(*args, **kwargs) + + def __str__(self): + return self.trip_id + + +class StopTime(models.Model): + """Times that a vehicle arrives at and departs from individual stops for each trip. + Maps to stop_times.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + trip_id = models.CharField(max_length=200) + _trip = models.ForeignKey(Trip, on_delete=models.CASCADE, blank=True, null=True) + arrival_time = models.TimeField( + help_text="Hora de llegada a la parada.", blank=True, null=True + ) + departure_time = models.TimeField( + help_text="Hora de salida de la parada.", blank=True, null=True + ) + stop_id = models.CharField(max_length=200) + _stop = models.ForeignKey(Stop, on_delete=models.CASCADE, blank=True, null=True) + stop_sequence = models.PositiveIntegerField( + help_text="Secuencia de la parada en el viaje." + ) + stop_headsign = models.CharField( + max_length=255, blank=True, null=True, help_text="Destino de la parada." + ) + pickup_type = models.PositiveIntegerField( + help_text="Tipo de recogida de pasajeros.", + ) + drop_off_type = models.PositiveIntegerField( + help_text="Tipo de bajada de pasajeros.", + ) + shape_dist_traveled = models.DecimalField( + max_digits=6, + decimal_places=4, + blank=True, + null=True, + help_text="Distancia recorrida en la trayectoria.", + ) + timepoint = models.BooleanField( + blank=True, + null=True, + default=False, + help_text="ΒΏEs un punto de tiempo programado y exacto?", + ) + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "trip_id", "stop_sequence"], + name="unique_stoptime_in_feed", + ) + ] + + def save(self, *args, **kwargs): + self._trip = Trip.objects.get(feed=self.feed, trip_id=self.trip_id) + self._stop = Stop.objects.get(feed=self.feed, stop_id=self.stop_id) + super(StopTime, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.trip_id}: {self.stop_id} ({self.stop_sequence})" + + +class FareAttribute(models.Model): + """Rules for how to calculate the fare for a certain kind of trip. + Maps to fare_attributes.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + fare_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la tarifa." + ) + price = models.DecimalField( + max_digits=6, decimal_places=2, help_text="Precio de la tarifa." + ) + currency_type = models.CharField( + max_length=3, help_text="CΓ³digo ISO 4217 de la moneda." + ) + payment_method = models.PositiveIntegerField( + choices=((0, "Pago a bordo"), (1, "Pago anticipado")), + help_text="MΓ©todo de pago.", + ) + transfers = models.PositiveIntegerField( + choices=( + (0, "No permitido"), + (1, "Permitido una vez"), + (2, "Permitido dos veces"), + ), + blank=True, + null=True, + help_text="NΓΊmero de transferencias permitidas.", + ) + agency_id = models.CharField(max_length=255, blank=True, null=True) + _agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) + transfer_duration = models.PositiveIntegerField( + blank=True, null=True, help_text="DuraciΓ³n de la transferencia." + ) + + class Meta: + constraints = [ + UniqueConstraint(fields=["feed", "fare_id"], name="unique_fare_in_feed") + ] + + def save(self, *args, **kwargs): + if self.agency_id: + self._agency = Agency.objects.get(feed=self.feed, agency_id=self.agency_id) + super(FareAttribute, self).save(*args, **kwargs) + + def __str__(self): + return self.fare_id + + +class FareRule(models.Model): + """Rules for which fare to apply in a given situation. + Maps to fare_rules.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + fare_id = models.CharField(max_length=200) + _fare = models.ForeignKey( + FareAttribute, on_delete=models.CASCADE, blank=True, null=True + ) + route_id = models.CharField(max_length=200) + _route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + origin_id = models.CharField(max_length=255, blank=True, null=True) + destination_id = models.CharField(max_length=255, blank=True, null=True) + contains_id = models.CharField(max_length=255, blank=True, null=True) + + class Meta: + constraints = [ + UniqueConstraint( + fields=[ + "feed", + "fare_id", + "route_id", + "origin_id", + "destination_id", + "contains_id", + ], + name="unique_fare_rule_in_feed", + ) + ] + + def save(self, *args, **kwargs): + self._fare = FareAttribute.objects.get(feed=self.feed, fare_id=self.fare_id) + self._route = Route.objects.get(feed=self.feed, route_id=self.route_id) + super(FareRule, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.fare_id}: {self.route_id}" + + +class FeedInfo(models.Model): + """Additional information about the feed itself, including publisher, version, and expiration information. + Maps to feed_info.txt in the GTFS feed. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + feed_publisher_name = models.CharField( + max_length=255, help_text="Nombre del editor del feed." + ) + feed_publisher_url = models.URLField( + help_text="URL del editor del feed.", blank=True, null=True + ) + feed_lang = models.CharField( + max_length=2, help_text="CΓ³digo ISO 639-1 de idioma primario." + ) + feed_start_date = models.DateField( + help_text="Fecha de inicio de la informaciΓ³n del feed.", blank=True, null=True + ) + feed_end_date = models.DateField( + help_text="Fecha de finalizaciΓ³n de la informaciΓ³n del feed.", + blank=True, + null=True, + ) + feed_version = models.CharField( + max_length=255, blank=True, null=True, help_text="VersiΓ³n del feed." + ) + feed_contact_email = models.EmailField( + max_length=254, + blank=True, + null=True, + help_text="Correo electrΓ³nico de contacto.", + ) + feed_contact_url = models.URLField( + blank=True, + null=True, + help_text="URL de contacto.", + ) + + def __str__(self): + return f"{self.feed_publisher_name}: {self.feed_version}" + + +# ---------------- +# Auxiliary models +# ---------------- + + +class RouteStop(models.Model): + """Describes the sequence of stops for a route and a shape.""" + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + route_id = models.CharField(max_length=200) + _route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + shape_id = models.CharField(max_length=200) + _shape = models.ForeignKey( + GeoShape, on_delete=models.CASCADE, blank=True, null=True + ) + direction_id = models.PositiveIntegerField() + stop_id = models.CharField(max_length=200) + _stop = models.ForeignKey(Stop, on_delete=models.CASCADE, blank=True, null=True) + stop_sequence = models.PositiveIntegerField() + timepoint = models.BooleanField() + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "route_id", "shape_id", "stop_sequence"], + name="unique_routestop_in_feed", + ) + ] + + def save(self, *args, **kwargs): + self._route = Route.objects.get(feed=self.feed, route_id=self.route_id) + self._shape = GeoShape.objects.get(feed=self.feed, shape_id=self.shape_id) + self._stop = Stop.objects.get(feed=self.feed, stop_id=self.stop_id) + super(RouteStop, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.route_id}: {self.stop_id} ({self.shape_id} {self.stop_sequence})" + + +class TripDuration(models.Model): + """Describes the duration of a trip for a route and a service.""" + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + route_id = models.CharField(max_length=200) + _route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + shape_id = models.CharField(max_length=200) + _shape = models.ForeignKey(Shape, on_delete=models.CASCADE, blank=True, null=True) + service_id = models.CharField(max_length=200) + _service = models.ForeignKey( + Calendar, on_delete=models.CASCADE, blank=True, null=True + ) + start_time = models.TimeField() + end_time = models.TimeField() + stretch = models.PositiveIntegerField() + stretch_duration = models.DurationField() + + class Meta: + constraints = [ + UniqueConstraint( + fields=[ + "feed", + "route_id", + "shape_id", + "service_id", + "start_time", + "stretch", + ], + name="unique_tripduration_in_feed", + ) + ] + + def save(self, *args, **kwargs): + self._route = Route.objects.get(feed=self.feed, route_id=self.route_id) + self._shape = Shape.objects.get(feed=self.feed, shape_id=self.shape_id) + self._service = Calendar.objects.get(feed=self.feed, service_id=self.service_id) + super(TripDuration, self).save(*args, **kwargs) + + def __str__(self): + return ( + f"{self.route_id}: {self.service_id} ({self.start_time} - {self.end_time})" + ) + + +class TripTime(models.Model): + """ + TODO: llenar cuando se hace el proceso de importaciΓ³n de GTFS. Este modelo se llena con los datos de stop_times.txt, con las filas donde timepoint = True. + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + trip_id = models.CharField(max_length=200) + _trip = models.ForeignKey(Trip, on_delete=models.CASCADE, blank=True, null=True) + stop_id = models.CharField(max_length=200) + _stop = models.ForeignKey(Stop, on_delete=models.CASCADE, blank=True, null=True) + stop_sequence = models.PositiveIntegerField() + departure_time = models.TimeField() + + class Meta: + constraints = [ + UniqueConstraint( + fields=["feed", "trip_id", "stop_id"], + name="unique_triptime_in_feed", + ) + ] + + def save(self, *args, **kwargs): + self._trip = Trip.objects.get(feed=self.feed, trip_id=self.trip_id) + self._stop = Stop.objects.get(feed=self.feed, stop_id=self.stop_id) + super(TripTime, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.trip_id}: {self.stop_id} ({self.departure_time})" + + +# ------------- +# GTFS Realtime +# ------------- + + +class FeedMessage(models.Model): + """ + Header of a GTFS Realtime FeedMessage. + + This is metadata to link records of other models to a retrieved FeedMessage containing several entities, typically (necessarily, in this implementation) of a single kind. + """ + + ENTITY_TYPE_CHOICES = ( + ("trip_update", "TripUpdate"), + ("vehicle", "VehiclePosition"), + ("alert", "Alert"), + ) + + feed_message_id = models.CharField(max_length=63, primary_key=True) + provider = models.ForeignKey( + GTFSProvider, on_delete=models.SET_NULL, blank=True, null=True + ) + entity_type = models.CharField(max_length=63, choices=ENTITY_TYPE_CHOICES) + timestamp = models.DateTimeField(auto_now=True) + incrementality = models.CharField(max_length=15) + gtfs_realtime_version = models.CharField(max_length=15) + + class Meta: + ordering = ["-timestamp"] + + def __str__(self): + return f"{self.entity_type} ({self.timestamp})" + + +class TripUpdate(models.Model): + """ + GTFS Realtime TripUpdate entity v2.0 (normalized). + + Trip updates represent fluctuations in the timetable. + """ + + id = models.BigAutoField(primary_key=True) + entity_id = models.CharField(max_length=127) + + # Foreign key to FeedMessage model + feed_message = models.ForeignKey("FeedMessage", on_delete=models.CASCADE) + + # TripDescriptor (message) + trip_trip_id = models.CharField(max_length=255, blank=True, null=True) + trip_route_id = models.CharField(max_length=255, blank=True, null=True) + trip_direction_id = models.IntegerField(blank=True, null=True) + trip_start_time = models.DurationField(blank=True, null=True) + trip_start_date = models.DateField(blank=True, null=True) + trip_schedule_relationship = models.CharField( + max_length=31, blank=True, null=True + ) # (enum) + + # VehicleDescriptor (message) + vehicle_id = models.CharField(max_length=255, blank=True, null=True) + vehicle_label = models.CharField(max_length=255, blank=True, null=True) + vehicle_license_plate = models.CharField(max_length=255, blank=True, null=True) + vehicle_wheelchair_accessible = models.CharField( + max_length=31, blank=True, null=True + ) # (enum) + + # Timestamp (uint64) + timestamp = models.DateTimeField(blank=True, null=True) + + # Delay (int32) + delay = models.IntegerField(blank=True, null=True) + + def __str__(self): + return f"{self.entity_id} ({self.feed_message})" + + +class StopTimeUpdate(models.Model): + """ + GTFS Realtime TripUpdate message v2.0 (normalized). + + Realtime update for arrival and/or departure events for a given stop on a trip, linked to a TripUpdate entity in a FeedMessage. + """ + + id = models.BigAutoField(primary_key=True) + + # Foreign key to FeedMessage and TripUpdate models + feed_message = models.ForeignKey(FeedMessage, on_delete=models.CASCADE) + trip_update = models.ForeignKey(TripUpdate, on_delete=models.CASCADE) + + # Stop ID (string) + stop_sequence = models.IntegerField(blank=True, null=True) + stop_id = models.CharField(max_length=127, blank=True, null=True) + + # StopTimeEvent (message): arrival + arrival_delay = models.IntegerField(blank=True, null=True) + arrival_time = models.DateTimeField(blank=True, null=True) + arrival_uncertainty = models.IntegerField(blank=True, null=True) + + # StopTimeEvent (message): departure + departure_delay = models.IntegerField(blank=True, null=True) + departure_time = models.DateTimeField(blank=True, null=True) + departure_uncertainty = models.IntegerField(blank=True, null=True) + + # OccupancyStatus (enum) + departure_occupancy_status = models.CharField(max_length=255, blank=True, null=True) + + # ScheduleRelationship (enum) + schedule_relationship = models.CharField(max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.stop_id} ({self.trip_update})" + + +class VehiclePosition(models.Model): + """ + GTFS Realtime VehiclePosition entity v2.0 (normalized). + + Vehicle position represents a few basic pieces of information about a particular vehicle on the network. + """ + + id = models.BigAutoField(primary_key=True) + entity_id = models.CharField(max_length=127) + + # Foreign key to FeedMessage model + feed_message = models.ForeignKey( + FeedMessage, on_delete=models.CASCADE, blank=True, null=True + ) + + # TripDescriptor (message) + vehicle_trip_trip_id = models.CharField(max_length=255) + vehicle_trip_route_id = models.CharField(max_length=255, blank=True, null=True) + vehicle_trip_direction_id = models.IntegerField(blank=True, null=True) + vehicle_trip_start_time = models.DurationField(blank=True, null=True) + vehicle_trip_start_date = models.DateField(blank=True, null=True) + vehicle_trip_schedule_relationship = models.CharField( + max_length=31, blank=True, null=True + ) # (enum) + + # VehicleDescriptor (message) + vehicle_vehicle_id = models.CharField(max_length=255, blank=True, null=True) + vehicle_vehicle_label = models.CharField(max_length=255, blank=True, null=True) + vehicle_vehicle_license_plate = models.CharField( + max_length=255, blank=True, null=True + ) + vehicle_vehicle_wheelchair_accessible = models.CharField( + max_length=31, blank=True, null=True + ) # (enum) + + # Position (message) + vehicle_position_latitude = models.FloatField(blank=True, null=True) + vehicle_position_longitude = models.FloatField(blank=True, null=True) + vehicle_position_point = models.PointField(srid=4326, blank=True, null=True) + vehicle_position_bearing = models.FloatField(blank=True, null=True) + vehicle_position_odometer = models.FloatField(blank=True, null=True) + vehicle_position_speed = models.FloatField(blank=True, null=True) # (meters/second) + + # Current stop sequence (uint32) + vehicle_current_stop_sequence = models.IntegerField(blank=True, null=True) + + # Stop ID (string) + vehicle_stop_id = models.CharField(max_length=255, blank=True, null=True) + + # VehicleStopStatus (enum) + vehicle_current_status = models.CharField(max_length=255, blank=True, null=True) + + # Timestamp (uint64) + vehicle_timestamp = models.DateTimeField(blank=True, null=True) + + # CongestionLevel (enum) + vehicle_congestion_level = models.CharField(max_length=255, blank=True, null=True) + + # OccupancyStatus (enum) + vehicle_occupancy_status = models.CharField(max_length=255, blank=True, null=True) + + # OccupancyPercentage (uint32) + vehicle_occupancy_percentage = models.FloatField(blank=True, null=True) + + # CarriageDetails (message): not implemented + + def save(self, *args, **kwargs): + self.vehicle_position_point = Point( + self.vehicle_position_longitude, self.vehicle_position_latitude + ) + super(VehiclePosition, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.entity_id} ({self.feed_message})" + + +class Alert(models.Model): + """Alerts and warnings about the service. + Maps to alerts.txt in the GTFS feed. + + TODO: ajustar con Alerts de GTFS Realtime + """ + + id = models.BigAutoField(primary_key=True) + feed = models.ForeignKey(Feed, on_delete=models.CASCADE) + alert_id = models.CharField( + max_length=255, help_text="Identificador ΓΊnico de la alerta." + ) + route_id = models.CharField(max_length=255, help_text="Identificador de la ruta.") + trip_id = models.CharField(max_length=255, help_text="Identificador del viaje.") + service_date = models.DateField( + help_text="Fecha del servicio descrito por la alerta." + ) + service_start_time = models.TimeField( + help_text="Hora de inicio del servicio descrito por la alerta." + ) + service_end_time = models.TimeField( + help_text="Hora de finalizaciΓ³n del servicio descrito por la alerta." + ) + alert_header = models.CharField( + max_length=255, help_text="Encabezado de la alerta." + ) + alert_description = models.TextField(help_text="DescripciΓ³n de la alerta.") + alert_url = models.URLField(blank=True, null=True, help_text="URL de la alerta.") + cause = models.PositiveIntegerField( + choices=( + (1, "Otra causa"), + (2, "Accidente"), + (3, "CongestiΓ³n"), + (4, "Evento"), + (5, "Mantenimiento"), + (6, "Planificado"), + (7, "Huelga"), + (8, "ManifestaciΓ³n"), + (9, "Demora"), + (10, "Cierre"), + ), + help_text="Causa de la alerta.", + ) + effect = models.PositiveIntegerField( + choices=( + (1, "Otro efecto"), + (2, "DesviaciΓ³n"), + (3, "Adelanto"), + (4, "CancelaciΓ³n"), + (5, "Cierre"), + (6, "DesvΓ­o"), + (7, "DetenciΓ³n"), + (8, "Desconocido"), + ), + help_text="Efecto de la alerta.", + ) + severity = models.PositiveIntegerField( + choices=( + (1, "Desconocido"), + (2, "InformaciΓ³n"), + (3, "Advertencia"), + (4, "Grave"), + (5, "Muy grave"), + ), + help_text="Severidad de la alerta.", + ) + published = models.DateTimeField( + help_text="Fecha y hora de publicaciΓ³n de la alerta." + ) + updated = models.DateTimeField( + help_text="Fecha y hora de actualizaciΓ³n de la alerta." + ) + informed_entity = models.JSONField(help_text="Entidades informadas por la alerta.") + + def __str__(self): + return self.alert_id diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/tasks.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/tasks.py new file mode 100644 index 0000000..83603e5 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/tasks.py @@ -0,0 +1,32 @@ +from celery import shared_task +import requests +import zipfile +import io +from django.db import transaction +from .models import Feed, Agency, Stop, Route, Trip, StopTime, Calendar +from .importers import GTFSImporter # You'll need to write this + +@shared_task(queue='static') +def fetch_and_import_gtfs_schedule(): + """ + Download GTFS .zip, extract, parse CSVs, bulk insert. + Run this less frequently (e.g., daily or when feed updates). + """ + url = os.getenv('GTFS_SCHEDULE_ZIP_URL') + + # Download + response = requests.get(url, timeout=30) + response.raise_for_status() + + # Check if feed changed (via ETag or Last-Modified) + etag = response.headers.get('ETag') + last_modified = response.headers.get('Last-Modified') + + # Create Feed record + feed_id = f"{os.getenv('FEED_NAME')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + with zipfile.ZipFile(io.BytesIO(response.content)) as z: + importer = GTFSImporter(feed_id, z) + importer.import_all() + + return f"Imported {feed_id}" \ No newline at end of file diff --git a/eta_prediction/gtfs-rt-pipeline/sch_pipeline/utils.py b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/utils.py new file mode 100644 index 0000000..b1939aa --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/sch_pipeline/utils.py @@ -0,0 +1,17 @@ +# gtfs-rt-pipeline/sch_pipeline/utils.py +from django.db.models import Count +from sch_pipeline.models import Trip + +def top_routes_by_scheduled_trips(n: int = 5) -> list[str]: + """ + Returns route_ids for the top-N busiest routes by number of scheduled trips. + Stable, fast, and independent of realtime ingestion volume. + """ + qs = ( + Trip.objects + # .filter(feed__provider_id=provider_id) + .values("route_id") + .annotate(trips_count=Count("id")) + .order_by("-trips_count")[:n] + ) + return [row["route_id"] for row in qs] diff --git a/eta_prediction/gtfs-rt-pipeline/uv.lock b/eta_prediction/gtfs-rt-pipeline/uv.lock new file mode 100644 index 0000000..acef554 --- /dev/null +++ b/eta_prediction/gtfs-rt-pipeline/uv.lock @@ -0,0 +1,1532 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "asgiref" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/bf/0f3ecda32f1cb3bf1dca480aca08a7a8a3bdc4bed2343a103f30731565c9/asgiref-3.9.2.tar.gz", hash = "sha256:a0249afacb66688ef258ffe503528360443e2b9a8d8c4581b6ebefa58c841ef1", size = 36894, upload-time = "2025-09-23T15:00:55.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d1/69d02ce34caddb0a7ae088b84c356a625a93cd4ff57b2f97644c03fad905/asgiref-3.9.2-py3-none-any.whl", hash = "sha256:0b61526596219d70396548fc003635056856dba5d0d086f86476f10b33c75960", size = 23788, upload-time = "2025-09-23T15:00:53.627Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "billiard" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/6a/1405343016bce8354b29d90aad6b0bf6485b5e60404516e4b9a3a9646cf0/billiard-4.2.2.tar.gz", hash = "sha256:e815017a062b714958463e07ba15981d802dc53d41c5b69d28c5a7c238f8ecf3", size = 155592, upload-time = "2025-09-20T14:44:40.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/80/ef8dff49aae0e4430f81842f7403e14e0ca59db7bbaf7af41245b67c6b25/billiard-4.2.2-py3-none-any.whl", hash = "sha256:4bc05dcf0d1cc6addef470723aac2a6232f3c7ed7475b0b580473a9145829457", size = 86896, upload-time = "2025-09-20T14:44:39.157Z" }, +] + +[[package]] +name = "black" +version = "25.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/f4/7531d4a336d2d4ac6cc101662184c8e7d068b548d35d874415ed9f4116ef/black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa", size = 1698727, upload-time = "2025-09-19T00:31:14.264Z" }, + { url = "https://files.pythonhosted.org/packages/28/f9/66f26bfbbf84b949cc77a41a43e138d83b109502cd9c52dfc94070ca51f2/black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d", size = 1555679, upload-time = "2025-09-19T00:31:29.265Z" }, + { url = "https://files.pythonhosted.org/packages/bf/59/61475115906052f415f518a648a9ac679d7afbc8da1c16f8fdf68a8cebed/black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608", size = 1617453, upload-time = "2025-09-19T00:30:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5b/20fd5c884d14550c911e4fb1b0dae00d4abb60a4f3876b449c4d3a9141d5/black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f", size = 1333655, upload-time = "2025-09-19T00:30:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165, upload-time = "2025-09-19T00:34:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259, upload-time = "2025-09-19T00:32:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583, upload-time = "2025-09-19T00:30:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428, upload-time = "2025-09-19T00:32:13.839Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" }, +] + +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "cramjam" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/12/34bf6e840a79130dfd0da7badfb6f7810b8fcfd60e75b0539372667b41b6/cramjam-2.11.0.tar.gz", hash = "sha256:5c82500ed91605c2d9781380b378397012e25127e89d64f460fea6aeac4389b4", size = 99100, upload-time = "2025-07-27T21:25:07.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/89/8001f6a9b6b6e9fa69bec5319789083475d6f26d52aaea209d3ebf939284/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:04cfa39118570e70e920a9b75c733299784b6d269733dbc791d9aaed6edd2615", size = 3559272, upload-time = "2025-07-27T21:22:01.988Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f3/001d00070ca92e5fbe6aacc768e455568b0cde46b0eb944561a4ea132300/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:66a18f68506290349a256375d7aa2f645b9f7993c10fc4cc211db214e4e61d2b", size = 1861743, upload-time = "2025-07-27T21:22:03.754Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/041a3af01bf3f6158f120070f798546d4383b962b63c35cd91dcbf193e17/cramjam-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50e7d65533857736cd56f6509cf2c4866f28ad84dd15b5bdbf2f8a81e77fa28a", size = 1699631, upload-time = "2025-07-27T21:22:05.192Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/5358b238808abebd0c949c42635c3751204ca7cf82b29b984abe9f5e33c8/cramjam-2.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f71989668458fc327ac15396db28d92df22f8024bb12963929798b2729d2df5", size = 2025603, upload-time = "2025-07-27T21:22:06.726Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/19dba7c03a27408d8d11b5a7a4a7908459cfd4e6f375b73264dc66517bf6/cramjam-2.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee77ac543f1e2b22af1e8be3ae589f729491b6090582340aacd77d1d757d9569", size = 1766283, upload-time = "2025-07-27T21:22:08.568Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/40e4b3408501d886d082db465c33971655fe82573c535428e52ab905f4d0/cramjam-2.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad52784120e7e4d8a0b5b0517d185b8bf7f74f5e17272857ddc8951a628d9be1", size = 1854407, upload-time = "2025-07-27T21:22:10.518Z" }, + { url = "https://files.pythonhosted.org/packages/36/6e/c1b60ceb6d7ea6ff8b0bf197520aefe23f878bf2bfb0de65f2b0c2f82cd1/cramjam-2.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b86f8e6d9c1b3f9a75b2af870c93ceee0f1b827cd2507387540e053b35d7459", size = 2035793, upload-time = "2025-07-27T21:22:12.504Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ad/32a8d5f4b1e3717787945ec6d71bd1c6e6bccba4b7e903fc0d9d4e4b08c3/cramjam-2.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:320d61938950d95da2371b46c406ec433e7955fae9f396c8e1bf148ffc187d11", size = 2067499, upload-time = "2025-07-27T21:22:14.067Z" }, + { url = "https://files.pythonhosted.org/packages/ff/cd/3b5a662736ea62ff7fa4c4a10a85e050bfdaad375cc53dc80427e8afe41c/cramjam-2.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41eafc8c1653a35a5c7e75ad48138f9f60085cc05cd99d592e5298552d944e9f", size = 1981853, upload-time = "2025-07-27T21:22:15.908Z" }, + { url = "https://files.pythonhosted.org/packages/26/8e/1dbcfaaa7a702ee82ee683ec3a81656934dd7e04a7bc4ee854033686f98a/cramjam-2.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03a7316c6bf763dfa34279335b27702321da44c455a64de58112968c0818ec4a", size = 2034514, upload-time = "2025-07-27T21:22:17.352Z" }, + { url = "https://files.pythonhosted.org/packages/50/62/f11709bfdce74af79a88b410dcb76dedc97612166e759136931bf63cfd7b/cramjam-2.11.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:244c2ed8bd7ccbb294a2abe7ca6498db7e89d7eb5e744691dc511a7dc82e65ca", size = 2155343, upload-time = "2025-07-27T21:22:18.854Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/3b98b61841a5376d9a9b8468ae58753a8e6cf22be9534a0fa5af4d8621cc/cramjam-2.11.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:405f8790bad36ce0b4bbdb964ad51507bfc7942c78447f25cb828b870a1d86a0", size = 2169367, upload-time = "2025-07-27T21:22:20.389Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/bd5db5c49dbebc8b002f1c4983101b28d2e7fc9419753db1c31ec22b03ef/cramjam-2.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b1b751a5411032b08fb3ac556160229ca01c6bbe4757bb3a9a40b951ebaac23", size = 2159334, upload-time = "2025-07-27T21:22:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/34/32/203c57acdb6eea727e7078b2219984e64ed4ad043c996ed56321301ba167/cramjam-2.11.0-cp311-cp311-win32.whl", hash = "sha256:5251585608778b9ac8effed544933df7ad85b4ba21ee9738b551f17798b215ac", size = 1605313, upload-time = "2025-07-27T21:22:24.126Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bd/102d6deb87a8524ac11cddcd31a7612b8f20bf9b473c3c645045e3b957c7/cramjam-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:dca88bc8b68ce6d35dafd8c4d5d59a238a56c43fa02b74c2ce5f9dfb0d1ccb46", size = 1710991, upload-time = "2025-07-27T21:22:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/7c84c913a5fae85b773a9dcf8874390f9d68ba0fcc6630efa7ff1541b950/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dba5c14b8b4f73ea1e65720f5a3fe4280c1d27761238378be8274135c60bbc6e", size = 3553368, upload-time = "2025-07-27T21:22:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cc/4f6d185d8a744776f53035e72831ff8eefc2354f46ab836f4bd3c4f6c138/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:11eb40722b3fcf3e6890fba46c711bf60f8dc26360a24876c85e52d76c33b25b", size = 1860014, upload-time = "2025-07-27T21:22:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a8/626c76263085c6d5ded0e71823b411e9522bfc93ba6cc59855a5869296e7/cramjam-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aeb26e2898994b6e8319f19a4d37c481512acdcc6d30e1b5ecc9d8ec57e835cb", size = 1693512, upload-time = "2025-07-27T21:22:30.999Z" }, + { url = "https://files.pythonhosted.org/packages/e9/52/0851a16a62447532e30ba95a80e638926fdea869a34b4b5b9d0a020083ba/cramjam-2.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f8d82081ed7d8fe52c982bd1f06e4c7631a73fe1fb6d4b3b3f2404f87dc40fe", size = 2025285, upload-time = "2025-07-27T21:22:32.954Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/122e444f59dbc216451d8e3d8282c9665dc79eaf822f5f1470066be1b695/cramjam-2.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:092a3ec26e0a679305018380e4f652eae1b6dfe3fc3b154ee76aa6b92221a17c", size = 1761327, upload-time = "2025-07-27T21:22:34.484Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bc/3a0189aef1af2b29632c039c19a7a1b752bc21a4053582a5464183a0ad3d/cramjam-2.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:529d6d667c65fd105d10bd83d1cd3f9869f8fd6c66efac9415c1812281196a92", size = 1854075, upload-time = "2025-07-27T21:22:36.157Z" }, + { url = "https://files.pythonhosted.org/packages/2e/80/8a6343b13778ce52d94bb8d5365a30c3aa951276b1857201fe79d7e2ad25/cramjam-2.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:555eb9c90c450e0f76e27d9ff064e64a8b8c6478ab1a5594c91b7bc5c82fd9f0", size = 2032710, upload-time = "2025-07-27T21:22:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/df/6b/cd1778a207c29eda10791e3dfa018b588001928086e179fc71254793c625/cramjam-2.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5edf4c9e32493035b514cf2ba0c969d81ccb31de63bd05490cc8bfe3b431674e", size = 2068353, upload-time = "2025-07-27T21:22:39.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f0/5c2a5cd5711032f3b191ca50cb786c17689b4a9255f9f768866e6c9f04d9/cramjam-2.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2fe41f48c4d58d923803383b0737f048918b5a0d10390de9628bb6272b107", size = 1978104, upload-time = "2025-07-27T21:22:41.106Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8b/b363a5fb2c3347504fe9a64f8d0f1e276844f0e532aa7162c061cd1ffee4/cramjam-2.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca14cf1cabdb0b77d606db1bb9e9ca593b1dbd421fcaf251ec9a5431ec449f3", size = 2030779, upload-time = "2025-07-27T21:22:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/78/7b/d83dad46adb6c988a74361f81ad9c5c22642be53ad88616a19baedd06243/cramjam-2.11.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:309e95bf898829476bccf4fd2c358ec00e7ff73a12f95a3cdeeba4bb1d3683d5", size = 2155297, upload-time = "2025-07-27T21:22:44.6Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/60d9be4cb33d8740a4aa94c7513f2ef3c4eba4fd13536f086facbafade71/cramjam-2.11.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:86dca35d2f15ef22922411496c220f3c9e315d5512f316fe417461971cc1648d", size = 2169255, upload-time = "2025-07-27T21:22:46.534Z" }, + { url = "https://files.pythonhosted.org/packages/11/b0/4a595f01a243aec8ad272b160b161c44351190c35d98d7787919d962e9e5/cramjam-2.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:193c6488bd2f514cbc0bef5c18fad61a5f9c8d059dd56edf773b3b37f0e85496", size = 2155651, upload-time = "2025-07-27T21:22:48.46Z" }, + { url = "https://files.pythonhosted.org/packages/38/47/7776659aaa677046b77f527106e53ddd47373416d8fcdb1e1a881ec5dc06/cramjam-2.11.0-cp312-cp312-win32.whl", hash = "sha256:514e2c008a8b4fa823122ca3ecab896eac41d9aa0f5fc881bd6264486c204e32", size = 1603568, upload-time = "2025-07-27T21:22:50.084Z" }, + { url = "https://files.pythonhosted.org/packages/75/b1/d53002729cfd94c5844ddfaf1233c86d29f2dbfc1b764a6562c41c044199/cramjam-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:53fed080476d5f6ad7505883ec5d1ec28ba36c2273db3b3e92d7224fe5e463db", size = 1709287, upload-time = "2025-07-27T21:22:51.534Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/406c5dc0f8e82385519d8c299c40fd6a56d97eca3fcd6f5da8dad48de75b/cramjam-2.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2c289729cc1c04e88bafa48b51082fb462b0a57dbc96494eab2be9b14dca62af", size = 3553330, upload-time = "2025-07-27T21:22:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/00/ad/4186884083d6e4125b285903e17841827ab0d6d0cffc86216d27ed91e91d/cramjam-2.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:045201ee17147e36cf43d8ae2fa4b4836944ac672df5874579b81cf6d40f1a1f", size = 1859756, upload-time = "2025-07-27T21:22:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/54/01/91b485cf76a7efef638151e8a7d35784dae2c4ff221b1aec2c083e4b106d/cramjam-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:619cd195d74c9e1d2a3ad78d63451d35379c84bd851aec552811e30842e1c67a", size = 1693609, upload-time = "2025-07-27T21:22:56.331Z" }, + { url = "https://files.pythonhosted.org/packages/cd/84/d0c80d279b2976870fc7d10f15dcb90a3c10c06566c6964b37c152694974/cramjam-2.11.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6eb3ae5ab72edb2ed68bdc0f5710f0a6cad7fd778a610ec2c31ee15e32d3921e", size = 2024912, upload-time = "2025-07-27T21:22:57.915Z" }, + { url = "https://files.pythonhosted.org/packages/d6/70/88f2a5cb904281ed5d3c111b8f7d5366639817a5470f059bcd26833fc870/cramjam-2.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7da3f4b19e3078f9635f132d31b0a8196accb2576e3213ddd7a77f93317c20", size = 1760715, upload-time = "2025-07-27T21:22:59.528Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/cf5b02081132537d28964fb385fcef9ed9f8a017dd7d8c59d317e53ba50d/cramjam-2.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57286b289cd557ac76c24479d8ecfb6c3d5b854cce54ccc7671f9a2f5e2a2708", size = 1853782, upload-time = "2025-07-27T21:23:01.07Z" }, + { url = "https://files.pythonhosted.org/packages/57/27/63525087ed40a53d1867021b9c4858b80cc86274ffe7225deed067d88d92/cramjam-2.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28952fbbf8b32c0cb7fa4be9bcccfca734bf0d0989f4b509dc7f2f70ba79ae06", size = 2032354, upload-time = "2025-07-27T21:23:03.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ef/dbba082c6ebfb6410da4dd39a64e654d7194fcfd4567f85991a83fa4ec32/cramjam-2.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78ed2e4099812a438b545dfbca1928ec825e743cd253bc820372d6ef8c3adff4", size = 2068007, upload-time = "2025-07-27T21:23:04.526Z" }, + { url = "https://files.pythonhosted.org/packages/35/ce/d902b9358a46a086938feae83b2251720e030f06e46006f4c1fc0ac9da20/cramjam-2.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9aecd5c3845d415bd6c9957c93de8d93097e269137c2ecb0e5a5256374bdc8", size = 1977485, upload-time = "2025-07-27T21:23:06.058Z" }, + { url = "https://files.pythonhosted.org/packages/e8/03/982f54553244b0afcbdb2ad2065d460f0ab05a72a96896a969a1ca136a1e/cramjam-2.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:362fcf4d6f5e1242a4540812455f5a594949190f6fbc04f2ffbfd7ae0266d788", size = 2030447, upload-time = "2025-07-27T21:23:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/74/5f/748e54cdb665ec098ec519e23caacc65fc5ae58718183b071e33fc1c45b4/cramjam-2.11.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:13240b3dea41b1174456cb9426843b085dc1a2bdcecd9ee2d8f65ac5703374b0", size = 2154949, upload-time = "2025-07-27T21:23:09.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/81/c4e6cb06ed69db0dc81f9a8b1dc74995ebd4351e7a1877143f7031ff2700/cramjam-2.11.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:c54eed83726269594b9086d827decc7d2015696e31b99bf9b69b12d9063584fe", size = 2168925, upload-time = "2025-07-27T21:23:10.976Z" }, + { url = "https://files.pythonhosted.org/packages/13/5b/966365523ce8290a08e163e3b489626c5adacdff2b3da9da1b0823dfb14e/cramjam-2.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f8195006fdd0fc0a85b19df3d64a3ef8a240e483ae1dfc7ac6a4316019eb5df2", size = 2154950, upload-time = "2025-07-27T21:23:12.514Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7d/7f8eb5c534b72b32c6eb79d74585bfee44a9a5647a14040bb65c31c2572d/cramjam-2.11.0-cp313-cp313-win32.whl", hash = "sha256:ccf30e3fe6d770a803dcdf3bb863fa44ba5dc2664d4610ba2746a3c73599f2e4", size = 1603199, upload-time = "2025-07-27T21:23:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/37/05/47b5e0bf7c41a3b1cdd3b7c2147f880c93226a6bef1f5d85183040cbdece/cramjam-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ee36348a204f0a68b03400f4736224e9f61d1c6a1582d7f875c1ca56f0254268", size = 1708924, upload-time = "2025-07-27T21:23:16.332Z" }, + { url = "https://files.pythonhosted.org/packages/de/07/a1051cdbbe6d723df16d756b97f09da7c1adb69e29695c58f0392bc12515/cramjam-2.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7ba5e38c9fbd06f086f4a5a64a1a5b7b417cd3f8fc07a20e5c03651f72f36100", size = 3554141, upload-time = "2025-07-27T21:23:17.938Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/58487d2e16ef3d04f51a7c7f0e69823e806744b4c21101e89da4873074bc/cramjam-2.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8adeee57b41fe08e4520698a4b0bd3cc76dbd81f99424b806d70a5256a391d3", size = 1860353, upload-time = "2025-07-27T21:23:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/67/b4/67f6254d166ffbcc9d5fa1b56876eaa920c32ebc8e9d3d525b27296b693b/cramjam-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b96a74fa03a636c8a7d76f700d50e9a8bc17a516d6a72d28711225d641e30968", size = 1693832, upload-time = "2025-07-27T21:23:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/55/a3/4e0b31c0d454ae70c04684ed7c13d3c67b4c31790c278c1e788cb804fa4a/cramjam-2.11.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c3811a56fa32e00b377ef79121c0193311fd7501f0fb378f254c7f083cc1fbe0", size = 2027080, upload-time = "2025-07-27T21:23:23.303Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c7/5e8eed361d1d3b8be14f38a54852c5370cc0ceb2c2d543b8ba590c34f080/cramjam-2.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d927e87461f8a0d448e4ab5eb2bca9f31ca5d8ea86d70c6f470bb5bc666d7e", size = 1761543, upload-time = "2025-07-27T21:23:24.991Z" }, + { url = "https://files.pythonhosted.org/packages/09/0c/06b7f8b0ce9fde89470505116a01fc0b6cb92d406c4fb1e46f168b5d3fa5/cramjam-2.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f1f5c450121430fd89cb5767e0a9728ecc65997768fd4027d069cb0368af62f9", size = 1854636, upload-time = "2025-07-27T21:23:26.987Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c6/6ebc02c9d5acdf4e5f2b1ec6e1252bd5feee25762246798ae823b3347457/cramjam-2.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:724aa7490be50235d97f07e2ca10067927c5d7f336b786ddbc868470e822aa25", size = 2032715, upload-time = "2025-07-27T21:23:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/a122971c23f5ca4b53e4322c647ac7554626c95978f92d19419315dddd05/cramjam-2.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54c4637122e7cfd7aac5c1d3d4c02364f446d6923ea34cf9d0e8816d6e7a4936", size = 2069039, upload-time = "2025-07-27T21:23:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f6121b90b86b9093c066889274d26a1de3f29969d45c2ed1ecbe2033cb78/cramjam-2.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17eb39b1696179fb471eea2de958fa21f40a2cd8bf6b40d428312d5541e19dc4", size = 1979566, upload-time = "2025-07-27T21:23:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/f95bc57fd7f4166ce6da816cfa917fb7df4bb80e669eb459d85586498414/cramjam-2.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:36aa5a798aa34e11813a80425a30d8e052d8de4a28f27bfc0368cfc454d1b403", size = 2030905, upload-time = "2025-07-27T21:23:33.696Z" }, + { url = "https://files.pythonhosted.org/packages/fc/52/e429de4e8bc86ee65e090dae0f87f45abd271742c63fb2d03c522ffde28a/cramjam-2.11.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:449fca52774dc0199545fbf11f5128933e5a6833946707885cf7be8018017839", size = 2155592, upload-time = "2025-07-27T21:23:35.375Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6c/65a7a0207787ad39ad804af4da7f06a60149de19481d73d270b540657234/cramjam-2.11.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:d87d37b3d476f4f7623c56a232045d25bd9b988314702ea01bd9b4a94948a778", size = 2170839, upload-time = "2025-07-27T21:23:37.197Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/5c5db505ba692bc844246b066e23901d5905a32baf2f33719c620e65887f/cramjam-2.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:26cb45c47d71982d76282e303931c6dd4baee1753e5d48f9a89b3a63e690b3a3", size = 2157236, upload-time = "2025-07-27T21:23:38.854Z" }, + { url = "https://files.pythonhosted.org/packages/b0/22/88e6693e60afe98901e5bbe91b8dea193e3aa7f42e2770f9c3339f5c1065/cramjam-2.11.0-cp314-cp314-win32.whl", hash = "sha256:4efe919d443c2fd112fe25fe636a52f9628250c9a50d9bddb0488d8a6c09acc6", size = 1604136, upload-time = "2025-07-27T21:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f8/01618801cd59ccedcc99f0f96d20be67d8cfc3497da9ccaaad6b481781dd/cramjam-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ccec3524ea41b9abd5600e3e27001fd774199dbb4f7b9cb248fcee37d4bda84c", size = 1710272, upload-time = "2025-07-27T21:23:42.236Z" }, + { url = "https://files.pythonhosted.org/packages/40/81/6cdb3ed222d13ae86bda77aafe8d50566e81a1169d49ed195b6263610704/cramjam-2.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:966ac9358b23d21ecd895c418c048e806fd254e46d09b1ff0cdad2eba195ea3e", size = 3559671, upload-time = "2025-07-27T21:23:44.504Z" }, + { url = "https://files.pythonhosted.org/packages/cb/43/52b7e54fe5ba1ef0270d9fdc43dabd7971f70ea2d7179be918c997820247/cramjam-2.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:387f09d647a0d38dcb4539f8a14281f8eb6bb1d3e023471eb18a5974b2121c86", size = 1867876, upload-time = "2025-07-27T21:23:46.987Z" }, + { url = "https://files.pythonhosted.org/packages/9d/28/30d5b8d10acd30db3193bc562a313bff722888eaa45cfe32aa09389f2b24/cramjam-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:665b0d8fbbb1a7f300265b43926457ec78385200133e41fef19d85790fc1e800", size = 1695562, upload-time = "2025-07-27T21:23:48.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/86/ec806f986e01b896a650655024ea52a13e25c3ac8a3a382f493089483cdc/cramjam-2.11.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ca905387c7a371531b9622d93471be4d745ef715f2890c3702479cd4fc85aa51", size = 2025056, upload-time = "2025-07-27T21:23:50.404Z" }, + { url = "https://files.pythonhosted.org/packages/09/43/c2c17586b90848d29d63181f7d14b8bd3a7d00975ad46e3edf2af8af7e1f/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1aa56aef2c8af55a21ed39040a94a12b53fb23beea290f94d19a76027e2ffb", size = 1764084, upload-time = "2025-07-27T21:23:52.265Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a9/68bc334fadb434a61df10071dc8606702aa4f5b6cdb2df62474fc21d2845/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5db59c1cdfaa2ab85cc988e602d6919495f735ca8a5fd7603608eb1e23c26d5", size = 1854859, upload-time = "2025-07-27T21:23:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4e/b48e67835b5811ec5e9cb2e2bcba9c3fd76dab3e732569fe801b542c6ca9/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f893014f00fe5e89a660a032e813bf9f6d91de74cd1490cdb13b2b59d0c9a3", size = 2035970, upload-time = "2025-07-27T21:23:55.758Z" }, + { url = "https://files.pythonhosted.org/packages/c4/70/d2ac33d572b4d90f7f0f2c8a1d60fb48f06b128fdc2c05f9b49891bb0279/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c26a1eb487947010f5de24943bd7c422dad955b2b0f8650762539778c380ca89", size = 2069320, upload-time = "2025-07-27T21:23:57.494Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4c/85cec77af4a74308ba5fca8e296c4e2f80ec465c537afc7ab1e0ca2f9a00/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d5c8bfb438d94e7b892d1426da5fc4b4a5370cc360df9b8d9d77c33b896c37e", size = 1982668, upload-time = "2025-07-27T21:23:59.126Z" }, + { url = "https://files.pythonhosted.org/packages/55/45/938546d1629e008cc3138df7c424ef892719b1796ff408a2ab8550032e5e/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:cb1fb8c9337ab0da25a01c05d69a0463209c347f16512ac43be5986f3d1ebaf4", size = 2034028, upload-time = "2025-07-27T21:24:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/b5a53e20505555f1640e66dcf70394bcf51a1a3a072aa18ea35135a0f9ed/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:1f6449f6de52dde3e2f1038284910c8765a397a25e2d05083870f3f5e7fc682c", size = 2155513, upload-time = "2025-07-27T21:24:02.92Z" }, + { url = "https://files.pythonhosted.org/packages/84/12/8d3f6ceefae81bbe45a347fdfa2219d9f3ac75ebc304f92cd5fcb4fbddc5/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:382dec4f996be48ed9c6958d4e30c2b89435d7c2c4dbf32480b3b8886293dd65", size = 2170035, upload-time = "2025-07-27T21:24:04.558Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/3be6f0a1398f976070672be64f61895f8839857618a2d8cc0d3ab529d3dc/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:d388bd5723732c3afe1dd1d181e4213cc4e1be210b080572e7d5749f6e955656", size = 2160229, upload-time = "2025-07-27T21:24:06.729Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/66cfc3635511b20014bbb3f2ecf0095efb3049e9e96a4a9e478e4f3d7b78/cramjam-2.11.0-cp314-cp314t-win32.whl", hash = "sha256:0a70ff17f8e1d13f322df616505550f0f4c39eda62290acb56f069d4857037c8", size = 1610267, upload-time = "2025-07-27T21:24:08.428Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c6/c71e82e041c95ffe6a92ac707785500aa2a515a4339c2c7dd67e3c449249/cramjam-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:028400d699442d40dbda02f74158c73d05cb76587a12490d0bfedd958fd49188", size = 1713108, upload-time = "2025-07-27T21:24:10.147Z" }, + { url = "https://files.pythonhosted.org/packages/81/da/b3301962ccd6fce9fefa1ecd8ea479edaeaa38fadb1f34d5391d2587216a/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:52d5db3369f95b27b9f3c14d067acb0b183333613363ed34268c9e04560f997f", size = 3573546, upload-time = "2025-07-27T21:24:52.944Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c2/410ddb8ad4b9dfb129284666293cb6559479645da560f7077dc19d6bee9e/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4820516366d455b549a44d0e2210ee7c4575882dda677564ce79092588321d54", size = 1873654, upload-time = "2025-07-27T21:24:54.958Z" }, + { url = "https://files.pythonhosted.org/packages/d5/99/f68a443c64f7ce7aff5bed369b0aa5b2fac668fa3dfd441837e316e97a1f/cramjam-2.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d9e5db525dc0a950a825202f84ee68d89a072479e07da98795a3469df942d301", size = 1702846, upload-time = "2025-07-27T21:24:57.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/02/0ff358ab773def1ee3383587906c453d289953171e9c92db84fdd01bf172/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62ab4971199b2270005359cdc379bc5736071dc7c9a228581c5122d9ffaac50c", size = 1773683, upload-time = "2025-07-27T21:24:59.28Z" }, + { url = "https://files.pythonhosted.org/packages/e9/31/3298e15f87c9cf2aabdbdd90b153d8644cf989cb42a45d68a1b71e1f7aaf/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24758375cc5414d3035ca967ebb800e8f24604ececcba3c67d6f0218201ebf2d", size = 1994136, upload-time = "2025-07-27T21:25:01.565Z" }, + { url = "https://files.pythonhosted.org/packages/c7/90/20d1747255f1ee69a412e319da51ea594c18cca195e7a4d4c713f045eff5/cramjam-2.11.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6c2eea545fef1065c7dd4eda991666fd9c783fbc1d226592ccca8d8891c02f23", size = 1714982, upload-time = "2025-07-27T21:25:05.79Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154, upload-time = "2025-09-17T16:33:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322, upload-time = "2025-09-17T16:33:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078, upload-time = "2025-09-17T16:33:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011, upload-time = "2025-09-17T16:33:35.711Z" }, + { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522, upload-time = "2025-09-17T16:33:38.466Z" }, + { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417, upload-time = "2025-09-17T16:33:41.299Z" }, + { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130, upload-time = "2025-09-17T16:33:43.554Z" }, + { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053, upload-time = "2025-09-17T16:33:53.033Z" }, + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "django" +version = "5.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/8c/2a21594337250a171d45dda926caa96309d5136becd1f48017247f9cdea0/django-5.2.6.tar.gz", hash = "sha256:da5e00372763193d73cecbf71084a3848458cecf4cee36b9a1e8d318d114a87b", size = 10858861, upload-time = "2025-09-03T13:04:03.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/af/6593f6d21404e842007b40fdeb81e73c20b6649b82d020bb0801b270174c/django-5.2.6-py3-none-any.whl", hash = "sha256:60549579b1174a304b77e24a93d8d9fafe6b6c03ac16311f3e25918ea5a20058", size = 8303111, upload-time = "2025-09-03T13:03:47.808Z" }, +] + +[[package]] +name = "django-environ" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastparquet" +version = "2024.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cramjam" }, + { name = "fsspec" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/66/862da14f5fde4eff2cedc0f51a8dc34ba145088e5041b45b2d57ac54f922/fastparquet-2024.11.0.tar.gz", hash = "sha256:e3b1fc73fd3e1b70b0de254bae7feb890436cb67e99458b88cb9bd3cc44db419", size = 467192, upload-time = "2024-11-15T19:30:10.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/51/e0d6e702523ac923ede6c05e240f4a02533ccf2cea9fec7a43491078e920/fastparquet-2024.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:374cdfa745aa7d5188430528d5841cf823eb9ad16df72ad6dadd898ccccce3be", size = 909934, upload-time = "2024-11-12T20:37:37.049Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c8/5c0fb644c19a8d80b2ae4d8aa7d90c2d85d0bd4a948c5c700bea5c2802ea/fastparquet-2024.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c8401bfd86cccaf0ab7c0ade58c91ae19317ff6092e1d4ad96c2178197d8124", size = 683844, upload-time = "2024-11-12T20:37:38.456Z" }, + { url = "https://files.pythonhosted.org/packages/33/4a/1e532fd1a0d4d8af7ffc7e3a8106c0bcd13ed914a93a61e299b3832dd3d2/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9cca4c6b5969df5561c13786f9d116300db1ec22c7941e237cfca4ce602f59b", size = 1791698, upload-time = "2024-11-12T20:37:41.101Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e8/e1ede861bea68394a755d8be1aa2e2d60a3b9f6b551bfd56aeca74987e2e/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a9387e77ac608d8978774caaf1e19de67eaa1386806e514dcb19f741b19cfe5", size = 1804289, upload-time = "2024-11-12T20:37:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1e/957090cccaede805583ca3f3e46e2762d0f9bf8860ecbce65197e47d84c1/fastparquet-2024.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6595d3771b3d587a31137e985f751b4d599d5c8e9af9c4858e373fdf5c3f8720", size = 1753638, upload-time = "2024-11-12T20:37:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/344787c685fd1531f07ae712a855a7c34d13deaa26c3fd4a9231bea7dbab/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:053695c2f730b78a2d3925df7cd5c6444d6c1560076af907993361cc7accf3e2", size = 1814407, upload-time = "2024-11-12T20:37:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ec/ab9d5685f776a1965797eb68c4364c72edf57cd35beed2df49b34425d1df/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a52eecc6270ae15f0d51347c3f762703dd667ca486f127dc0a21e7e59856ae5", size = 1874462, upload-time = "2024-11-12T20:37:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/90/4f/7a4ea9a7ddf0a3409873f0787f355806f9e0b73f42f2acecacdd9a8eff0a/fastparquet-2024.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:e29ff7a367fafa57c6896fb6abc84126e2466811aefd3e4ad4070b9e18820e54", size = 671023, upload-time = "2024-11-12T20:37:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/08/76/068ac7ec9b4fc783be21a75a6a90b8c0654da4d46934d969e524ce287787/fastparquet-2024.11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dbad4b014782bd38b58b8e9f514fe958cfa7a6c4e187859232d29fd5c5ddd849", size = 915968, upload-time = "2024-11-12T20:37:52.861Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9e/6d3b4188ad64ed51173263c07109a5f18f9c84a44fa39ab524fca7420cda/fastparquet-2024.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:403d31109d398b6be7ce84fa3483fc277c6a23f0b321348c0a505eb098a041cb", size = 685399, upload-time = "2024-11-12T20:37:54.899Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6c/809220bc9fbe83d107df2d664c3fb62fb81867be8f5218ac66c2e6b6a358/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbbb9057a26acf0abad7adf58781ee357258b7708ee44a289e3bee97e2f55d42", size = 1758557, upload-time = "2024-11-12T20:37:56.553Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2c/b3b3e6ca2e531484289024138cd4709c22512b3fe68066d7f9849da4a76c/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e0e416e25c15daa174aad8ba991c2e9e5b0dc347e5aed5562124261400f87b", size = 1781052, upload-time = "2024-11-12T20:37:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/21/fe/97ed45092d0311c013996dae633122b7a51c5d9fe8dcbc2c840dc491201e/fastparquet-2024.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2d7f02f57231e6c86d26e9ea71953737202f20e948790e5d4db6d6a1a150dc", size = 1715797, upload-time = "2024-11-12T20:38:00.694Z" }, + { url = "https://files.pythonhosted.org/packages/24/df/02fa6aee6c0d53d1563b5bc22097076c609c4c5baa47056b0b4bed456fcf/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbe4468146b633d8f09d7b196fea0547f213cb5ce5f76e9d1beb29eaa9593a93", size = 1795682, upload-time = "2024-11-12T20:38:02.38Z" }, + { url = "https://files.pythonhosted.org/packages/b0/25/f4f87557589e1923ee0e3bebbc84f08b7c56962bf90f51b116ddc54f2c9f/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29d5c718817bcd765fc519b17f759cad4945974421ecc1931d3bdc3e05e57fa9", size = 1857842, upload-time = "2024-11-12T20:38:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/98cd0c39115879be1044d59c9b76e8292776e99bb93565bf990078fd11c4/fastparquet-2024.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:74a0b3c40ab373442c0fda96b75a36e88745d8b138fcc3a6143e04682cbbb8ca", size = 673269, upload-time = "2024-12-11T21:22:48.073Z" }, + { url = "https://files.pythonhosted.org/packages/47/e3/e7db38704be5db787270d43dde895eaa1a825ab25dc245e71df70860ec12/fastparquet-2024.11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59e5c5b51083d5b82572cdb7aed0346e3181e3ac9d2e45759da2e804bdafa7ee", size = 912523, upload-time = "2024-11-12T20:38:06.003Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/e3387c99293dae441634e7724acaa425b27de19a00ee3d546775dace54a9/fastparquet-2024.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdadf7b6bad789125b823bfc5b0a719ba5c4a2ef965f973702d3ea89cff057f6", size = 683779, upload-time = "2024-11-12T20:38:07.442Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/d112d0573d086b578bf04302a502e9a7605ea8f1244a7b8577cd945eec78/fastparquet-2024.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46b2db02fc2a1507939d35441c8ab211d53afd75d82eec9767d1c3656402859b", size = 1751113, upload-time = "2024-11-12T20:38:09.36Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a7/040507cee3a7798954e8fdbca21d2dbc532774b02b882d902b8a4a6849ef/fastparquet-2024.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3afdef2895c9f459135a00a7ed3ceafebfbce918a9e7b5d550e4fae39c1b64d", size = 1780496, upload-time = "2024-11-12T20:38:11.022Z" }, + { url = "https://files.pythonhosted.org/packages/bc/75/d0d9f7533d780ec167eede16ad88073ee71696150511126c31940e7f73aa/fastparquet-2024.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36b5c9bd2ffaaa26ff45d59a6cefe58503dd748e0c7fad80dd905749da0f2b9e", size = 1713608, upload-time = "2024-11-12T20:38:12.848Z" }, + { url = "https://files.pythonhosted.org/packages/30/fa/1d95bc86e45e80669c4f374b2ca26a9e5895a1011bb05d6341b4a7414693/fastparquet-2024.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b7df5d3b61a19d76e209fe8d3133759af1c139e04ebc6d43f3cc2d8045ef338", size = 1792779, upload-time = "2024-11-12T20:38:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/13/3d/c076beeb926c79593374c04662a9422a76650eef17cd1c8e10951340764a/fastparquet-2024.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b35823ac7a194134e5f82fa4a9659e42e8f9ad1f2d22a55fbb7b9e4053aabbb", size = 1851322, upload-time = "2024-11-12T20:38:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/1d0d47e64816002824d4a876644e8c65540fa23f91b701f0daa726931545/fastparquet-2024.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d20632964e65530374ff7cddd42cc06aa0a1388934903693d6d22592a5ba827b", size = 673266, upload-time = "2024-11-12T20:38:17.661Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "gtfs-realtime-bindings" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/55/ed46db9267d2615851bfba3c22b6c0e7f88751efb5d5d1468291935c7f65/gtfs-realtime-bindings-1.0.0.tar.gz", hash = "sha256:2e8ced8904400cc93ab7e8520adb6934cfa601edacc6f593fc2cb4448662bb47", size = 6197, upload-time = "2023-02-23T17:53:20.8Z" } + +[[package]] +name = "gtfs-rt-pipeline" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "celery" }, + { name = "django" }, + { name = "django-environ" }, + { name = "fastparquet" }, + { name = "gtfs-realtime-bindings" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "psycopg", extra = ["binary", "pool"] }, + { name = "python-dotenv" }, + { name = "redis" }, + { name = "requests" }, + { name = "scikit-learn" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "ipython" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-django" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipykernel" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'" }, + { name = "celery", specifier = ">=5.4" }, + { name = "django", specifier = ">=5.0" }, + { name = "django-environ", specifier = ">=0.11" }, + { name = "fastparquet", specifier = ">=2024.11.0" }, + { name = "gtfs-realtime-bindings", specifier = ">=1.0.0" }, + { name = "ipython", marker = "extra == 'dev'" }, + { name = "mypy", marker = "extra == 'dev'" }, + { name = "numpy", specifier = ">=2.3.4" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest-django", marker = "extra == 'dev'" }, + { name = "python-dotenv", specifier = ">=1.0" }, + { name = "redis", specifier = ">=5.0" }, + { name = "requests", specifier = ">=2.32" }, + { name = "scikit-learn", specifier = ">=1.7.2" }, +] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [{ name = "ipykernel", specifier = ">=7.1.0" }] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "9.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/71/a86262bf5a68bf211bcc71fe302af7e05f18a2852fdc610a854d20d085e6/ipython-9.5.0.tar.gz", hash = "sha256:129c44b941fe6d9b82d36fc7a7c18127ddb1d6f02f78f867f402e2e3adde3113", size = 4389137, upload-time = "2025-08-29T12:15:21.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl", hash = "sha256:88369ffa1d5817d609120daa523a6da06d02518e582347c29f8451732a9c5e72", size = 612426, upload-time = "2025-08-29T12:15:18.866Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", size = 21259519, upload-time = "2025-10-15T16:15:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", size = 14452796, upload-time = "2025-10-15T16:15:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", size = 5381639, upload-time = "2025-10-15T16:15:25.572Z" }, + { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", size = 6914296, upload-time = "2025-10-15T16:15:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", size = 14591904, upload-time = "2025-10-15T16:15:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", size = 16939602, upload-time = "2025-10-15T16:15:31.106Z" }, + { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", size = 16372661, upload-time = "2025-10-15T16:15:33.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", size = 18884682, upload-time = "2025-10-15T16:15:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" }, + { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, + { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, + { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" }, + { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" }, + { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "protobuf" +version = "6.32.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f1/0258a123c045afaf3c3b60c22ccff077bceeb24b8dc2c593270899353bd0/psycopg-3.2.10.tar.gz", hash = "sha256:0bce99269d16ed18401683a8569b2c5abd94f72f8364856d56c0389bcd50972a", size = 160380, upload-time = "2025-09-08T09:13:37.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/90/422ffbbeeb9418c795dae2a768db860401446af0c6768bc061ce22325f58/psycopg-3.2.10-py3-none-any.whl", hash = "sha256:ab5caf09a9ec42e314a21f5216dbcceac528e0e05142e42eea83a3b28b320ac3", size = 206586, upload-time = "2025-09-08T09:07:50.121Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] +pool = [ + { name = "psycopg-pool" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.10" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/8c/f15bd09a0cc09f010c1462f1cb846d7d2706f0f6226ef8e953328243edcc/psycopg_binary-3.2.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db0eb06a19e4c64a08db0db80875ede44939af6a2afc281762c338fad5d6e547", size = 4002654, upload-time = "2025-09-08T09:08:49.779Z" }, + { url = "https://files.pythonhosted.org/packages/c9/df/9b7c9db70b624b96544560d062c27030a817e932f1fa803b58e25b26dcdd/psycopg_binary-3.2.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d922fdd49ed17c558b6b2f9ae2054c3d0cced2a34e079ce5a41c86904d0203f7", size = 4074650, upload-time = "2025-09-08T09:08:57.53Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/7aba5874e1dfd90bc3dcd26dd9200ae65e1e6e169230759dad60139f1b99/psycopg_binary-3.2.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d557a94cd6d2e775b3af6cc0bd0ff0d9d641820b5cc3060ccf1f5ca2bf971217", size = 4630536, upload-time = "2025-09-08T09:09:03.492Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b1/a430d08b4eb28dc534181eb68a9c2a9e90b77c0e2933e338790534e7dce0/psycopg_binary-3.2.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:29b6bb87959515bc8b6abef10d8d23a9a681f03e48e9f0c8adb4b9fb7fa73f11", size = 4728387, upload-time = "2025-09-08T09:09:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d4/26d0fa9e8e7c05f0338024d2822a3740fac6093999443ad54e164f154bcc/psycopg_binary-3.2.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b29285474e3339d0840e1b5079fdb0481914108f92ec62de0c87ae333c60b24", size = 4413805, upload-time = "2025-09-08T09:09:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f2/d05c037c02e2ac4cb1c5b895c6c82428b3eaa0c48d08767b771bc2ea155a/psycopg_binary-3.2.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:62590dd113d10cd9c08251cb80b32e2e8aaf01ece04a700322e776b1d216959f", size = 3886830, upload-time = "2025-09-08T09:09:18.102Z" }, + { url = "https://files.pythonhosted.org/packages/8f/84/db3dee4335cd80c56e173a5ffbda6d17a7a10eeed030378d9adf3ab19ea7/psycopg_binary-3.2.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:764a5b9b40ad371c55dfdf95374d89e44a82fd62272d4fceebea0adb8930e2fb", size = 3568543, upload-time = "2025-09-08T09:09:22.765Z" }, + { url = "https://files.pythonhosted.org/packages/1b/45/4117274f24b8d49b8a9c1cb60488bb172ac9e57b8f804726115c332d16f8/psycopg_binary-3.2.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bd3676a04970cf825d2c771b0c147f91182c5a3653e0dbe958e12383668d0f79", size = 3610614, upload-time = "2025-09-08T09:09:27.534Z" }, + { url = "https://files.pythonhosted.org/packages/3c/22/f1b294dfc8af32a96a363aa99c0ebb530fc1c372a424c54a862dcf77ef47/psycopg_binary-3.2.10-cp311-cp311-win_amd64.whl", hash = "sha256:646048f46192c8d23786cc6ef19f35b7488d4110396391e407eca695fdfe9dcd", size = 2888340, upload-time = "2025-09-08T09:09:32.696Z" }, + { url = "https://files.pythonhosted.org/packages/a6/34/91c127fdedf8b270b1e3acc9f849d07ee8b80194379590c6f48dcc842924/psycopg_binary-3.2.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dee2f4d2adc9adacbfecf8254bd82f6ac95cff707e1b9b99aa721cd1ef16b47", size = 3983963, upload-time = "2025-09-08T09:09:38.454Z" }, + { url = "https://files.pythonhosted.org/packages/1e/03/1d10ce2bf70cf549a8019639dc0c49be03e41092901d4324371a968b8c01/psycopg_binary-3.2.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8b45e65383da9c4a42a56f817973e521e893f4faae897fe9f1a971f9fe799742", size = 4069171, upload-time = "2025-09-08T09:09:44.395Z" }, + { url = "https://files.pythonhosted.org/packages/4c/5e/39cb924d6e119145aa5fc5532f48e79c67e13a76675e9366c327098db7b5/psycopg_binary-3.2.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:484d2b1659afe0f8f1cef5ea960bb640e96fa864faf917086f9f833f5c7a8034", size = 4610780, upload-time = "2025-09-08T09:09:53.073Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/5a1282ebc4e39f5890abdd4bb7edfe9d19e4667497a1793ad288a8b81826/psycopg_binary-3.2.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3bb4046973264ebc8cb7e20a83882d68577c1f26a6f8ad4fe52e4468cd9a8eee", size = 4700479, upload-time = "2025-09-08T09:09:58.183Z" }, + { url = "https://files.pythonhosted.org/packages/af/7a/e1c06e558ca3f37b7e6b002e555ebcfce0bf4dee6f3ae589a7444e16ce17/psycopg_binary-3.2.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14bcbcac0cab465d88b2581e43ec01af4b01c9833e663f1352e05cb41be19e44", size = 4391772, upload-time = "2025-09-08T09:10:04.406Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d6/56f449c86988c9a97dc6c5f31d3689cfe8aedb37f2a02bd3e3882465d385/psycopg_binary-3.2.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70bb7f665587dfd79e69f48b34efe226149454d7aab138ed22d5431d703de2f6", size = 3858214, upload-time = "2025-09-08T09:10:09.693Z" }, + { url = "https://files.pythonhosted.org/packages/93/56/f9eed67c9a1701b1e315f3687ff85f2f22a0a7d0eae4505cff65ef2f2679/psycopg_binary-3.2.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2fe9eaa367f6171ab1a21a7dcb335eb2398be7f8bb7e04a20e2260aedc6f782", size = 3528051, upload-time = "2025-09-08T09:10:13.423Z" }, + { url = "https://files.pythonhosted.org/packages/25/cc/636709c72540cb859566537c0a03e46c3d2c4c4c2e13f78df46b6c4082b3/psycopg_binary-3.2.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:299834cce3eec0c48aae5a5207fc8f0c558fd65f2ceab1a36693329847da956b", size = 3580117, upload-time = "2025-09-08T09:10:17.81Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a8/a2c822fa06b0dbbb8ad4b0221da2534f77bac54332d2971dbf930f64be5a/psycopg_binary-3.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:e037aac8dc894d147ef33056fc826ee5072977107a3fdf06122224353a057598", size = 2878872, upload-time = "2025-09-08T09:10:22.162Z" }, + { url = "https://files.pythonhosted.org/packages/3a/80/db840f7ebf948ab05b4793ad34d4da6ad251829d6c02714445ae8b5f1403/psycopg_binary-3.2.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:55b14f2402be027fe1568bc6c4d75ac34628ff5442a70f74137dadf99f738e3b", size = 3982057, upload-time = "2025-09-08T09:10:28.725Z" }, + { url = "https://files.pythonhosted.org/packages/2d/53/39308328bb8388b1ec3501a16128c5ada405f217c6d91b3d921b9f3c5604/psycopg_binary-3.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:43d803fb4e108a67c78ba58f3e6855437ca25d56504cae7ebbfbd8fce9b59247", size = 4066830, upload-time = "2025-09-08T09:10:34.083Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5a/18e6f41b40c71197479468cb18703b2999c6e4ab06f9c05df3bf416a55d7/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:470594d303928ab72a1ffd179c9c7bde9d00f76711d6b0c28f8a46ddf56d9807", size = 4610747, upload-time = "2025-09-08T09:10:39.697Z" }, + { url = "https://files.pythonhosted.org/packages/be/ab/9198fed279aca238c245553ec16504179d21aad049958a2865d0aa797db4/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a1d4e4d309049e3cb61269652a3ca56cb598da30ecd7eb8cea561e0d18bc1a43", size = 4700301, upload-time = "2025-09-08T09:10:44.715Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0d/59024313b5e6c5da3e2a016103494c609d73a95157a86317e0f600c8acb3/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a92ff1c2cd79b3966d6a87e26ceb222ecd5581b5ae4b58961f126af806a861ed", size = 4392679, upload-time = "2025-09-08T09:10:49.106Z" }, + { url = "https://files.pythonhosted.org/packages/ff/47/21ef15d8a66e3a7a76a177f885173d27f0c5cbe39f5dd6eda9832d6b4e19/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac0365398947879c9827b319217096be727da16c94422e0eb3cf98c930643162", size = 3857881, upload-time = "2025-09-08T09:10:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/af/35/c5e5402ccd40016f15d708bbf343b8cf107a58f8ae34d14dc178fdea4fd4/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:42ee399c2613b470a87084ed79b06d9d277f19b0457c10e03a4aef7059097abc", size = 3531135, upload-time = "2025-09-08T09:11:03.346Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e2/9b82946859001fe5e546c8749991b8b3b283f40d51bdc897d7a8e13e0a5e/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2028073fc12cd70ba003309d1439c0c4afab4a7eee7653b8c91213064fffe12b", size = 3581813, upload-time = "2025-09-08T09:11:08.76Z" }, + { url = "https://files.pythonhosted.org/packages/c5/91/c10cfccb75464adb4781486e0014ecd7c2ad6decf6cbe0afd8db65ac2bc9/psycopg_binary-3.2.10-cp313-cp313-win_amd64.whl", hash = "sha256:8390db6d2010ffcaf7f2b42339a2da620a7125d37029c1f9b72dfb04a8e7be6f", size = 2881466, upload-time = "2025-09-08T09:11:14.078Z" }, + { url = "https://files.pythonhosted.org/packages/fd/89/b0702ba0d007cc787dd7a205212c8c8cae229d1e7214c8e27bdd3b13d33e/psycopg_binary-3.2.10-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b34c278a58aa79562afe7f45e0455b1f4cad5974fc3d5674cc5f1f9f57e97fc5", size = 3981253, upload-time = "2025-09-08T09:11:19.864Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/e51ac72ac34d1d8ea7fd861008ad8de60e56997f5bd3fbae7536570f6f58/psycopg_binary-3.2.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810f65b9ef1fe9dddb5c05937884ea9563aaf4e1a2c3d138205231ed5f439511", size = 4067542, upload-time = "2025-09-08T09:11:25.366Z" }, + { url = "https://files.pythonhosted.org/packages/d6/27/49625c79ae89959a070c1fb63ebb5c6eed426fa09e15086b6f5b626fcdc2/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8923487c3898c65e1450847e15d734bb2e6adbd2e79d2d1dd5ad829a1306bdc0", size = 4615338, upload-time = "2025-09-08T09:11:31.079Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0d/9fdb5482f50f56303770ea8a3b1c1f32105762da731c7e2a4f425e0b3887/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7950ff79df7a453ac8a7d7a74694055b6c15905b0a2b6e3c99eb59c51a3f9bf7", size = 4703401, upload-time = "2025-09-08T09:11:38.718Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f3/eb2f75ca2c090bf1d0c90d6da29ef340876fe4533bcfc072a9fd94dd52b4/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c2b95e83fda70ed2b0b4fadd8538572e4a4d987b721823981862d1ab56cc760", size = 4393458, upload-time = "2025-09-08T09:11:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/887abe0591b2f1c1af31164b9efb46c5763e4418f403503bc9fbddaa02ef/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20384985fbc650c09a547a13c6d7f91bb42020d38ceafd2b68b7fc4a48a1f160", size = 3863733, upload-time = "2025-09-08T09:11:49.237Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/9446e3a84187220a98657ef778518f9b44eba55b1f6c3e8300d229ec9930/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1f6982609b8ff8fcd67299b67cd5787da1876f3bb28fedd547262cfa8ddedf94", size = 3535121, upload-time = "2025-09-08T09:11:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e1/f0382c956bfaa951a0dbd4d5a354acf093ef7e5219996958143dfd2bf37d/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bf30dcf6aaaa8d4779a20d2158bdf81cc8e84ce8eee595d748a7671c70c7b890", size = 3584235, upload-time = "2025-09-08T09:12:01.118Z" }, + { url = "https://files.pythonhosted.org/packages/5a/dd/464bd739bacb3b745a1c93bc15f20f0b1e27f0a64ec693367794b398673b/psycopg_binary-3.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:d5c6a66a76022af41970bf19f51bc6bf87bd10165783dd1d40484bfd87d6b382", size = 2973554, upload-time = "2025-09-08T09:12:05.884Z" }, +] + +[[package]] +name = "psycopg-pool" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-django" +version = "4.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "pytokens" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/5f/e959a442435e24f6fb5a01aec6c657079ceaca1b3baf18561c3728d681da/pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044", size = 12171, upload-time = "2025-02-19T14:51:22.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e5/63bed382f6a7a5ba70e7e132b8b7b8abbcf4888ffa6be4877698dcfbed7d/pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b", size = 12046, upload-time = "2025-02-19T14:51:18.694Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92", size = 36619956, upload-time = "2025-09-11T17:39:20.5Z" }, + { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e", size = 28931117, upload-time = "2025-09-11T17:39:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173", size = 20921997, upload-time = "2025-09-11T17:39:34.892Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d", size = 23523374, upload-time = "2025-09-11T17:39:40.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2", size = 33583702, upload-time = "2025-09-11T17:39:49.011Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9", size = 35883427, upload-time = "2025-09-11T17:39:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3", size = 36212940, upload-time = "2025-09-11T17:40:06.013Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88", size = 38865092, upload-time = "2025-09-11T17:40:15.143Z" }, + { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa", size = 38687626, upload-time = "2025-09-11T17:40:24.041Z" }, + { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c", size = 25503506, upload-time = "2025-09-11T17:40:30.703Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, + { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, + { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, + { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, + { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, + { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, + { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, + { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, + { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, + { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] diff --git a/eta_prediction/pyproject.toml b/eta_prediction/pyproject.toml new file mode 100644 index 0000000..64a71e7 --- /dev/null +++ b/eta_prediction/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "eta_prediction" +version = "0.1.0" +description = "Utilities for exploring GTFS-Realtime feeds (Vehicle Positions, Trip Updates, Alerts)." +authors = [ + { name = "JΓ¦" } +] +license = { text = "MIT" } +readme = "README.md" +requires-python = ">=3.11" + +dependencies = [ + "Django>=5.0", + "celery>=5.4", + "redis>=5.0", + "psycopg[binary,pool]>=3.2", + "django-environ>=0.11", + "python-dotenv>=1.0", + "requests>=2.32", + "gtfs-realtime-bindings>=1.0.0", + "numpy>=1.26", + "pandas>=2.3.3", + "fastparquet>=2024.11.0", + "psycopg>=3.2.11", + "matplotlib>=3.9.4", + "scikit-learn>=1.7.2", + "xgboost>=3.1.2", +] + +[tool.uv.workspace] +members = [ + "feature_engineering/proj", + "feature_engineering", +] diff --git a/eta_prediction/uv.lock b/eta_prediction/uv.lock new file mode 100644 index 0000000..c2f1277 --- /dev/null +++ b/eta_prediction/uv.lock @@ -0,0 +1,1819 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[manifest] +members = [ + "eta-prediction", + "feature-engineering", +] + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "asgiref" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/08/4dfec9b90758a59acc6be32ac82e98d1fbfc321cb5cfa410436dbacf821c/asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e", size = 37483, upload-time = "2025-10-05T09:15:06.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/9c/fc2331f538fbf7eedba64b2052e99ccf9ba9d6888e2f41441ee28847004b/asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734", size = 24050, upload-time = "2025-10-05T09:15:05.11Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "billiard" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/6a/1405343016bce8354b29d90aad6b0bf6485b5e60404516e4b9a3a9646cf0/billiard-4.2.2.tar.gz", hash = "sha256:e815017a062b714958463e07ba15981d802dc53d41c5b69d28c5a7c238f8ecf3", size = 155592, upload-time = "2025-09-20T14:44:40.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/80/ef8dff49aae0e4430f81842f7403e14e0ca59db7bbaf7af41245b67c6b25/billiard-4.2.2-py3-none-any.whl", hash = "sha256:4bc05dcf0d1cc6addef470723aac2a6232f3c7ed7475b0b580473a9145829457", size = 86896, upload-time = "2025-09-20T14:44:39.157Z" }, +] + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813, upload-time = "2024-10-07T19:20:50.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/cc/7496bb63a9b06a954d3d0ac9fe7a73f3bf1cd92d7a58877c27f4ad1e9d41/black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", size = 1607468, upload-time = "2024-10-07T19:26:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e3/69a738fb5ba18b5422f50b4f143544c664d7da40f09c13969b2fd52900e0/black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", size = 1437270, upload-time = "2024-10-07T19:25:24.291Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9b/2db8045b45844665c720dcfe292fdaf2e49825810c0103e1191515fc101a/black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", size = 1737061, upload-time = "2024-10-07T19:23:52.18Z" }, + { url = "https://files.pythonhosted.org/packages/a3/95/17d4a09a5be5f8c65aa4a361444d95edc45def0de887810f508d3f65db7a/black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", size = 1423293, upload-time = "2024-10-07T19:24:41.7Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256, upload-time = "2024-10-07T19:27:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534, upload-time = "2024-10-07T19:26:44.953Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892, upload-time = "2024-10-07T19:24:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796, upload-time = "2024-10-07T19:25:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986, upload-time = "2024-10-07T19:28:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085, upload-time = "2024-10-07T19:28:12.093Z" }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928, upload-time = "2024-10-07T19:24:15.233Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875, upload-time = "2024-10-07T19:24:42.762Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898, upload-time = "2024-10-07T19:20:48.317Z" }, +] + +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + +[[package]] +name = "cligj" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/837dbd5d8430fd0f01ed72c4cfb2f548180f4c68c635df84ce87956cff32/cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27", size = 9803, upload-time = "2021-05-28T21:23:27.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069, upload-time = "2021-05-28T21:23:26.877Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cramjam" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/12/34bf6e840a79130dfd0da7badfb6f7810b8fcfd60e75b0539372667b41b6/cramjam-2.11.0.tar.gz", hash = "sha256:5c82500ed91605c2d9781380b378397012e25127e89d64f460fea6aeac4389b4", size = 99100, upload-time = "2025-07-27T21:25:07.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/89/8001f6a9b6b6e9fa69bec5319789083475d6f26d52aaea209d3ebf939284/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:04cfa39118570e70e920a9b75c733299784b6d269733dbc791d9aaed6edd2615", size = 3559272, upload-time = "2025-07-27T21:22:01.988Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f3/001d00070ca92e5fbe6aacc768e455568b0cde46b0eb944561a4ea132300/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:66a18f68506290349a256375d7aa2f645b9f7993c10fc4cc211db214e4e61d2b", size = 1861743, upload-time = "2025-07-27T21:22:03.754Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/041a3af01bf3f6158f120070f798546d4383b962b63c35cd91dcbf193e17/cramjam-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50e7d65533857736cd56f6509cf2c4866f28ad84dd15b5bdbf2f8a81e77fa28a", size = 1699631, upload-time = "2025-07-27T21:22:05.192Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/5358b238808abebd0c949c42635c3751204ca7cf82b29b984abe9f5e33c8/cramjam-2.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f71989668458fc327ac15396db28d92df22f8024bb12963929798b2729d2df5", size = 2025603, upload-time = "2025-07-27T21:22:06.726Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/19dba7c03a27408d8d11b5a7a4a7908459cfd4e6f375b73264dc66517bf6/cramjam-2.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee77ac543f1e2b22af1e8be3ae589f729491b6090582340aacd77d1d757d9569", size = 1766283, upload-time = "2025-07-27T21:22:08.568Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/40e4b3408501d886d082db465c33971655fe82573c535428e52ab905f4d0/cramjam-2.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad52784120e7e4d8a0b5b0517d185b8bf7f74f5e17272857ddc8951a628d9be1", size = 1854407, upload-time = "2025-07-27T21:22:10.518Z" }, + { url = "https://files.pythonhosted.org/packages/36/6e/c1b60ceb6d7ea6ff8b0bf197520aefe23f878bf2bfb0de65f2b0c2f82cd1/cramjam-2.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b86f8e6d9c1b3f9a75b2af870c93ceee0f1b827cd2507387540e053b35d7459", size = 2035793, upload-time = "2025-07-27T21:22:12.504Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ad/32a8d5f4b1e3717787945ec6d71bd1c6e6bccba4b7e903fc0d9d4e4b08c3/cramjam-2.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:320d61938950d95da2371b46c406ec433e7955fae9f396c8e1bf148ffc187d11", size = 2067499, upload-time = "2025-07-27T21:22:14.067Z" }, + { url = "https://files.pythonhosted.org/packages/ff/cd/3b5a662736ea62ff7fa4c4a10a85e050bfdaad375cc53dc80427e8afe41c/cramjam-2.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41eafc8c1653a35a5c7e75ad48138f9f60085cc05cd99d592e5298552d944e9f", size = 1981853, upload-time = "2025-07-27T21:22:15.908Z" }, + { url = "https://files.pythonhosted.org/packages/26/8e/1dbcfaaa7a702ee82ee683ec3a81656934dd7e04a7bc4ee854033686f98a/cramjam-2.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03a7316c6bf763dfa34279335b27702321da44c455a64de58112968c0818ec4a", size = 2034514, upload-time = "2025-07-27T21:22:17.352Z" }, + { url = "https://files.pythonhosted.org/packages/50/62/f11709bfdce74af79a88b410dcb76dedc97612166e759136931bf63cfd7b/cramjam-2.11.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:244c2ed8bd7ccbb294a2abe7ca6498db7e89d7eb5e744691dc511a7dc82e65ca", size = 2155343, upload-time = "2025-07-27T21:22:18.854Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/3b98b61841a5376d9a9b8468ae58753a8e6cf22be9534a0fa5af4d8621cc/cramjam-2.11.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:405f8790bad36ce0b4bbdb964ad51507bfc7942c78447f25cb828b870a1d86a0", size = 2169367, upload-time = "2025-07-27T21:22:20.389Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/bd5db5c49dbebc8b002f1c4983101b28d2e7fc9419753db1c31ec22b03ef/cramjam-2.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b1b751a5411032b08fb3ac556160229ca01c6bbe4757bb3a9a40b951ebaac23", size = 2159334, upload-time = "2025-07-27T21:22:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/34/32/203c57acdb6eea727e7078b2219984e64ed4ad043c996ed56321301ba167/cramjam-2.11.0-cp311-cp311-win32.whl", hash = "sha256:5251585608778b9ac8effed544933df7ad85b4ba21ee9738b551f17798b215ac", size = 1605313, upload-time = "2025-07-27T21:22:24.126Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bd/102d6deb87a8524ac11cddcd31a7612b8f20bf9b473c3c645045e3b957c7/cramjam-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:dca88bc8b68ce6d35dafd8c4d5d59a238a56c43fa02b74c2ce5f9dfb0d1ccb46", size = 1710991, upload-time = "2025-07-27T21:22:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/7c84c913a5fae85b773a9dcf8874390f9d68ba0fcc6630efa7ff1541b950/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dba5c14b8b4f73ea1e65720f5a3fe4280c1d27761238378be8274135c60bbc6e", size = 3553368, upload-time = "2025-07-27T21:22:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cc/4f6d185d8a744776f53035e72831ff8eefc2354f46ab836f4bd3c4f6c138/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:11eb40722b3fcf3e6890fba46c711bf60f8dc26360a24876c85e52d76c33b25b", size = 1860014, upload-time = "2025-07-27T21:22:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a8/626c76263085c6d5ded0e71823b411e9522bfc93ba6cc59855a5869296e7/cramjam-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aeb26e2898994b6e8319f19a4d37c481512acdcc6d30e1b5ecc9d8ec57e835cb", size = 1693512, upload-time = "2025-07-27T21:22:30.999Z" }, + { url = "https://files.pythonhosted.org/packages/e9/52/0851a16a62447532e30ba95a80e638926fdea869a34b4b5b9d0a020083ba/cramjam-2.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f8d82081ed7d8fe52c982bd1f06e4c7631a73fe1fb6d4b3b3f2404f87dc40fe", size = 2025285, upload-time = "2025-07-27T21:22:32.954Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/122e444f59dbc216451d8e3d8282c9665dc79eaf822f5f1470066be1b695/cramjam-2.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:092a3ec26e0a679305018380e4f652eae1b6dfe3fc3b154ee76aa6b92221a17c", size = 1761327, upload-time = "2025-07-27T21:22:34.484Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bc/3a0189aef1af2b29632c039c19a7a1b752bc21a4053582a5464183a0ad3d/cramjam-2.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:529d6d667c65fd105d10bd83d1cd3f9869f8fd6c66efac9415c1812281196a92", size = 1854075, upload-time = "2025-07-27T21:22:36.157Z" }, + { url = "https://files.pythonhosted.org/packages/2e/80/8a6343b13778ce52d94bb8d5365a30c3aa951276b1857201fe79d7e2ad25/cramjam-2.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:555eb9c90c450e0f76e27d9ff064e64a8b8c6478ab1a5594c91b7bc5c82fd9f0", size = 2032710, upload-time = "2025-07-27T21:22:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/df/6b/cd1778a207c29eda10791e3dfa018b588001928086e179fc71254793c625/cramjam-2.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5edf4c9e32493035b514cf2ba0c969d81ccb31de63bd05490cc8bfe3b431674e", size = 2068353, upload-time = "2025-07-27T21:22:39.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f0/5c2a5cd5711032f3b191ca50cb786c17689b4a9255f9f768866e6c9f04d9/cramjam-2.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2fe41f48c4d58d923803383b0737f048918b5a0d10390de9628bb6272b107", size = 1978104, upload-time = "2025-07-27T21:22:41.106Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8b/b363a5fb2c3347504fe9a64f8d0f1e276844f0e532aa7162c061cd1ffee4/cramjam-2.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca14cf1cabdb0b77d606db1bb9e9ca593b1dbd421fcaf251ec9a5431ec449f3", size = 2030779, upload-time = "2025-07-27T21:22:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/78/7b/d83dad46adb6c988a74361f81ad9c5c22642be53ad88616a19baedd06243/cramjam-2.11.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:309e95bf898829476bccf4fd2c358ec00e7ff73a12f95a3cdeeba4bb1d3683d5", size = 2155297, upload-time = "2025-07-27T21:22:44.6Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/60d9be4cb33d8740a4aa94c7513f2ef3c4eba4fd13536f086facbafade71/cramjam-2.11.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:86dca35d2f15ef22922411496c220f3c9e315d5512f316fe417461971cc1648d", size = 2169255, upload-time = "2025-07-27T21:22:46.534Z" }, + { url = "https://files.pythonhosted.org/packages/11/b0/4a595f01a243aec8ad272b160b161c44351190c35d98d7787919d962e9e5/cramjam-2.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:193c6488bd2f514cbc0bef5c18fad61a5f9c8d059dd56edf773b3b37f0e85496", size = 2155651, upload-time = "2025-07-27T21:22:48.46Z" }, + { url = "https://files.pythonhosted.org/packages/38/47/7776659aaa677046b77f527106e53ddd47373416d8fcdb1e1a881ec5dc06/cramjam-2.11.0-cp312-cp312-win32.whl", hash = "sha256:514e2c008a8b4fa823122ca3ecab896eac41d9aa0f5fc881bd6264486c204e32", size = 1603568, upload-time = "2025-07-27T21:22:50.084Z" }, + { url = "https://files.pythonhosted.org/packages/75/b1/d53002729cfd94c5844ddfaf1233c86d29f2dbfc1b764a6562c41c044199/cramjam-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:53fed080476d5f6ad7505883ec5d1ec28ba36c2273db3b3e92d7224fe5e463db", size = 1709287, upload-time = "2025-07-27T21:22:51.534Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/406c5dc0f8e82385519d8c299c40fd6a56d97eca3fcd6f5da8dad48de75b/cramjam-2.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2c289729cc1c04e88bafa48b51082fb462b0a57dbc96494eab2be9b14dca62af", size = 3553330, upload-time = "2025-07-27T21:22:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/00/ad/4186884083d6e4125b285903e17841827ab0d6d0cffc86216d27ed91e91d/cramjam-2.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:045201ee17147e36cf43d8ae2fa4b4836944ac672df5874579b81cf6d40f1a1f", size = 1859756, upload-time = "2025-07-27T21:22:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/54/01/91b485cf76a7efef638151e8a7d35784dae2c4ff221b1aec2c083e4b106d/cramjam-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:619cd195d74c9e1d2a3ad78d63451d35379c84bd851aec552811e30842e1c67a", size = 1693609, upload-time = "2025-07-27T21:22:56.331Z" }, + { url = "https://files.pythonhosted.org/packages/cd/84/d0c80d279b2976870fc7d10f15dcb90a3c10c06566c6964b37c152694974/cramjam-2.11.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6eb3ae5ab72edb2ed68bdc0f5710f0a6cad7fd778a610ec2c31ee15e32d3921e", size = 2024912, upload-time = "2025-07-27T21:22:57.915Z" }, + { url = "https://files.pythonhosted.org/packages/d6/70/88f2a5cb904281ed5d3c111b8f7d5366639817a5470f059bcd26833fc870/cramjam-2.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7da3f4b19e3078f9635f132d31b0a8196accb2576e3213ddd7a77f93317c20", size = 1760715, upload-time = "2025-07-27T21:22:59.528Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/cf5b02081132537d28964fb385fcef9ed9f8a017dd7d8c59d317e53ba50d/cramjam-2.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57286b289cd557ac76c24479d8ecfb6c3d5b854cce54ccc7671f9a2f5e2a2708", size = 1853782, upload-time = "2025-07-27T21:23:01.07Z" }, + { url = "https://files.pythonhosted.org/packages/57/27/63525087ed40a53d1867021b9c4858b80cc86274ffe7225deed067d88d92/cramjam-2.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28952fbbf8b32c0cb7fa4be9bcccfca734bf0d0989f4b509dc7f2f70ba79ae06", size = 2032354, upload-time = "2025-07-27T21:23:03.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ef/dbba082c6ebfb6410da4dd39a64e654d7194fcfd4567f85991a83fa4ec32/cramjam-2.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78ed2e4099812a438b545dfbca1928ec825e743cd253bc820372d6ef8c3adff4", size = 2068007, upload-time = "2025-07-27T21:23:04.526Z" }, + { url = "https://files.pythonhosted.org/packages/35/ce/d902b9358a46a086938feae83b2251720e030f06e46006f4c1fc0ac9da20/cramjam-2.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9aecd5c3845d415bd6c9957c93de8d93097e269137c2ecb0e5a5256374bdc8", size = 1977485, upload-time = "2025-07-27T21:23:06.058Z" }, + { url = "https://files.pythonhosted.org/packages/e8/03/982f54553244b0afcbdb2ad2065d460f0ab05a72a96896a969a1ca136a1e/cramjam-2.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:362fcf4d6f5e1242a4540812455f5a594949190f6fbc04f2ffbfd7ae0266d788", size = 2030447, upload-time = "2025-07-27T21:23:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/74/5f/748e54cdb665ec098ec519e23caacc65fc5ae58718183b071e33fc1c45b4/cramjam-2.11.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:13240b3dea41b1174456cb9426843b085dc1a2bdcecd9ee2d8f65ac5703374b0", size = 2154949, upload-time = "2025-07-27T21:23:09.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/81/c4e6cb06ed69db0dc81f9a8b1dc74995ebd4351e7a1877143f7031ff2700/cramjam-2.11.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:c54eed83726269594b9086d827decc7d2015696e31b99bf9b69b12d9063584fe", size = 2168925, upload-time = "2025-07-27T21:23:10.976Z" }, + { url = "https://files.pythonhosted.org/packages/13/5b/966365523ce8290a08e163e3b489626c5adacdff2b3da9da1b0823dfb14e/cramjam-2.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f8195006fdd0fc0a85b19df3d64a3ef8a240e483ae1dfc7ac6a4316019eb5df2", size = 2154950, upload-time = "2025-07-27T21:23:12.514Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7d/7f8eb5c534b72b32c6eb79d74585bfee44a9a5647a14040bb65c31c2572d/cramjam-2.11.0-cp313-cp313-win32.whl", hash = "sha256:ccf30e3fe6d770a803dcdf3bb863fa44ba5dc2664d4610ba2746a3c73599f2e4", size = 1603199, upload-time = "2025-07-27T21:23:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/37/05/47b5e0bf7c41a3b1cdd3b7c2147f880c93226a6bef1f5d85183040cbdece/cramjam-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ee36348a204f0a68b03400f4736224e9f61d1c6a1582d7f875c1ca56f0254268", size = 1708924, upload-time = "2025-07-27T21:23:16.332Z" }, + { url = "https://files.pythonhosted.org/packages/de/07/a1051cdbbe6d723df16d756b97f09da7c1adb69e29695c58f0392bc12515/cramjam-2.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7ba5e38c9fbd06f086f4a5a64a1a5b7b417cd3f8fc07a20e5c03651f72f36100", size = 3554141, upload-time = "2025-07-27T21:23:17.938Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/58487d2e16ef3d04f51a7c7f0e69823e806744b4c21101e89da4873074bc/cramjam-2.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8adeee57b41fe08e4520698a4b0bd3cc76dbd81f99424b806d70a5256a391d3", size = 1860353, upload-time = "2025-07-27T21:23:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/67/b4/67f6254d166ffbcc9d5fa1b56876eaa920c32ebc8e9d3d525b27296b693b/cramjam-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b96a74fa03a636c8a7d76f700d50e9a8bc17a516d6a72d28711225d641e30968", size = 1693832, upload-time = "2025-07-27T21:23:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/55/a3/4e0b31c0d454ae70c04684ed7c13d3c67b4c31790c278c1e788cb804fa4a/cramjam-2.11.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c3811a56fa32e00b377ef79121c0193311fd7501f0fb378f254c7f083cc1fbe0", size = 2027080, upload-time = "2025-07-27T21:23:23.303Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c7/5e8eed361d1d3b8be14f38a54852c5370cc0ceb2c2d543b8ba590c34f080/cramjam-2.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d927e87461f8a0d448e4ab5eb2bca9f31ca5d8ea86d70c6f470bb5bc666d7e", size = 1761543, upload-time = "2025-07-27T21:23:24.991Z" }, + { url = "https://files.pythonhosted.org/packages/09/0c/06b7f8b0ce9fde89470505116a01fc0b6cb92d406c4fb1e46f168b5d3fa5/cramjam-2.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f1f5c450121430fd89cb5767e0a9728ecc65997768fd4027d069cb0368af62f9", size = 1854636, upload-time = "2025-07-27T21:23:26.987Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c6/6ebc02c9d5acdf4e5f2b1ec6e1252bd5feee25762246798ae823b3347457/cramjam-2.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:724aa7490be50235d97f07e2ca10067927c5d7f336b786ddbc868470e822aa25", size = 2032715, upload-time = "2025-07-27T21:23:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/a122971c23f5ca4b53e4322c647ac7554626c95978f92d19419315dddd05/cramjam-2.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54c4637122e7cfd7aac5c1d3d4c02364f446d6923ea34cf9d0e8816d6e7a4936", size = 2069039, upload-time = "2025-07-27T21:23:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f6121b90b86b9093c066889274d26a1de3f29969d45c2ed1ecbe2033cb78/cramjam-2.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17eb39b1696179fb471eea2de958fa21f40a2cd8bf6b40d428312d5541e19dc4", size = 1979566, upload-time = "2025-07-27T21:23:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/f95bc57fd7f4166ce6da816cfa917fb7df4bb80e669eb459d85586498414/cramjam-2.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:36aa5a798aa34e11813a80425a30d8e052d8de4a28f27bfc0368cfc454d1b403", size = 2030905, upload-time = "2025-07-27T21:23:33.696Z" }, + { url = "https://files.pythonhosted.org/packages/fc/52/e429de4e8bc86ee65e090dae0f87f45abd271742c63fb2d03c522ffde28a/cramjam-2.11.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:449fca52774dc0199545fbf11f5128933e5a6833946707885cf7be8018017839", size = 2155592, upload-time = "2025-07-27T21:23:35.375Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6c/65a7a0207787ad39ad804af4da7f06a60149de19481d73d270b540657234/cramjam-2.11.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:d87d37b3d476f4f7623c56a232045d25bd9b988314702ea01bd9b4a94948a778", size = 2170839, upload-time = "2025-07-27T21:23:37.197Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/5c5db505ba692bc844246b066e23901d5905a32baf2f33719c620e65887f/cramjam-2.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:26cb45c47d71982d76282e303931c6dd4baee1753e5d48f9a89b3a63e690b3a3", size = 2157236, upload-time = "2025-07-27T21:23:38.854Z" }, + { url = "https://files.pythonhosted.org/packages/b0/22/88e6693e60afe98901e5bbe91b8dea193e3aa7f42e2770f9c3339f5c1065/cramjam-2.11.0-cp314-cp314-win32.whl", hash = "sha256:4efe919d443c2fd112fe25fe636a52f9628250c9a50d9bddb0488d8a6c09acc6", size = 1604136, upload-time = "2025-07-27T21:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f8/01618801cd59ccedcc99f0f96d20be67d8cfc3497da9ccaaad6b481781dd/cramjam-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ccec3524ea41b9abd5600e3e27001fd774199dbb4f7b9cb248fcee37d4bda84c", size = 1710272, upload-time = "2025-07-27T21:23:42.236Z" }, + { url = "https://files.pythonhosted.org/packages/40/81/6cdb3ed222d13ae86bda77aafe8d50566e81a1169d49ed195b6263610704/cramjam-2.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:966ac9358b23d21ecd895c418c048e806fd254e46d09b1ff0cdad2eba195ea3e", size = 3559671, upload-time = "2025-07-27T21:23:44.504Z" }, + { url = "https://files.pythonhosted.org/packages/cb/43/52b7e54fe5ba1ef0270d9fdc43dabd7971f70ea2d7179be918c997820247/cramjam-2.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:387f09d647a0d38dcb4539f8a14281f8eb6bb1d3e023471eb18a5974b2121c86", size = 1867876, upload-time = "2025-07-27T21:23:46.987Z" }, + { url = "https://files.pythonhosted.org/packages/9d/28/30d5b8d10acd30db3193bc562a313bff722888eaa45cfe32aa09389f2b24/cramjam-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:665b0d8fbbb1a7f300265b43926457ec78385200133e41fef19d85790fc1e800", size = 1695562, upload-time = "2025-07-27T21:23:48.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/86/ec806f986e01b896a650655024ea52a13e25c3ac8a3a382f493089483cdc/cramjam-2.11.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ca905387c7a371531b9622d93471be4d745ef715f2890c3702479cd4fc85aa51", size = 2025056, upload-time = "2025-07-27T21:23:50.404Z" }, + { url = "https://files.pythonhosted.org/packages/09/43/c2c17586b90848d29d63181f7d14b8bd3a7d00975ad46e3edf2af8af7e1f/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1aa56aef2c8af55a21ed39040a94a12b53fb23beea290f94d19a76027e2ffb", size = 1764084, upload-time = "2025-07-27T21:23:52.265Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a9/68bc334fadb434a61df10071dc8606702aa4f5b6cdb2df62474fc21d2845/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5db59c1cdfaa2ab85cc988e602d6919495f735ca8a5fd7603608eb1e23c26d5", size = 1854859, upload-time = "2025-07-27T21:23:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4e/b48e67835b5811ec5e9cb2e2bcba9c3fd76dab3e732569fe801b542c6ca9/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1f893014f00fe5e89a660a032e813bf9f6d91de74cd1490cdb13b2b59d0c9a3", size = 2035970, upload-time = "2025-07-27T21:23:55.758Z" }, + { url = "https://files.pythonhosted.org/packages/c4/70/d2ac33d572b4d90f7f0f2c8a1d60fb48f06b128fdc2c05f9b49891bb0279/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c26a1eb487947010f5de24943bd7c422dad955b2b0f8650762539778c380ca89", size = 2069320, upload-time = "2025-07-27T21:23:57.494Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4c/85cec77af4a74308ba5fca8e296c4e2f80ec465c537afc7ab1e0ca2f9a00/cramjam-2.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d5c8bfb438d94e7b892d1426da5fc4b4a5370cc360df9b8d9d77c33b896c37e", size = 1982668, upload-time = "2025-07-27T21:23:59.126Z" }, + { url = "https://files.pythonhosted.org/packages/55/45/938546d1629e008cc3138df7c424ef892719b1796ff408a2ab8550032e5e/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:cb1fb8c9337ab0da25a01c05d69a0463209c347f16512ac43be5986f3d1ebaf4", size = 2034028, upload-time = "2025-07-27T21:24:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/b5a53e20505555f1640e66dcf70394bcf51a1a3a072aa18ea35135a0f9ed/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:1f6449f6de52dde3e2f1038284910c8765a397a25e2d05083870f3f5e7fc682c", size = 2155513, upload-time = "2025-07-27T21:24:02.92Z" }, + { url = "https://files.pythonhosted.org/packages/84/12/8d3f6ceefae81bbe45a347fdfa2219d9f3ac75ebc304f92cd5fcb4fbddc5/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_i686.whl", hash = "sha256:382dec4f996be48ed9c6958d4e30c2b89435d7c2c4dbf32480b3b8886293dd65", size = 2170035, upload-time = "2025-07-27T21:24:04.558Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/3be6f0a1398f976070672be64f61895f8839857618a2d8cc0d3ab529d3dc/cramjam-2.11.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:d388bd5723732c3afe1dd1d181e4213cc4e1be210b080572e7d5749f6e955656", size = 2160229, upload-time = "2025-07-27T21:24:06.729Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/66cfc3635511b20014bbb3f2ecf0095efb3049e9e96a4a9e478e4f3d7b78/cramjam-2.11.0-cp314-cp314t-win32.whl", hash = "sha256:0a70ff17f8e1d13f322df616505550f0f4c39eda62290acb56f069d4857037c8", size = 1610267, upload-time = "2025-07-27T21:24:08.428Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c6/c71e82e041c95ffe6a92ac707785500aa2a515a4339c2c7dd67e3c449249/cramjam-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:028400d699442d40dbda02f74158c73d05cb76587a12490d0bfedd958fd49188", size = 1713108, upload-time = "2025-07-27T21:24:10.147Z" }, + { url = "https://files.pythonhosted.org/packages/81/da/b3301962ccd6fce9fefa1ecd8ea479edaeaa38fadb1f34d5391d2587216a/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:52d5db3369f95b27b9f3c14d067acb0b183333613363ed34268c9e04560f997f", size = 3573546, upload-time = "2025-07-27T21:24:52.944Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c2/410ddb8ad4b9dfb129284666293cb6559479645da560f7077dc19d6bee9e/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4820516366d455b549a44d0e2210ee7c4575882dda677564ce79092588321d54", size = 1873654, upload-time = "2025-07-27T21:24:54.958Z" }, + { url = "https://files.pythonhosted.org/packages/d5/99/f68a443c64f7ce7aff5bed369b0aa5b2fac668fa3dfd441837e316e97a1f/cramjam-2.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d9e5db525dc0a950a825202f84ee68d89a072479e07da98795a3469df942d301", size = 1702846, upload-time = "2025-07-27T21:24:57.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/02/0ff358ab773def1ee3383587906c453d289953171e9c92db84fdd01bf172/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62ab4971199b2270005359cdc379bc5736071dc7c9a228581c5122d9ffaac50c", size = 1773683, upload-time = "2025-07-27T21:24:59.28Z" }, + { url = "https://files.pythonhosted.org/packages/e9/31/3298e15f87c9cf2aabdbdd90b153d8644cf989cb42a45d68a1b71e1f7aaf/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24758375cc5414d3035ca967ebb800e8f24604ececcba3c67d6f0218201ebf2d", size = 1994136, upload-time = "2025-07-27T21:25:01.565Z" }, + { url = "https://files.pythonhosted.org/packages/c7/90/20d1747255f1ee69a412e319da51ea594c18cca195e7a4d4c713f045eff5/cramjam-2.11.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6c2eea545fef1065c7dd4eda991666fd9c783fbc1d226592ccca8d8891c02f23", size = 1714982, upload-time = "2025-07-27T21:25:05.79Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "django" +version = "5.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/96/bd84e2bb997994de8bcda47ae4560991084e86536541d7214393880f01a8/django-5.2.7.tar.gz", hash = "sha256:e0f6f12e2551b1716a95a63a1366ca91bbcd7be059862c1b18f989b1da356cdd", size = 10865812, upload-time = "2025-10-01T14:22:12.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/ef/81f3372b5dd35d8d354321155d1a38894b2b766f576d0abffac4d8ae78d9/django-5.2.7-py3-none-any.whl", hash = "sha256:59a13a6515f787dec9d97a0438cd2efac78c8aca1c80025244b0fe507fe0754b", size = 8307145, upload-time = "2025-10-01T14:22:49.476Z" }, +] + +[[package]] +name = "django-environ" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" }, +] + +[[package]] +name = "eta-prediction" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "celery" }, + { name = "django" }, + { name = "django-environ" }, + { name = "fastparquet" }, + { name = "gtfs-realtime-bindings" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "psycopg", extra = ["binary", "pool"] }, + { name = "python-dotenv" }, + { name = "redis" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "xgboost" }, +] + +[package.metadata] +requires-dist = [ + { name = "celery", specifier = ">=5.4" }, + { name = "django", specifier = ">=5.0" }, + { name = "django-environ", specifier = ">=0.11" }, + { name = "fastparquet", specifier = ">=2024.11.0" }, + { name = "gtfs-realtime-bindings", specifier = ">=1.0.0" }, + { name = "matplotlib", specifier = ">=3.9.4" }, + { name = "numpy", specifier = ">=1.26" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "psycopg", specifier = ">=3.2.11" }, + { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2" }, + { name = "python-dotenv", specifier = ">=1.0" }, + { name = "redis", specifier = ">=5.0" }, + { name = "requests", specifier = ">=2.32" }, + { name = "scikit-learn", specifier = ">=1.7.2" }, + { name = "xgboost", specifier = ">=3.1.2" }, +] + +[[package]] +name = "fastparquet" +version = "2024.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cramjam" }, + { name = "fsspec" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/66/862da14f5fde4eff2cedc0f51a8dc34ba145088e5041b45b2d57ac54f922/fastparquet-2024.11.0.tar.gz", hash = "sha256:e3b1fc73fd3e1b70b0de254bae7feb890436cb67e99458b88cb9bd3cc44db419", size = 467192, upload-time = "2024-11-15T19:30:10.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/51/e0d6e702523ac923ede6c05e240f4a02533ccf2cea9fec7a43491078e920/fastparquet-2024.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:374cdfa745aa7d5188430528d5841cf823eb9ad16df72ad6dadd898ccccce3be", size = 909934, upload-time = "2024-11-12T20:37:37.049Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c8/5c0fb644c19a8d80b2ae4d8aa7d90c2d85d0bd4a948c5c700bea5c2802ea/fastparquet-2024.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c8401bfd86cccaf0ab7c0ade58c91ae19317ff6092e1d4ad96c2178197d8124", size = 683844, upload-time = "2024-11-12T20:37:38.456Z" }, + { url = "https://files.pythonhosted.org/packages/33/4a/1e532fd1a0d4d8af7ffc7e3a8106c0bcd13ed914a93a61e299b3832dd3d2/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9cca4c6b5969df5561c13786f9d116300db1ec22c7941e237cfca4ce602f59b", size = 1791698, upload-time = "2024-11-12T20:37:41.101Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e8/e1ede861bea68394a755d8be1aa2e2d60a3b9f6b551bfd56aeca74987e2e/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a9387e77ac608d8978774caaf1e19de67eaa1386806e514dcb19f741b19cfe5", size = 1804289, upload-time = "2024-11-12T20:37:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1e/957090cccaede805583ca3f3e46e2762d0f9bf8860ecbce65197e47d84c1/fastparquet-2024.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6595d3771b3d587a31137e985f751b4d599d5c8e9af9c4858e373fdf5c3f8720", size = 1753638, upload-time = "2024-11-12T20:37:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/344787c685fd1531f07ae712a855a7c34d13deaa26c3fd4a9231bea7dbab/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:053695c2f730b78a2d3925df7cd5c6444d6c1560076af907993361cc7accf3e2", size = 1814407, upload-time = "2024-11-12T20:37:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ec/ab9d5685f776a1965797eb68c4364c72edf57cd35beed2df49b34425d1df/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a52eecc6270ae15f0d51347c3f762703dd667ca486f127dc0a21e7e59856ae5", size = 1874462, upload-time = "2024-11-12T20:37:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/90/4f/7a4ea9a7ddf0a3409873f0787f355806f9e0b73f42f2acecacdd9a8eff0a/fastparquet-2024.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:e29ff7a367fafa57c6896fb6abc84126e2466811aefd3e4ad4070b9e18820e54", size = 671023, upload-time = "2024-11-12T20:37:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/08/76/068ac7ec9b4fc783be21a75a6a90b8c0654da4d46934d969e524ce287787/fastparquet-2024.11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dbad4b014782bd38b58b8e9f514fe958cfa7a6c4e187859232d29fd5c5ddd849", size = 915968, upload-time = "2024-11-12T20:37:52.861Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9e/6d3b4188ad64ed51173263c07109a5f18f9c84a44fa39ab524fca7420cda/fastparquet-2024.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:403d31109d398b6be7ce84fa3483fc277c6a23f0b321348c0a505eb098a041cb", size = 685399, upload-time = "2024-11-12T20:37:54.899Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6c/809220bc9fbe83d107df2d664c3fb62fb81867be8f5218ac66c2e6b6a358/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbbb9057a26acf0abad7adf58781ee357258b7708ee44a289e3bee97e2f55d42", size = 1758557, upload-time = "2024-11-12T20:37:56.553Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2c/b3b3e6ca2e531484289024138cd4709c22512b3fe68066d7f9849da4a76c/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e0e416e25c15daa174aad8ba991c2e9e5b0dc347e5aed5562124261400f87b", size = 1781052, upload-time = "2024-11-12T20:37:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/21/fe/97ed45092d0311c013996dae633122b7a51c5d9fe8dcbc2c840dc491201e/fastparquet-2024.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2d7f02f57231e6c86d26e9ea71953737202f20e948790e5d4db6d6a1a150dc", size = 1715797, upload-time = "2024-11-12T20:38:00.694Z" }, + { url = "https://files.pythonhosted.org/packages/24/df/02fa6aee6c0d53d1563b5bc22097076c609c4c5baa47056b0b4bed456fcf/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbe4468146b633d8f09d7b196fea0547f213cb5ce5f76e9d1beb29eaa9593a93", size = 1795682, upload-time = "2024-11-12T20:38:02.38Z" }, + { url = "https://files.pythonhosted.org/packages/b0/25/f4f87557589e1923ee0e3bebbc84f08b7c56962bf90f51b116ddc54f2c9f/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29d5c718817bcd765fc519b17f759cad4945974421ecc1931d3bdc3e05e57fa9", size = 1857842, upload-time = "2024-11-12T20:38:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/98cd0c39115879be1044d59c9b76e8292776e99bb93565bf990078fd11c4/fastparquet-2024.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:74a0b3c40ab373442c0fda96b75a36e88745d8b138fcc3a6143e04682cbbb8ca", size = 673269, upload-time = "2024-12-11T21:22:48.073Z" }, + { url = "https://files.pythonhosted.org/packages/47/e3/e7db38704be5db787270d43dde895eaa1a825ab25dc245e71df70860ec12/fastparquet-2024.11.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59e5c5b51083d5b82572cdb7aed0346e3181e3ac9d2e45759da2e804bdafa7ee", size = 912523, upload-time = "2024-11-12T20:38:06.003Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/e3387c99293dae441634e7724acaa425b27de19a00ee3d546775dace54a9/fastparquet-2024.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdadf7b6bad789125b823bfc5b0a719ba5c4a2ef965f973702d3ea89cff057f6", size = 683779, upload-time = "2024-11-12T20:38:07.442Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/d112d0573d086b578bf04302a502e9a7605ea8f1244a7b8577cd945eec78/fastparquet-2024.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46b2db02fc2a1507939d35441c8ab211d53afd75d82eec9767d1c3656402859b", size = 1751113, upload-time = "2024-11-12T20:38:09.36Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a7/040507cee3a7798954e8fdbca21d2dbc532774b02b882d902b8a4a6849ef/fastparquet-2024.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3afdef2895c9f459135a00a7ed3ceafebfbce918a9e7b5d550e4fae39c1b64d", size = 1780496, upload-time = "2024-11-12T20:38:11.022Z" }, + { url = "https://files.pythonhosted.org/packages/bc/75/d0d9f7533d780ec167eede16ad88073ee71696150511126c31940e7f73aa/fastparquet-2024.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36b5c9bd2ffaaa26ff45d59a6cefe58503dd748e0c7fad80dd905749da0f2b9e", size = 1713608, upload-time = "2024-11-12T20:38:12.848Z" }, + { url = "https://files.pythonhosted.org/packages/30/fa/1d95bc86e45e80669c4f374b2ca26a9e5895a1011bb05d6341b4a7414693/fastparquet-2024.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b7df5d3b61a19d76e209fe8d3133759af1c139e04ebc6d43f3cc2d8045ef338", size = 1792779, upload-time = "2024-11-12T20:38:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/13/3d/c076beeb926c79593374c04662a9422a76650eef17cd1c8e10951340764a/fastparquet-2024.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b35823ac7a194134e5f82fa4a9659e42e8f9ad1f2d22a55fbb7b9e4053aabbb", size = 1851322, upload-time = "2024-11-12T20:38:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/1d0d47e64816002824d4a876644e8c65540fa23f91b701f0daa726931545/fastparquet-2024.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:d20632964e65530374ff7cddd42cc06aa0a1388934903693d6d22592a5ba827b", size = 673266, upload-time = "2024-11-12T20:38:17.661Z" }, +] + +[[package]] +name = "feature-engineering" +version = "0.1.0" +source = { editable = "feature_engineering" } +dependencies = [ + { name = "geopandas" }, + { name = "joblib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyproj" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "shapely" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=24.3,<25" }, + { name = "geopandas", specifier = ">=0.14,<1" }, + { name = "isort", marker = "extra == 'dev'", specifier = ">=5.13,<6" }, + { name = "joblib", specifier = ">=1.3,<2" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.11,<2" }, + { name = "numpy", specifier = ">=1.26,<2" }, + { name = "pandas", specifier = ">=2.2,<3" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.4,<4" }, + { name = "pyproj", specifier = ">=3.6,<4" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4,<8" }, + { name = "scikit-learn", specifier = ">=1.3,<2" }, + { name = "scipy", specifier = ">=1.11,<2" }, + { name = "shapely", specifier = ">=2.1,<3" }, + { name = "tqdm", specifier = ">=4.66,<5" }, +] +provides-extras = ["dev"] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "fiona" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "certifi" }, + { name = "click" }, + { name = "click-plugins" }, + { name = "cligj" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/e0/71b63839cc609e1d62cea2fc9774aa605ece7ea78af823ff7a8f1c560e72/fiona-1.10.1.tar.gz", hash = "sha256:b00ae357669460c6491caba29c2022ff0acfcbde86a95361ea8ff5cd14a86b68", size = 444606, upload-time = "2024-09-16T20:15:47.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/b9/7a8356cfaff8ef162bad44283554d3171e13032635b4f8e10e694a9596ee/fiona-1.10.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:98fe556058b370da07a84f6537c286f87eb4af2343d155fbd3fba5d38ac17ed7", size = 16144293, upload-time = "2024-09-16T20:14:34.519Z" }, + { url = "https://files.pythonhosted.org/packages/65/0c/e8070b15c8303f60bd4444a120842597ccd6ed550548948e2e36cffbaa93/fiona-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:be29044d4aeebae92944b738160dc5f9afc4cdf04f551d59e803c5b910e17520", size = 14752213, upload-time = "2024-09-16T20:14:37.763Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/3f80ba2fda9b8686681f0a1b18c8e95ad152ada1d6fb1d3f25281d9229fd/fiona-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94bd3d448f09f85439e4b77c38b9de1aebe3eef24acc72bd631f75171cdfde51", size = 17272183, upload-time = "2024-09-16T20:14:42.389Z" }, + { url = "https://files.pythonhosted.org/packages/95/32/c1d53b4d77926414ffdf5bd38344e900e378ae9ccb2a65754cdb6d5344c2/fiona-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:30594c0cd8682c43fd01e7cdbe000f94540f8fa3b7cb5901e805c88c4ff2058b", size = 24489398, upload-time = "2024-09-16T20:14:46.233Z" }, + { url = "https://files.pythonhosted.org/packages/73/ab/036c418d531afb74abe4ca9a8be487b863901fe7b42ddba1ba2fb0681d77/fiona-1.10.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7338b8c68beb7934bde4ec9f49eb5044e5e484b92d940bc3ec27defdb2b06c67", size = 16114589, upload-time = "2024-09-16T20:14:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/ba/45/693c1cca53023aaf6e3adc11422080f5fa427484e7b85e48f19c40d6357f/fiona-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c77fcfd3cdb0d3c97237965f8c60d1696a64923deeeb2d0b9810286cbe25911", size = 14754603, upload-time = "2024-09-16T20:14:53.829Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/be204fb409b59876ef4658710a022794f16f779a3e9e7df654acc38b2104/fiona-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537872cbc9bda7fcdf73851c91bc5338fca2b502c4c17049ccecaa13cde1f18f", size = 17223639, upload-time = "2024-09-16T20:14:57.146Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/914fd3c4c32043c2c512fa5021e83b2348e1b7a79365d75a0a37cb545362/fiona-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:41cde2c52c614457e9094ea44b0d30483540789e62fe0fa758c2a2963e980817", size = 24464921, upload-time = "2024-09-16T20:15:01.121Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/665ce969cab6339c19527318534236e5e4184ee03b38cd474497ebd22f4d/fiona-1.10.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:a00b05935c9900678b2ca660026b39efc4e4b916983915d595964eb381763ae7", size = 16106571, upload-time = "2024-09-16T20:15:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/23/c8/150094fbc4220d22217f480cc67b6ee4c2f4324b4b58cd25527cd5905937/fiona-1.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f78b781d5bcbbeeddf1d52712f33458775dbb9fd1b2a39882c83618348dd730f", size = 14738178, upload-time = "2024-09-16T20:15:06.848Z" }, + { url = "https://files.pythonhosted.org/packages/20/83/63da54032c0c03d4921b854111e33d3a1dadec5d2b7e741fba6c8c6486a6/fiona-1.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ceeb38e3cd30d91d68858d0817a1bb0c4f96340d334db4b16a99edb0902d35", size = 17221414, upload-time = "2024-09-16T20:15:09.606Z" }, + { url = "https://files.pythonhosted.org/packages/60/14/5ef47002ef19bd5cfbc7a74b21c30ef83f22beb80609314ce0328989ceda/fiona-1.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:15751c90e29cee1e01fcfedf42ab85987e32f0b593cf98d88ed52199ef5ca623", size = 24461486, upload-time = "2024-09-16T20:15:13.399Z" }, +] + +[[package]] +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, + { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, + { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, + { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, + { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, + { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, + { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, + { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, + { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, + { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "geopandas" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fiona" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyproj" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/79/79af2645d40d590a466f8329ab04c2d4fffc811e6713d1c1580dcfdf285c/geopandas-0.14.4.tar.gz", hash = "sha256:56765be9d58e2c743078085db3bd07dc6be7719f0dbe1dfdc1d705cb80be7c25", size = 1106304, upload-time = "2024-04-28T13:49:27.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/b0/69fa7a0f55122847506a42fea6988d03b34136938082f142151bc9d9f7e7/geopandas-0.14.4-py3-none-any.whl", hash = "sha256:3bb6473cb59d51e1a7fe2dbc24a1a063fb0ebdeddf3ce08ddbf8c7ddc99689aa", size = 1109913, upload-time = "2024-04-28T13:49:24.25Z" }, +] + +[[package]] +name = "gtfs-realtime-bindings" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/55/ed46db9267d2615851bfba3c22b6c0e7f88751efb5d5d1468291935c7f65/gtfs-realtime-bindings-1.0.0.tar.gz", hash = "sha256:2e8ced8904400cc93ab7e8520adb6934cfa601edacc6f593fc2cb4448662bb47", size = 6197, upload-time = "2023-02-23T17:53:20.8Z" } + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload-time = "2023-12-13T20:37:26.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507, upload-time = "2025-10-09T00:26:19.073Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565, upload-time = "2025-10-09T00:26:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668, upload-time = "2025-10-09T00:26:22.967Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051, upload-time = "2025-10-09T00:26:25.041Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878, upload-time = "2025-10-09T00:26:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142, upload-time = "2025-10-09T00:26:29.774Z" }, + { url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439, upload-time = "2025-10-09T00:26:40.32Z" }, + { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389, upload-time = "2025-10-09T00:26:42.474Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247, upload-time = "2025-10-09T00:26:44.77Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996, upload-time = "2025-10-09T00:26:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153, upload-time = "2025-10-09T00:26:49.07Z" }, + { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093, upload-time = "2025-10-09T00:26:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771, upload-time = "2025-10-09T00:26:53.296Z" }, + { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812, upload-time = "2025-10-09T00:26:54.882Z" }, + { url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212, upload-time = "2025-10-09T00:26:56.752Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713, upload-time = "2025-10-09T00:26:59.001Z" }, + { url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527, upload-time = "2025-10-09T00:27:00.69Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690, upload-time = "2025-10-09T00:27:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732, upload-time = "2025-10-09T00:27:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727, upload-time = "2025-10-09T00:27:06.814Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958, upload-time = "2025-10-09T00:27:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849, upload-time = "2025-10-09T00:27:10.254Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225, upload-time = "2025-10-09T00:27:12.241Z" }, + { url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708, upload-time = "2025-10-09T00:27:13.879Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409, upload-time = "2025-10-09T00:27:15.684Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054, upload-time = "2025-10-09T00:27:17.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100, upload-time = "2025-10-09T00:27:20.039Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131, upload-time = "2025-10-09T00:27:21.608Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787, upload-time = "2025-10-09T00:27:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348, upload-time = "2025-10-09T00:27:24.926Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949, upload-time = "2025-10-09T00:27:26.704Z" }, + { url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247, upload-time = "2025-10-09T00:27:28.514Z" }, + { url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497, upload-time = "2025-10-09T00:27:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732, upload-time = "2025-10-09T00:27:32.332Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240, upload-time = "2025-10-09T00:27:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938, upload-time = "2025-10-09T00:27:35.534Z" }, + { url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245, upload-time = "2025-10-09T00:27:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411, upload-time = "2025-10-09T00:27:39.387Z" }, + { url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664, upload-time = "2025-10-09T00:27:41.492Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" }, + { url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" }, + { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283, upload-time = "2025-10-09T00:27:54.739Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733, upload-time = "2025-10-09T00:27:56.406Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815, upload-time = "2024-07-28T19:59:01.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643, upload-time = "2024-07-28T19:58:59.335Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/77/c72d10262b872617e509a0c60445afcc4ce2cd5cd6bc1c97700246d69c85/psycopg-3.2.12.tar.gz", hash = "sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b", size = 160642, upload-time = "2025-10-26T00:46:03.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/28/8c4f90e415411dc9c78d6ba10b549baa324659907c13f64bfe3779d4066c/psycopg-3.2.12-py3-none-any.whl", hash = "sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee", size = 206765, upload-time = "2025-10-26T00:10:42.173Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] +pool = [ + { name = "psycopg-pool" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.12" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/4d/980fdd0f75914c8b1f229a6e5a9c422b53e809166b96a7d0e1287b369796/psycopg_binary-3.2.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90", size = 4037686, upload-time = "2025-10-26T00:16:22.041Z" }, + { url = "https://files.pythonhosted.org/packages/51/76/6b6ccd3fd31c67bec8608225407322f26a2a633c05b35c56b7c0638dcc67/psycopg_binary-3.2.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3", size = 4098526, upload-time = "2025-10-26T00:16:58.395Z" }, + { url = "https://files.pythonhosted.org/packages/91/d8/be5242efed4f57f74a27eb47cb3a01bebb04e43ca57e903fcbda23361e72/psycopg_binary-3.2.12-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893", size = 4646680, upload-time = "2025-10-26T00:17:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/20/c1/96e42d39c0e75c4059f80e8fc9b286e2b6d9652f30b42698101d4be201cf/psycopg_binary-3.2.12-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4", size = 4749345, upload-time = "2025-10-26T00:18:16.61Z" }, + { url = "https://files.pythonhosted.org/packages/78/00/0ee41e18bdb05b43a27ebf8a952343319554cd9bde7931f633343b5abbad/psycopg_binary-3.2.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b", size = 4432535, upload-time = "2025-10-26T00:18:53.823Z" }, + { url = "https://files.pythonhosted.org/packages/c5/77/580cc455ba909d9e3082b80bb1952f67c5b9692a56ecaf71816ce0e9aa69/psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9", size = 3888888, upload-time = "2025-10-26T00:19:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/cb/29/0d0d2aa4238fd57ddbd2f517c58cefb26d408d3e989cbca9ad43f4c48433/psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219", size = 3571385, upload-time = "2025-10-26T00:19:56.844Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7d/eb11cd86339122c19c1039cb5ee5f87f88d6015dff564b1ed23d0c4a90e7/psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45", size = 3614219, upload-time = "2025-10-26T00:20:27.135Z" }, + { url = "https://files.pythonhosted.org/packages/65/02/dff51dc1f88d9854405013e2cabbf7060c2b3993cb82d6e8ad21396081af/psycopg_binary-3.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61", size = 2919778, upload-time = "2025-10-26T00:20:51.974Z" }, + { url = "https://files.pythonhosted.org/packages/db/4a/b2779f74fdb0d661febe802fb3b770546a99f0a513ef108e8f9ed36b87cb/psycopg_binary-3.2.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8", size = 4019926, upload-time = "2025-10-26T00:21:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/d5/af/df6c2beb44de456c4f025a61dfe611cf5b3eb3d3fa671144ce19ac7f1139/psycopg_binary-3.2.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823", size = 4092107, upload-time = "2025-10-26T00:22:00.043Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3b/b16260c93a0a435000fd175f1abb8d12af5542bd9d35d17dd2b7f347dbd5/psycopg_binary-3.2.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1", size = 4626849, upload-time = "2025-10-26T00:22:38.606Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/2c8d1c534777176e3e4832105f0b2f70c0ff3d63def0f1fda46833cc2dc1/psycopg_binary-3.2.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd", size = 4719811, upload-time = "2025-10-26T00:23:18.23Z" }, + { url = "https://files.pythonhosted.org/packages/34/44/005ab6a42698489310f52f287b78c26560aeedb091ba12f034acdff4549b/psycopg_binary-3.2.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a", size = 4410950, upload-time = "2025-10-26T00:23:55.532Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ba/c59303ed65659cd62da2b3f4ad2b8635ae10eb85e7645d063025277c953d/psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de", size = 3861578, upload-time = "2025-10-26T00:24:28.482Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/d36f03b11959978b2c2522c87369fa8d75c1fa9b311805b39ce7678455ae/psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f", size = 3534948, upload-time = "2025-10-26T00:24:58.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/e0e5fc0d5f2d2650f85540cebd0d047e14b0933b99f713749b2ebc031047/psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6", size = 3583525, upload-time = "2025-10-26T00:25:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/13/27/e2b1afb9819835f85f1575f07fdfc871dd8b4ea7ed8244bfe86a2f6d6566/psycopg_binary-3.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd", size = 2910254, upload-time = "2025-10-26T00:25:53.335Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0b/9d480aba4a4864832c29e6fc94ddd34d9927c276448eb3b56ffe24ed064c/psycopg_binary-3.2.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b", size = 4017829, upload-time = "2025-10-26T00:26:27.031Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f3/0d294b30349bde24a46741a1f27a10e8ab81e9f4118d27c2fe592acfb42a/psycopg_binary-3.2.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5", size = 4089835, upload-time = "2025-10-26T00:27:01.392Z" }, + { url = "https://files.pythonhosted.org/packages/82/d4/ff82e318e5a55d6951b278d3af7b4c7c1b19344e3a3722b6613f156a38ea/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d", size = 4625474, upload-time = "2025-10-26T00:27:40.34Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/2c9df6475a5ab6d614d516f4497c568d84f7d6c21d0e11444468c9786c9f/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3", size = 4720350, upload-time = "2025-10-26T00:28:20.104Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/7aec81b0c41985dc006e2d5822486ad4b7c2a1a97a5a05e37dc2adaf1512/psycopg_binary-3.2.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3", size = 4411621, upload-time = "2025-10-26T00:28:59.104Z" }, + { url = "https://files.pythonhosted.org/packages/fc/15/d3cb41b8fa9d5f14320ab250545fbb66f9ddb481e448e618902672a806c0/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675", size = 3863081, upload-time = "2025-10-26T00:29:31.235Z" }, + { url = "https://files.pythonhosted.org/packages/69/8a/72837664e63e3cd3aa145cedcf29e5c21257579739aba78ab7eb668f7d9c/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a", size = 3537428, upload-time = "2025-10-26T00:30:01.465Z" }, + { url = "https://files.pythonhosted.org/packages/cc/7e/1b78ae38e7d69e6d7fb1e2dcce101493f5fa429480bac3a68b876c9b1635/psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e", size = 3585981, upload-time = "2025-10-26T00:30:31.635Z" }, + { url = "https://files.pythonhosted.org/packages/a3/f8/245b4868b2dac46c3fb6383b425754ae55df1910c826d305ed414da03777/psycopg_binary-3.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc", size = 2912929, upload-time = "2025-10-26T00:30:56.413Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5b/76fbb40b981b73b285a00dccafc38cf67b7a9b3f7d4f2025dda7b896e7ef/psycopg_binary-3.2.12-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef", size = 4016868, upload-time = "2025-10-26T00:31:29.974Z" }, + { url = "https://files.pythonhosted.org/packages/0e/08/8841ae3e2d1a3228e79eaaf5b7f991d15f0a231bb5031a114305b19724b1/psycopg_binary-3.2.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441", size = 4090508, upload-time = "2025-10-26T00:32:04.192Z" }, + { url = "https://files.pythonhosted.org/packages/05/de/a41f62230cf4095ae4547eceada218cf28c17e7f94376913c1c8dde9546f/psycopg_binary-3.2.12-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a", size = 4629788, upload-time = "2025-10-26T00:32:43.28Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/529d92134eae44475f781a86d58cdf3edd0953e17c69762abf387a9f2636/psycopg_binary-3.2.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f", size = 4724124, upload-time = "2025-10-26T00:33:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f5/97344e87065f7c9713ce213a2cff7732936ec3af6622e4b2a88715a953f2/psycopg_binary-3.2.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e", size = 4411340, upload-time = "2025-10-26T00:34:00.759Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c2/34bce068f6bfb4c2e7bb1187bb64a3f3be254702b158c4ad05eacc0055cf/psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084", size = 3867815, upload-time = "2025-10-26T00:34:33.181Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a1/c647e01ab162e6bfa52380e23e486215e9d28ffd31e9cf3cb1e9ca59008b/psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7", size = 3541756, upload-time = "2025-10-26T00:35:08.622Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d0/795bdaa8c946a7b7126bf7ca8d4371eaaa613093e3ec341a0e50f52cbee2/psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e", size = 3587950, upload-time = "2025-10-26T00:35:41.183Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/10c3e95827a3ca8af332dfc471befec86e15a14dc83cee893c49a4910dad/psycopg_binary-3.2.12-cp314-cp314-win_amd64.whl", hash = "sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39", size = 3005787, upload-time = "2025-10-26T00:36:06.783Z" }, +] + +[[package]] +name = "psycopg-pool" +version = "3.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/8f/3ec52b17087c2ed5fa32b64fd4814dde964c9aa4bd49d0d30fc24725ca6d/psycopg_pool-3.2.7.tar.gz", hash = "sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b", size = 29765, upload-time = "2025-10-26T00:46:10.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/59/74e752f605c6f0e351d4cf1c54fb9a1616dc800db4572b95bbfbb1a6225f/psycopg_pool-3.2.7-py3-none-any.whl", hash = "sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f", size = 38232, upload-time = "2025-10-26T00:46:00.496Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "pyproj" +version = "3.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz", hash = "sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c", size = 226279, upload-time = "2025-08-14T12:05:42.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/bd/f205552cd1713b08f93b09e39a3ec99edef0b3ebbbca67b486fdf1abe2de/pyproj-3.7.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2514d61f24c4e0bb9913e2c51487ecdaeca5f8748d8313c933693416ca41d4d5", size = 6227022, upload-time = "2025-08-14T12:03:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/75/4c/9a937e659b8b418ab573c6d340d27e68716928953273e0837e7922fcac34/pyproj-3.7.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8693ca3892d82e70de077701ee76dd13d7bca4ae1c9d1e739d72004df015923a", size = 4625810, upload-time = "2025-08-14T12:03:53.808Z" }, + { url = "https://files.pythonhosted.org/packages/c0/7d/a9f41e814dc4d1dc54e95b2ccaf0b3ebe3eb18b1740df05fe334724c3d89/pyproj-3.7.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5e26484d80fea56273ed1555abaea161e9661d81a6c07815d54b8e883d4ceb25", size = 9638694, upload-time = "2025-08-14T12:03:55.669Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ab/9bdb4a6216b712a1f9aab1c0fcbee5d3726f34a366f29c3e8c08a78d6b70/pyproj-3.7.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:281cb92847814e8018010c48b4069ff858a30236638631c1a91dd7bfa68f8a8a", size = 9493977, upload-time = "2025-08-14T12:03:57.937Z" }, + { url = "https://files.pythonhosted.org/packages/c9/db/2db75b1b6190f1137b1c4e8ef6a22e1c338e46320f6329bfac819143e063/pyproj-3.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9c8577f0b7bb09118ec2e57e3babdc977127dd66326d6c5d755c76b063e6d9dc", size = 10841151, upload-time = "2025-08-14T12:04:00.271Z" }, + { url = "https://files.pythonhosted.org/packages/89/f7/989643394ba23a286e9b7b3f09981496172f9e0d4512457ffea7dc47ffc7/pyproj-3.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a23f59904fac3a5e7364b3aa44d288234af267ca041adb2c2b14a903cd5d3ac5", size = 10751585, upload-time = "2025-08-14T12:04:02.228Z" }, + { url = "https://files.pythonhosted.org/packages/53/6d/ad928fe975a6c14a093c92e6a319ca18f479f3336bb353a740bdba335681/pyproj-3.7.2-cp311-cp311-win32.whl", hash = "sha256:f2af4ed34b2cf3e031a2d85b067a3ecbd38df073c567e04b52fa7a0202afde8a", size = 5908533, upload-time = "2025-08-14T12:04:04.821Z" }, + { url = "https://files.pythonhosted.org/packages/79/e0/b95584605cec9ed50b7ebaf7975d1c4ddeec5a86b7a20554ed8b60042bd7/pyproj-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b7cb633565129677b2a183c4d807c727d1c736fcb0568a12299383056e67433", size = 6320742, upload-time = "2025-08-14T12:04:06.357Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4d/536e8f93bca808175c2d0a5ac9fdf69b960d8ab6b14f25030dccb07464d7/pyproj-3.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:38b08d85e3a38e455625b80e9eb9f78027c8e2649a21dec4df1f9c3525460c71", size = 6245772, upload-time = "2025-08-14T12:04:08.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ab/9893ea9fb066be70ed9074ae543914a618c131ed8dff2da1e08b3a4df4db/pyproj-3.7.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0a9bb26a6356fb5b033433a6d1b4542158fb71e3c51de49b4c318a1dff3aeaab", size = 6219832, upload-time = "2025-08-14T12:04:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/53/78/4c64199146eed7184eb0e85bedec60a4aa8853b6ffe1ab1f3a8b962e70a0/pyproj-3.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:567caa03021178861fad27fabde87500ec6d2ee173dd32f3e2d9871e40eebd68", size = 4620650, upload-time = "2025-08-14T12:04:11.978Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ac/14a78d17943898a93ef4f8c6a9d4169911c994e3161e54a7cedeba9d8dde/pyproj-3.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c203101d1dc3c038a56cff0447acc515dd29d6e14811406ac539c21eed422b2a", size = 9667087, upload-time = "2025-08-14T12:04:13.964Z" }, + { url = "https://files.pythonhosted.org/packages/b8/be/212882c450bba74fc8d7d35cbd57e4af84792f0a56194819d98106b075af/pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1edc34266c0c23ced85f95a1ee8b47c9035eae6aca5b6b340327250e8e281630", size = 9552797, upload-time = "2025-08-14T12:04:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c0/c0f25c87b5d2a8686341c53c1792a222a480d6c9caf60311fec12c99ec26/pyproj-3.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa9f26c21bc0e2dc3d224cb1eb4020cf23e76af179a7c66fea49b828611e4260", size = 10837036, upload-time = "2025-08-14T12:04:18.733Z" }, + { url = "https://files.pythonhosted.org/packages/5d/37/5cbd6772addde2090c91113332623a86e8c7d583eccb2ad02ea634c4a89f/pyproj-3.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9428b318530625cb389b9ddc9c51251e172808a4af79b82809376daaeabe5e9", size = 10775952, upload-time = "2025-08-14T12:04:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/69/a1/dc250e3cf83eb4b3b9a2cf86fdb5e25288bd40037ae449695550f9e96b2f/pyproj-3.7.2-cp312-cp312-win32.whl", hash = "sha256:b3d99ed57d319da042f175f4554fc7038aa4bcecc4ac89e217e350346b742c9d", size = 5898872, upload-time = "2025-08-14T12:04:22.485Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a6/6fe724b72b70f2b00152d77282e14964d60ab092ec225e67c196c9b463e5/pyproj-3.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:11614a054cd86a2ed968a657d00987a86eeb91fdcbd9ad3310478685dc14a128", size = 6312176, upload-time = "2025-08-14T12:04:24.736Z" }, + { url = "https://files.pythonhosted.org/packages/5d/68/915cc32c02a91e76d02c8f55d5a138d6ef9e47a0d96d259df98f4842e558/pyproj-3.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:509a146d1398bafe4f53273398c3bb0b4732535065fa995270e52a9d3676bca3", size = 6233452, upload-time = "2025-08-14T12:04:27.287Z" }, + { url = "https://files.pythonhosted.org/packages/be/14/faf1b90d267cea68d7e70662e7f88cefdb1bc890bd596c74b959e0517a72/pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1", size = 6214580, upload-time = "2025-08-14T12:04:28.804Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/da9a45b184d375f62667f62eba0ca68569b0bd980a0bb7ffcc1d50440520/pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7", size = 4615388, upload-time = "2025-08-14T12:04:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e7/d2b459a4a64bca328b712c1b544e109df88e5c800f7c143cfbc404d39bfb/pyproj-3.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa", size = 9628455, upload-time = "2025-08-14T12:04:32.435Z" }, + { url = "https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681", size = 9514269, upload-time = "2025-08-14T12:04:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/34/38/07a9b89ae7467872f9a476883a5bad9e4f4d1219d31060f0f2b282276cbe/pyproj-3.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5", size = 10808437, upload-time = "2025-08-14T12:04:36.485Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/fda1daeabbd39dec5b07f67233d09f31facb762587b498e6fc4572be9837/pyproj-3.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67", size = 10745540, upload-time = "2025-08-14T12:04:38.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/90/c793182cbba65a39a11db2ac6b479fe76c59e6509ae75e5744c344a0da9d/pyproj-3.7.2-cp313-cp313-win32.whl", hash = "sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3", size = 5896506, upload-time = "2025-08-14T12:04:41.059Z" }, + { url = "https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7", size = 6310195, upload-time = "2025-08-14T12:04:42.974Z" }, + { url = "https://files.pythonhosted.org/packages/82/64/fc7598a53172c4931ec6edf5228280663063150625d3f6423b4c20f9daff/pyproj-3.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100", size = 6230748, upload-time = "2025-08-14T12:04:44.491Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f0/611dd5cddb0d277f94b7af12981f56e1441bf8d22695065d4f0df5218498/pyproj-3.7.2-cp313-cp313t-macosx_13_0_x86_64.whl", hash = "sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279", size = 6241729, upload-time = "2025-08-14T12:04:46.274Z" }, + { url = "https://files.pythonhosted.org/packages/15/93/40bd4a6c523ff9965e480870611aed7eda5aa2c6128c6537345a2b77b542/pyproj-3.7.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6", size = 4652497, upload-time = "2025-08-14T12:04:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ae/7150ead53c117880b35e0d37960d3138fe640a235feb9605cb9386f50bb0/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220", size = 9942610, upload-time = "2025-08-14T12:04:49.652Z" }, + { url = "https://files.pythonhosted.org/packages/d8/17/7a4a7eafecf2b46ab64e5c08176c20ceb5844b503eaa551bf12ccac77322/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c", size = 9692390, upload-time = "2025-08-14T12:04:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/c3/55/ae18f040f6410f0ea547a21ada7ef3e26e6c82befa125b303b02759c0e9d/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c", size = 11047596, upload-time = "2025-08-14T12:04:53.748Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2e/d3fff4d2909473f26ae799f9dda04caa322c417a51ff3b25763f7d03b233/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69", size = 10896975, upload-time = "2025-08-14T12:04:55.875Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bc/8fc7d3963d87057b7b51ebe68c1e7c51c23129eee5072ba6b86558544a46/pyproj-3.7.2-cp313-cp313t-win32.whl", hash = "sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2", size = 5953057, upload-time = "2025-08-14T12:04:58.466Z" }, + { url = "https://files.pythonhosted.org/packages/cc/27/ea9809966cc47d2d51e6d5ae631ea895f7c7c7b9b3c29718f900a8f7d197/pyproj-3.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3", size = 6375414, upload-time = "2025-08-14T12:04:59.861Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/1ef0129fba9a555c658e22af68989f35e7ba7b9136f25758809efec0cd6e/pyproj-3.7.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd", size = 6262501, upload-time = "2025-08-14T12:05:01.39Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02", size = 6214541, upload-time = "2025-08-14T12:05:03.166Z" }, + { url = "https://files.pythonhosted.org/packages/03/68/68ada9c8aea96ded09a66cfd9bf87aa6db8c2edebe93f5bf9b66b0143fbc/pyproj-3.7.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08", size = 4617456, upload-time = "2025-08-14T12:05:04.563Z" }, + { url = "https://files.pythonhosted.org/packages/81/e4/4c50ceca7d0e937977866b02cb64e6ccf4df979a5871e521f9e255df6073/pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b", size = 9615590, upload-time = "2025-08-14T12:05:06.094Z" }, + { url = "https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281", size = 9474960, upload-time = "2025-08-14T12:05:07.973Z" }, + { url = "https://files.pythonhosted.org/packages/51/07/9d48ad0a8db36e16f842f2c8a694c1d9d7dcf9137264846bef77585a71f3/pyproj-3.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516", size = 10799478, upload-time = "2025-08-14T12:05:14.102Z" }, + { url = "https://files.pythonhosted.org/packages/85/cf/2f812b529079f72f51ff2d6456b7fef06c01735e5cfd62d54ffb2b548028/pyproj-3.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e", size = 10710030, upload-time = "2025-08-14T12:05:16.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/9b/4626a19e1f03eba4c0e77b91a6cf0f73aa9cb5d51a22ee385c22812bcc2c/pyproj-3.7.2-cp314-cp314-win32.whl", hash = "sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25", size = 5991181, upload-time = "2025-08-14T12:05:19.492Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112", size = 6434721, upload-time = "2025-08-14T12:05:21.022Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ce/6c910ea2e1c74ef673c5d48c482564b8a7824a44c4e35cca2e765b68cfcc/pyproj-3.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6", size = 6363821, upload-time = "2025-08-14T12:05:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/5532f6f7491812ba782a2177fe9de73fd8e2912b59f46a1d056b84b9b8f2/pyproj-3.7.2-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37", size = 6241773, upload-time = "2025-08-14T12:05:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/0938c3f2bbbef1789132d1726d9b0e662f10cfc22522743937f421ad664e/pyproj-3.7.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b", size = 4652537, upload-time = "2025-08-14T12:05:26.391Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/488b1ed47d25972f33874f91f09ca8f2227902f05f63a2b80dc73e7b1c97/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357", size = 9940864, upload-time = "2025-08-14T12:05:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cc/7f4c895d0cb98e47b6a85a6d79eaca03eb266129eed2f845125c09cf31ff/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81", size = 9688868, upload-time = "2025-08-14T12:05:30.425Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/c7e306b8bb0f071d9825b753ee4920f066c40fbfcce9372c4f3cfb2fc4ed/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888", size = 11045910, upload-time = "2025-08-14T12:05:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/42/fb/538a4d2df695980e2dde5c04d965fbdd1fe8c20a3194dc4aaa3952a4d1be/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59", size = 10895724, upload-time = "2025-08-14T12:05:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8b/a3f0618b03957de9db5489a04558a8826f43906628bb0b766033aa3b5548/pyproj-3.7.2-cp314-cp314t-win32.whl", hash = "sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa", size = 6056848, upload-time = "2025-08-14T12:05:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/bc/56/413240dd5149dd3291eda55aa55a659da4431244a2fd1319d0ae89407cfb/pyproj-3.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c", size = 6517676, upload-time = "2025-08-14T12:05:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/15/73/a7141a1a0559bf1a7aa42a11c879ceb19f02f5c6c371c6d57fd86cefd4d1/pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4", size = 6391844, upload-time = "2025-08-14T12:05:40.745Z" }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { 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 = "redis" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/8f/f125feec0b958e8d22c8f0b492b30b1991d9499a4315dfde466cf4289edc/redis-7.0.1.tar.gz", hash = "sha256:c949df947dca995dc68fdf5a7863950bf6df24f8d6022394585acc98e81624f1", size = 4755322, upload-time = "2025-10-27T14:34:00.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/97/9f22a33c475cda519f20aba6babb340fb2f2254a02fb947816960d1e669a/redis-7.0.1-py3-none-any.whl", hash = "sha256:4977af3c7d67f8f0eb8b6fec0dafc9605db9343142f634041fb0235f67c0588a", size = 339938, upload-time = "2025-10-27T14:33:58.553Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92", size = 36619956, upload-time = "2025-09-11T17:39:20.5Z" }, + { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e", size = 28931117, upload-time = "2025-09-11T17:39:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173", size = 20921997, upload-time = "2025-09-11T17:39:34.892Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d", size = 23523374, upload-time = "2025-09-11T17:39:40.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2", size = 33583702, upload-time = "2025-09-11T17:39:49.011Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9", size = 35883427, upload-time = "2025-09-11T17:39:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3", size = 36212940, upload-time = "2025-09-11T17:40:06.013Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88", size = 38865092, upload-time = "2025-09-11T17:40:15.143Z" }, + { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa", size = 38687626, upload-time = "2025-09-11T17:40:24.041Z" }, + { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c", size = 25503506, upload-time = "2025-09-11T17:40:30.703Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, + { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, + { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, + { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, + { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, + { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, + { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, + { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, + { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, + { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shapely" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290, upload-time = "2025-09-24T13:51:13.56Z" }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463, upload-time = "2025-09-24T13:51:14.972Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145, upload-time = "2025-09-24T13:51:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806, upload-time = "2025-09-24T13:51:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803, upload-time = "2025-09-24T13:51:20.37Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301, upload-time = "2025-09-24T13:51:21.887Z" }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247, upload-time = "2025-09-24T13:51:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019, upload-time = "2025-09-24T13:51:24.873Z" }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137, upload-time = "2025-09-24T13:51:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884, upload-time = "2025-09-24T13:51:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320, upload-time = "2025-09-24T13:51:29.903Z" }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931, upload-time = "2025-09-24T13:51:32.699Z" }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406, upload-time = "2025-09-24T13:51:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511, upload-time = "2025-09-24T13:51:36.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.35.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44", size = 6002907, upload-time = "2025-10-10T21:23:33.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "xgboost" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/64/42310363ecd814de5930981672d20da3d35271721ad2d2b4970b4092825b/xgboost-3.1.2.tar.gz", hash = "sha256:0f94496db277f5c227755e1f3ec775c59bafae38f58c94aa97c5198027c93df5", size = 1237438, upload-time = "2025-11-20T18:33:29.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/1e/efdd603db8cb37422b01d925f9cce1baaac46508661c73f6aafd5b9d7c51/xgboost-3.1.2-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b44f6ee43a28b998e289ab05285bd65a65d7999c78cf60b215e523d23dc94c5d", size = 2377854, upload-time = "2025-11-20T18:06:21.217Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c6/ed928cb106f56ab73b3f4edb5287c1352251eb9225b5932d3dd5e2803f60/xgboost-3.1.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:09690f7430504fcd3b3e62bf826bb1282bb49873b68b07120d2696ab5638df41", size = 2211078, upload-time = "2025-11-20T18:06:47.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/2f/5418f4b1deaf0886caf81c5e056299228ac2fc09b965a2dfe5e4496331c8/xgboost-3.1.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f9b83f39340e5852bbf3e918318e7feb7a2a700ac7e8603f9bc3a06787f0d86b", size = 4953319, upload-time = "2025-11-20T18:28:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ab/c60fcc137fa685533bb31e721de3ecc88959d393830d59d0204c5cbd2c85/xgboost-3.1.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:24879ac75c0ee21acae0101f791bc43303f072a86d70fdfc89dae10a0008767f", size = 115885060, upload-time = "2025-11-20T18:32:00.773Z" }, + { url = "https://files.pythonhosted.org/packages/30/7d/41847e45ff075f3636c95d1000e0b75189aed4f1ae18c36812575bb42b4b/xgboost-3.1.2-py3-none-win_amd64.whl", hash = "sha256:e627c50003269b4562aa611ed348dff8cb770e11a9f784b3888a43139a0f5073", size = 71979118, upload-time = "2025-11-20T18:27:55.23Z" }, +] From ba608ed482b45aa986e9daff53e9ae69390a7cd7 Mon Sep 17 00:00:00 2001 From: Jae Date: Thu, 27 Nov 2025 22:10:37 -0600 Subject: [PATCH 2/5] feat(features): PR2 - Add Feature Engineering Module (Depends on PR1) --- eta_prediction/datasets/sample.parquet | Bin 0 -> 432009 bytes eta_prediction/feature_engineering/README.md | 131 ++++ .../feature_engineering/__init__.py | 0 .../feature_engineering/dataset_builder.py | 562 ++++++++++++++++++ .../feature_engineering/pyproject.toml | 35 ++ eta_prediction/feature_engineering/spatial.py | 372 ++++++++++++ .../feature_engineering/temporal.py | 94 +++ .../feature_engineering/tests/__init__.py | 1 + .../feature_engineering/tests/test_spatial.py | 124 ++++ .../tests/test_temporal.py | 108 ++++ .../feature_engineering/tests/test_weather.py | 192 ++++++ eta_prediction/feature_engineering/weather.py | 103 ++++ 12 files changed, 1722 insertions(+) create mode 100644 eta_prediction/datasets/sample.parquet create mode 100644 eta_prediction/feature_engineering/README.md create mode 100644 eta_prediction/feature_engineering/__init__.py create mode 100644 eta_prediction/feature_engineering/dataset_builder.py create mode 100644 eta_prediction/feature_engineering/pyproject.toml create mode 100644 eta_prediction/feature_engineering/spatial.py create mode 100644 eta_prediction/feature_engineering/temporal.py create mode 100644 eta_prediction/feature_engineering/tests/__init__.py create mode 100644 eta_prediction/feature_engineering/tests/test_spatial.py create mode 100644 eta_prediction/feature_engineering/tests/test_temporal.py create mode 100644 eta_prediction/feature_engineering/tests/test_weather.py create mode 100644 eta_prediction/feature_engineering/weather.py diff --git a/eta_prediction/datasets/sample.parquet b/eta_prediction/datasets/sample.parquet new file mode 100644 index 0000000000000000000000000000000000000000..ff2885305414c00354ee809a5e18d773743f31db GIT binary patch literal 432009 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ⓈykKO}nfktNeotF1j)rI*4QL$0RwA*L5W0AzSxc^rygu9nXV~-^`1` z0`>@9@&ItpxX&X%^8(2L$h63TX~cql9mZ7_6r;TN^~HD!Jg~eA{A^FY6GmCxxG6$o z+BnYaj*#V}26KiY>K!FE@DyCTJ`jFc<06Enuh!QHznlWiIQZGO_6UYwefswk0P9AL zm;`X2(^=SVwdQ|l0cA-Z$S-rQ9>D855o9D@&t3X^kF79Q;`1hpMOHeSKl6g8!~3-M1HS6+yO1Mg4w+K!VxXa z>FWQEZA3>mgQVlboPPkfzB9Cc5XClI_TwrA?$-UO>;K}=o7yA^>EEbLK{I}hO5StC z72h)Vfv9VBm}%*Ju~ZuG^8;tySYW6%Z|Om;t+_+xx#qOaHFy{V;Z#%bGvGd(4 zd5aZ2j~|VBSQeZ2jO%S!UeX&LZ^1G;%;Z`ND(Mxp)+>WN6v~B0MQr627apUM*(&3^ zcgiQd>L+g_e-_|6h39r-DI34zn(lP)eQF$1h%q*B&+e4E;2sVTlG({06Hy=u4fgv1 zTc^xD2$;Fx$zeeHu#B@P|IrQoxE5QTHtrEa!lKMMMQ}W8p4cXziZ_@24L6fHf7HS) zF?|$Qb(Z2)RQzhsKgeXNk;Ny_IT9cG86(B)r;AO+to}X!jzWQMco>8pQ_8g<52GG^ zP+AFhd-05JOmmy@3=1i#8{OFB83J|Uce}liAgia}hanM{uuQJ+L3!W5TO_!VxuV6e zg~zY()NU+y{SBVqgZiGa-ip*sE+5(fznZFUq7YWDzYQRZ<5j}CS4mw@z;AP;TsT%_ zx4R;;vBT{u5}I1&EX?DmvCEJt@|gbiJ|MGO-`9xH@L{0f(Gatod-fpbv8z9V7@OYu zFIFpKR`w4}mFsNTi*|l%aZu#+LSe_pBxj0Y%C5L7T*bmXyUpf03kpcquSCS6KLSMI zAGwo(_AcoxSWNh^jd~W`aV|HQlG^%7+S&vvyPknMVTHf;=4wmYb^XUeuxi?I*#*Ru zubf*6h`$(K4KNrx+yTUSTotw9cV#wA9} z_jhJ)p?~q*!Q^=H+9enZYG+DX^ELWYsm_*VO?<)GKx+E~A(v8Jf7W!j42J?)>Qg(O zG=Os5}>CXyJ~(4X8`!!(m+8kHTma3<*kT{=wv83~`!>8fTSh z-dx?EQhGU!fnRAGHxv^))_x9G_akYn^K3*v|KXW9LYOzt6eplwTatA83xw6A`I188 zCt>b0eW>L@z##JRB#S$$|*f>Bk{z!O64vADQfLcY4uwN3xrM@l}w z7o#JT41H+3;t}>Id(`z|tz|cOMSnUwW?|n4YHI1ZOmrYOhP)NZ&2?>~E8u+L_t0@9 z+n0pqP5g7%c(5xiJHT0QhApSOq7N%i-G_tNGKJEzH#3zMTdtR~&&`gT

FvX12{Nm93G*qO@J?dko2hH~^h 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

w#EHvkh$!X_$2y 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

J}e-uCa03i(rEe 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

pNss`wS!Htfa)l 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%0A05l; zPY+mF&R~0UVKEQeb8DV&HcJWIAoBFs)pIAH-|?_pLr81i`0>xbV8A}jF9#18*p53# zMdCc_mllcQ7H0F(y?*!52XH)St%7Lll_FVi_+@;FaS&x4F4E?477)=Y0dr?>n8B|k zV&ZA=93rY5dD!mzdZ-{}ae_P;#P(U$^|dR{E9^9jW^h(Du(_j@Pg-)_7irpp$~U7! zc;^}cnajm&OQ_=9yI;*g^qh`2AUN! z41mqS9u^^E;T8`g+2+Bg&V*3Ik|8~i_SxICzDU&MiB0C|{D9gNGva7Tny47YVF8b} z4GpbRMxnU8`<-UEAfr@Yg;>R{Sp#WQWcpU2MAVmYCYwQ2YhTqZNNYbqevm3D3-UdS z@(xA*BQhrP7cR+nP+nM2$iJx4(Z@{WjbQ%g_X~M%<|czn``$8m#3|g`j3 z<*(2bQNwtvn-qJfy!qDbDwJKkZ?c&rM3b{``Pc*DHf}>`i;rDzdoNVxF#~C|2pCp? zy2IwKN>_E|wO!+Kfi{a7nJv3cCyW~`km}gR-h8Qj}d9J*ovUb@D;}sE_b6R-r7HMj-f#gPc8~q z7q;>AHBml}Ti#2?^CTXz`Q!&+S+Fpd*2u-bETro&7P@I&ByxHect5AQ4@lCqtA{1R zbyiLpjcP*zctr2li`utm9{N8I17W3k12z!#V?N(WM6r$?r>COTvZw&QsM-^XA<9a+ zw2-#0Lu@OaB_L%{+h|%2)m4Q+Nbt_r4rxZa>(;WZQ?~@^8ncls$)lHGBe`{bZU>_P z9_Vz(26~>X*o!Z6NFRrC%;)r5dx>M-0m>KkNvNH>d#aS)dMqh z@+NT0KK0s?*v`!E4ICvbV_x?EiR@+k<;&lq!D-|r9FL;3`Jo%&CH1CGB5x$i`Zh)c zOyKz;Q9UHtomuhSzQR3J>F66+zO=UYzCx#K4Syammq?Yy4wSzCU|P(*YGN>`Q?P01 zFG@bJyL1AI6OZ4zKzTbqSjU9+95l{ti=$*|&l3K^FuQv3>QS^Qe9aq)q?malLdPVc zS4Treb}t!g?Kwn*XtLIbHW4)m)mFv2k?td#Zw=i?W@yTd2RHqJUR(6pleCyK$3hJq4R=9sjJdrW1%{eWv`1byt5EbPKmPr{IRGyT z0@eU%H{2vG+++AP+6+hz2_Xgz37p;&OtH~cVu!zQvp+omo>d(W18_4|tOTf-r?(2A zXr2Mh0pf`0>|@gU zWpi7FTNfjDzR{8C0P*~78vW96-gw*_C?21Go7!ul;r|D~%LMamfRZcS2dQQ=ogKFX z3{*%pyG}&P3d3`>&ygmd?*fpscK}^b=ZmbzgOy~l6M5WjMkoH+YZoYY@M9z{&}Nw3 zNo*Fw4o2(!9f%~Co0MzCs)@(&n^U^|0?Mk*x_TL)X5irK06Xp%6aiQ?UwQ&$T`E3` zsGhv}OVJl(kl(-4g1C%MQ?(>^J$cg;6b` z*PCMbk&1x)xw5ME>ng?M;pBUd`5h&B5G-1Je~`v6a+&fDvuR?}57ZFnGFZ#k5rU5A9XTvR!1wjXjgU(`X`7fpp}*UC zfEkmTp8iV2Sn*wBITBf{T&uiobtup25PsnYh{jBM+A&f9Hl|n5Sb>|X_W*m`?{$wZ z=S7xh2HM zpxt>XlvcmRG(F`6Utzw zvL)cfk~fhQSiI;R&c%4=37?FSFLzPH0BVCn_hTberQ55xRiZw2_1FuW%nfz?lZ?@X zxZ+6gCr>?V@1^@esTQ^EUk&2&ZjN+qR518!@*>4DBAewQT-E=q;-HS3TN}Yj0ZF2}%9N zB?up*WUdDM`+?ovB)Zz%V8dK>vj|Opx30cfI}qb-f>8y~Scn;$hF7yUgE@ zo}~UbkQVBKemDIkir%s*{hZ(eU`c8q^5lQM9tvWqG}~rGv^z2}_R{Mg2InP0E>V|N>!hvVVTVgUV1UC-36tnbnDtH{JTc>$BfmL78JJfUnOP^Aab0Z}SC~gY zAB1>L5`}uaT+u&Cd3R4GP`~aM&?J9P@NP-DnJgfTH!o{;#MvE-(vKGbzwxWhJFxd6 zbC2fEd(E)ng2dUL3*tbl#}f)@w(M;BVJE73aptLL)I+R3Gd+$Sve9Ub^Pk*zht=nq++RGHD8wP!<&{J$&^HORS!R@BadKjYi_`)lB|3_ zPidY)k1@+D!Yfgeouq}#eQ~*3rrP)_nbmxjRah;eX7I+8I*$G@Zry+bMxan|K$nK# zfvg~OCVQyWSx6T#k1PfJ0&=Uu=IKa#Mxib;MsaVwC3JBiF!7{6Dp=9!(_&RNHqY7{ z9@#Qo8aJz2@wZ17kZ$7l#*zfhGxP5x?MqH1rT`?`A6Cw~>XB3ER5$P20xwAvt^~?~ zL-F5x=oD9d@V_zWWNwoXLwCJhf2I)48l@(uf)SeTYMUiV?7?d#t7+TCZR|Z_T8V?Y z@X>>pC@Z;o=Yu*7d&j=7Crc7_cvH$oQWd;wR?~QPd@pn#pcyH&@n`jm5-E?0}rvk&h+(q3Ny@sm(z<0dHXSCh&wP^4~mo0 zRgj^a*)19;40}aNg`p*jc%mk%ynz-wewvHCZoJ5(pQEakEYE9B zn0ZLDaJ1y<2JUNsQR_huA4yd=9`S0`YDr-);GsJ$(|tTdO$9bJ6kAR zK*oZ?ZN4b%(}6-!yqS5uJu9pxa2*!AlvagUplA2wHjB-0#K9kTf4KoE^ZQ_PcV@e7 zdMQs&IZOkBOpz1wg&lZA`e$JGrYj%-E1z{$WH@ujq^)G;+f=WAHIb!ov?(r)^3=7ccM7gB*?1n->82x?!@4AbveC4_EzI+|N?%MlzpnQ-xwP zuQ>Ng9l9#h_ue2BOJ%wi%>URCQ5D87e%j~LejP1J-6$CO@>9b^cFzLfle5EA6wBPmb){Y!k+z3V!5HCsO!V6@7XZSz?nj=3yKUmq9a=RbYt6# z`I+A$tw4+bm81#8G<~>N$%Q0|BLh}#YaK>Z=dq?nKhdJYOVp}y?TDXgy|@5WlVwD( z(T0~PLSmt_rTsc?=v!A>@?9LUj5M~u7iw83($J5I)+fTzSVc|0d>OBeyLtolIhQ8h zM;eJ|WDRwcb>T!@uaUVLbEm8l+V+u+Iopc(Ly9?V0Krke1>!0wPxRZTz;BMjgN*+^I>K(Y`&=22ZsMwo&oovOk#N{L z(0x9|zvK2W-_gG-PO@>TQsu{9e8t=8q1Hd6P^am{q7Hsz87nWFxg-IvYmW# z2Y2zyx=8QY(=PuJc5q;CN%r&YJJ7tx3Lm)S5(gJ+<=Ed{NFZv)4CbEv+IJuPvCiG} zgU@{iL*2Spj_>6*ZhzZ%f2^=?Kkur%@M?$_CJLLJ(#yB*U>#*W&s@yjaPKoe!}`cy z{^eQkXK>yA<(qloV)n5oKa(HeaX7#GKly-W=f2_ZuDfl;;y!co1o&{l$O+|`r{5Z* zV_SD{Yft5~~G)w zHQd)1UwuD5D$nj^zx$$J$F@%~Nd2kC0= z6P@4PpN$P0pLs(*e%&7*lYv#!ProbQm$~mAdu}!^pFioLpFwQ<5gGNfyH9F=9nF}( zdix)6@+W@uK44gM`+1N27<3`~@YTP`cdp{jcx`!nNkbA7cm}tbG(o0y8PJFJiAa1s<4i z*L~>gzkl4r663*#q^p1TBmVe9C&x&c?^&L6{CxlP;A=1kxwD>r#bvk^c64VqUp%NR+AO=PPDrJ^}qOQ6rmrxnE7UlWB>3HWOGMw z_&=EH6Bx}SUw>pDeKZg6{ysXTbj~O9UZ#VOPW<%B<5l*?51gF$FXle+=I1>RrB{GQi*GvF@$?l-fF=~K~Ncj4+64{yu7W#N_N!EB)J%)JV10DIGY zKl!H3O}_IlU3@Zo&P;aaUv{p@`xbXv>!sOxn8weJz(>oY7qI4{pV!alj{L8ae|5aW zp8RAzlbBC8?$2nN=V`Rc3ozWIO9rZ1%DU%Z`aiKU zVuO43vtK$Bmkj)YfrA2ndmgOHjaUC=Ha`n1XCrxMldt<(neMUA{(av37>7r8W`#67 zvh%9!w%ecjxDQJjZg9VP!xuk@4%j^Bx_tFI?(HYul&!ly<+I=WEUx+5_dfQOSED1o z@QST`Jl%X@cn!p*OEJ#KsL)mLNeSKIG6j7NHQE_v>g_y%@$#kS*#Ja^8EFTMtMa%tzvH)F7@ zo4Lb}{?~V)!@T6naCMw|>aFj?+nfIInGfLY0{+Yo<5ROd`{l2rb$Koxe+0``*cUZU z%}?{})7!twch125bH8P#kD+?EypM~{$^9eR_RxPhJwG^uX%{4u{l+_fl%E{t?tbr) z-=h=ePrMH9ZS|CM4tpOZtnk}EwS{CudJ}u%nR#6CRkvB0ue{yC4gE1&a3-u?@&^@DTUXh7!-Id~2C<>|Xm-4G75 z$y+fYZ)T%c-j;8dxtm^j`&BsCX}`(A-E;lcC$5K$-SwY0=WUa_Z*h7iO*BOrnE>;N zLi}M&>vJOUpAnrDGxGqYJ?k0d3Xk0T9)4q#Nlw`Q?{3UHXLH|v`dfeUM?CnZjpt^& zX59D7GfHTs+}62Q4NgTt7fPE+l+1 z_~Y+A^XJI*77ecd%VYcSBBg?I5odn#hd)K@HokOq7AEfc<+J~WxBoczTgbtg7ZtiV z5qs8woxE_EE6wT20F}uD$Craa#_;V={7ONazIEyw&&(;G0PJ#>2lvN8fXF= zANfXp;&9FnUYw9C}q+%4zTE%eos z58Rvy)wW)G!5z2&ckyGleFVS$@LJkLH+;V!eduA_)p!5Cn|ChazW+IM?*t2E(I1y5 zvuC_X&6^i-S6uo5DCF6Vt5&Z^Ea%R6{Md6YX>r|CKm37bSq3L%A3Nz#9-TM&#a6!h zLT>gt^Vg?$xRUhAmD3@_azoo655K6pQqIfTyy7rr0&=ih%KrW1GRLsx$UJ)wT)i+SMzcI%Fh z_NT&^E>lZ5_oIJ#GZZh}l`lW^ZR`3WAZHJ zzWa@xEKPp+8;@Oc|g ze8%ZRx$#==$% zNB=VK-N>Hv)J=K)cJzhJ08HL_{_9sf{K7qSx8(-{Pksn^q z{p$X|!pdg3e?7M~3chq41a`qobDLi~^Qz-Xl{~m!j!gFU_uP|?oZms*Zr{fUG{9AeJgjv zHv*(n><^<(UcdK`k9?eMus_$AdG<~&S~=&Ojn?VRBZbK=-0Yk;Ab(@mcq}t5viF}; z&9`pky4)wS3he4vHou8+c<_2TKRcbXPW&)j3HRwI9{)M+?i2lYEn(`i^JcE~oWk=r zI;W$F+^r9N<$M_GEzeP2h|hfH2lu?;CLCRR=$qMgchmp+GQRWjGzPkVtXPzz0Zed!5 z?v31afAZ@~H`<4|b50+>1Ao8t_mAC+vwuPPd0s7XcYo5ux?}d@-Gljdm9v!PQ8fL@ zH~!_XaSE@{zJ*mkZeeC*o&L<)saY_dJ@QaKx#f>f8HXTX{`CuSw$*1&#^PS?s}H^w zOBV3q&m`wV$^+6%DRGb7bJ8_9DfiSf8NX#cs$GvSeEtu`eB;gBP5h^C#otbQ^o8$+ zeV8TAzUMF|63#IB+Z$T=%d?+)945|5Cx8F1@U35+fpsT`xY8dk{2PD~q{COb>p6`*_BdKGuOblL(p^}Xva%J$6fF@{+U^G9=PU~m*PYZ|Jeugpn7h7;agESfK0jXu%rL+k$m)S?(P3O z&hnV^|1+0gB2E3yubGYRA)N7?H+4_KJx^cAe&dT z#3^scSO12)<4YgEV#7Yf{rq?Ly&A*!i4F4_OaeH&(H-pMNjKjv;Pl+Te(_5Wt=KYi zd?SDxCO<$zEq4l8lLG4f@OYEHc#=6om;L<8YgW)v52H=i`?yE0P84unPh9-O`8fH{ z<>kfbPW^q{IsfmQ-+8zHy@uy74=oi*RkO@$vO6fL&Gin^ra-lgl{i za}Rv}_(h`HZ@-N11(!XI&s}vn{~Ig?YMgrsiSYJq#=d`rHSd-SAIo+={Qv%m zj5yul#qSOeo+nQBSMckJB06XOy%UvHd<2J9m=ji34eNxHR+*RaEc0i0orM?n++fbf z>n%BU)jEP7gE!%~*Wo|WtMP-m48NU$*Guuzv!mtg=y^Go8Su`kbu}-P9EP+RH-`(o z2p_U}miT;UHF|kYT8&%;asx?-W_)gfb3~NJIVU(TrDsv4#K}*>!boonil4$kRRHaj@6N%cPURJ5#n&P0> zAvW!37SVTA+Aoj9zS|}99h-WhMcPDl6?fp;o8}3k>PDMW2HmzKJ{^z|@klE>*FxHv z7y+*v2~}N&w|i`FBs#>eIlXDrK;OmsaHI%Gr}u(H0`9k(@%n9I5mZENayAX<1IFax*B+jM3)#39L|nwsUQ(Vzhu z+f9>Rs~oIOuRUAsF|NHj-Nu+*%p=UOs~=1;Ueapo9tT1q_HFx`+SG77AEg4Sg})M>NDPPC8D^Fp%&WgzQWru zu(&W>lhw7J56@>XbQ+pkDsl)zE9fCWL3E_7r~^f+H6;$BWeC#*PV4t7Uj(i>8S z3WZ?3I9b;QBKnFp#Dq9}us>@Cq`Dmwoe#uWQ#Gb(zlmu>bYhXg*e@%_XfFzJb6U{r zuSVCEb?T6qZ&VUG2h1F@iw+%O4hOVbSBOHB5GKtJsUtgwu$L$%;M{d+ush+W^?>R} zr*elwkJ+*Xmr&`*U0!C_`N;yIS=6cBj4BqsyJTsVcjSoxdB2_8nla(vS(b4<8dF_s zz|d4`xq;R<0++2R$D|#{n579-c1*f763r|%4q#tlcf>?;a7eRBs}j48woK#QKAH0) zIu!>s%bo95i79v1_+`%*g{CJ~sgn*;H2;_o;)Y3MTou(l)fL1RsS;mPD0U;wiISF3 zzaqE__KoB_#DvqC7N>`Zxz`qx)a30tgJ!Y422F@0&T2Lj?ag4@4aS%KLj^r2>YMh7 z)S(KAx7$Q7Tg0x+Qz0*CM{$Ci|n%FGzgF)&w%Z_H$nY2svAbD45LQVbS969pR`A^fmM@W@(Ba%9RYbjJ(@}kxR$=`LoyhYX zX}eQns)F6d6`iR{h#9`m6ri;l~3p z>P>h^5Vq`9%@*f7!fs4knv+(Ex66i0dA6nvwiJe+kr*S?k*8~Ps4=OOMzX@wHt86w zri#hfguV3#75Btk9&QG$$ff1)-fAn1xEyt4P|A^hJ-i z#7=n{>c$LyqR|A0k8%P@;iAjF7{HxY$DOp3&RmBVj0qgMuVHo^tCl4?*ssz^gB6OE zYHc7^(-!ZLCDFrm` zz(3bI;*{@-CY=-*wgOAzyRvkOJmPwOrQ4h`gQG1v+IPwf>7>%C_4GCEc z;HB_pQDG@tGno;O!1dmommOpD<9`oU^>+hx%k&Y%_({p;G@KNnF6EDAXeS^8LoFxUh-G2B+#&-a^`_!%XDMd(-zl_MhAG(V z%s@5|7bfi1@&v6v)=99-q>3D~p%Vrq!N>?D=>R;GZG|SjeuC#IaX_>=D00RNK0E* ztPDvkY+{OCQq^$TEr5@?HryYK=X_^8H=N$Q=hY%Pc@9~M0cFZeMd7XOd2bX(S&y&L zb}c^Er8;TR4&Pb~OZEyn0Y0T5omMni;uIP=s)){>Sy!7L0RSSi5WX27m}aBO$_-xA z-DR(u9f6GG1-osg@-~%5tAn>8^v;Z!!p~C%xN|__Jt<|4fjrFNOx>ncRPl-CT-4ps zj$qhfgLHU{D7_I{=^0aZQfAX2L;y;4F=-BE?->2E zE;>}$b9x=O6UGF>71o%9%feiSv+BtH+IE+zT&}qKR&}jB-SSqnF2$1w3QG}Iv0O8j zo902%uFMb};XrIQ9La+m2{a&|db_O3jdj_RDBOI-qgA(~jnbuT@ia09V>N{{EJkcO z5KS`GS_pr$lxdDCHtz$*Fq*T_cZHr=i^SGZ#~6@h*>^25hF42g=b%2$#u7&wkwLw& zHz$$7p#1}_N)$5K8o=om^fksROmYl!WRxpdY;g|91vtj09ikMJjqOo7+mQ@K)5lYT z9Vav@!--*tg`&^8Qj-zAT2PtLQCWZ)4c){Y_7WQKK7t-w_lZ@D40xbT{XnsugHxXJ z4btwJA>4adr9jXKD=<^LzT8%}qtB_z|d7<9ng~ha@1FATN z?J{LVFJq`B+f2zk!1qa87PycxEZTBA00mO2r0Czh$uV8CQRG65bF>*iErWaa;k*Z0 zY?#1P!-_>rq%6MNKfRXJSz?BI=2zme8k$VyA4heQSB-&&X=Nn1w-)haW=&g$OVa8| zMN{D;!wdVNQfMC@$D)9)04fLUw8}wox~`exhzxdgBX;$%-vpo)0{{%Nr0sO1%`1K4 z?AUU9-O%^uZj9*cxCovkAPA$)h*4Z+3}?yQpv}uu5#fmj8Y^S-{SjeA{g8r zHzOKQr_$=pF==LXm2b=a0tQ;$Y3wIW3_O|b*kT8OlBth&s#G=Ruo)FDVsd1(g1hyA z<`T9hj7_<92#swl)UDycCc+At8`jvtJROihsefRslGahX0&m8O{^pRO>~?E2IIz_@ z5DsZ)?2|68J=!n;=4yVu+MLo>eN;~Beb?mM#re9{E=4C)ObV+ek?4(uT11rHpbWUv ztQs?Jb9k1 zo-HQ$@~i$^Zp1tZHsCS5KEtzO+r{1($gl7CIHJ2%P){v7e6wE^*fM+qP_ySQ;T0pg zto226vm=HGaN83qgsr1WCDOWLqcl7iws^DBb>Vb0TOM2l*V{j>G&zML%`Er@OeLjf z7l&(bt?1+)SRRqKvri+~0knk;rbzt|Z5%iSWG!0yDOJ9NCb41W5)Yqn!CG|+;&VW?yIy`qzSPbNN zO_R1&c#~O%2loZiL2T~OdC5Bgi3LKa52(?gOO0`r()<7-$`lc$wI}qt?o^C{q{OH& zVhx9nu<&en-Y$j_cLHjHHW;fmL)^0pMfI$31X#@!XB<_v7L_VaxYXvZw2!IVivT5C zq*L&e;#4X)>tt59(A1#N=3HlgrUiAyodQKqnC)qK1Z(y*X4}`2Nb$oj}Qz7kZjtf9VtqO*IVUA>ZRZ$0|8fLTunHeL>W`U8;`DjtC z#MDOiGX&6R0B=<*Rad8M&qtycPJt>eC3e{;DSI@jt<8S$v1YeL1BtLwxMt&DX6 z&|8Ar>Gl{RAlGD(X!v20CIs{xk+#9MfI({+12@4c8V~6)ln-Med%wP>K%bW4+4H0EoM?u%^Y>zZ3IO*pmP#CbGS@i1w;QTOi7g zvpYn}HqvIWd=q31Vl+UsKt`dryCnV1(F+!FamMc<V1^?cFN_?8juD8)nqZmPVcF#s$X>WE88aA)W0}IF%+_NmbFjTjlo?nP_@_-YUm^`*&lOf-t zCLDt9R+&M`JLvidH$JN3UU=V^Zv0B_0er@S8#o8zHOG&6I~Ww2@adL zcI^z_l8D&+(wL`Rq87Z3)Pq658-OxHJ|(%MO=hL1%hl%^(M}79U``{_2s<^i&hiT4 zg-R?MpkLZ$2^(;G>NuctMv(*C?beuGlQQn4>2J5?S#CVR_+Tt-q?0lQHUgtUQpqywN$dp0BFEIKk&YSkpy{t4!0u%;Dt-b z$_2fsn6T6jtBckS+}&>M#^?;Y?zrrtzAH z4pkDTlU>H0l|3>N=L^ZL)*6Us3Px~V28h|3ALP}L*+u4#a)nTfF%WH8VM{ubVyb95 zNR?sQ=Mj?0k{^mvXw_6vas?3B3aS8#$dZ=p^q7KPBGV%nTs5CX@;QraB6jVbv}YPq zDy0G65LDne$6g zf?sGLm5wxFi(P3>(iYfr-;e;@mZ1N);f&liueuT{p#KQ&!puk$eM4h#1Z;`L(mON?uEc%|rZnot~}00Czhhe^TS5QRb? zY_Fke32HjJ+-B?Z&4`iXvy6$>5kVy)*i7+LDKmAX#_}kaO31Tq5G(4oo(6kenc?da zF^_i81p-oiz%RZ-m512_cdAU$5nYuITvRcc*B!RkOA@mUGR^Jt-3rt63>zu5Joyv2 zQYiCIl?Sitb&3|ze7W^T8dbU>^6_#N&~X93Lr1k>f3jm{cQ8XrkF<EOlkA#UZQ9!w(sf_-;7I@rVdsGy0PyXDgX%>oxNlJzw@cnCKO1+C4e5}2M_aOf z8krXrA+&)!qw&OG$Xs*80CuMUEqdFd9UrjQ>zS?sq|i!7Wp;ap(F}H%djayzMD(b= z>4BGk+r2hf(LBVT5x_if3do_ZA&_Mal7Sagvdk)*lhKyFQtJ~HY$da7;)w~Mk5vYl3c?O^z;l?5mz``0ro3%Xv&RuRf43KFo zqD;!qDvlPxEV^QY&Jbdkn+`*&V(W;<$iqJ?I62fks@gnsP%@Ym=464)C>*#0S_LUn zNH`)@f`WR8A~`|M#&@>kbQywB;oD;0MWTqVN4Zg+abbc~vWq;uU(l3_3BYFU*{BKj zsg40g%_UTjnoY}tqPGroKJ7Wc#=>F*7LqYo(nla{xh5GQ`&3E8l}J<{g$y;pb`JMv zo09`|I~<~%=FW&!7_i0e#@J5#wZ5@zsudI|P>pFAlBhF5gbD14PIDRB{X-A91Wf%~<9FtK!fQ{PF4LUSTcQZtJx z&=O2UXWZsnyE<0YXlB?&JEf9v5{4S{AJfo6v}k}&o&te&$wbz3g@$6-T2PGEG#}o4 zRs;DZw`egw1r{<#cA|s`F^j}?YRG)WCB{4(G9Zkdh6t#dv5oM-;C4tW?d)5$S^&aO zidB|GV~4iKdI8nW7$HOH4NzWJ3ehEUTR}RpItmYuPEHk&u7yr+AN7l^(ZTX$_1g~s z7JLk$-{aY$xQO-7N>T_)B1$Z-P#SEwW;#z>6sbRwr|N0MEGa9i z(uZ>M{iUmVZhPTe%`!NuRN4!@tS>;Z)jF7o!d83$GDI1VW!lXFa~;oCx_lLLr`Wlw*G)Zm z9(Ftn!PUTnOn3{F|8}cnt~6P)q}I5xaOxcd3o?@}O#5(>V(`e9`X$9bD7cO%V zA;p(!AkwZcK*y2(O8EN#?$cY-QdH;ssiX(psI)KpcF zTTD1#^x>aS7>))AZno(GoQx)tH9v)SlOkTyF`vNKZpYV-@T@!C3Rd@8#ANQ~b(ln_l8lwTwwo}GOiTq5 z^E7<_S$@Dg!grXr<4do?3#F`5I(LQLS&GUe%z26+1}ZTg3r!O_PH!&S0H9XEC18GVS;D7{Ym@!m61_*H^(#)D{u8V4+vnC2e=cWCitb`LWN+qp<1>-=g zsH75#n70`nKvgcB0mThL5~jnVvR-0NItmd4Y7NZ3IfkQ#`8mBT`$grz7%o~=-D6Ab z70kS)*g2xJg}98l3=*zvBBOH~fRqfd8jw=EG-Jwo1=SO2KjBd)m6e<^l7)?rq84n=F4OqwG(6)rP&Ebf)fC~t&Lq!nxKt#v)(~6<#xuy zuu*42&kc5b2sh=<^GLGatxYh;Y&kfi-fmX68(`cJ`pE>HrEf7SaG(>^`KG`|Z6w2x zuK?58sRWWzg{xwb6oC%dvt70r^GD`cRd4{#v=jR1p*AuU6;>H=6(8^lsx7{e3KfQ^ zu0{1D?E}w(mZ}_X@C?&*wp-z_5J^1;>`_ackE^u5GZLj9iv*NeD@vJ5Lb()hb}zwi zzy#%Z4B-&LS#;`y zP49p)p)ESu6YAzdSS*_?vg(?$4~W7of<0g?BSaXbH0181w{K=AaBMM0o%N z+J!Dn)QJz=4o-3nVMhRoHmbnL!=M*{lLjZ^#xO+{&@!pV&50pFp5=io%vAjV0M$#2 zv50~)c-66&vJ{EBs6w}eB%P0d@pRcd9|4<8H3cUXc2&rh*n%&*&;g-44NTi|jLFyK zU7zpL>h91ThRRNL-$WRP$^!PDdWGtGZ2n8|( zu2TSu5LXb#;TWi|^@}med9EriQ6t}|3~8SqjywBm7!fD*o5FayYn7Z8%z%!@P+y$y zMFwc{ih!PM@KHT*g_;D$WB@i36|Z#zb!C1gPi{6ky?z-QtYUJg;IE^v*rLymC$$*{ z4hA%nqr58^9yCeIl6OF?hJB#w_83gxE^13<6liF_*4Uj>q=Q7=oF3Q`PLTEW&?_jA z+_|RMrXw=v+uM>Bi^zf+V5?{iwJN{3+;9_WY1PHDViHu^K;E;4 zz<0L641i*t82TnuwbMfXuR+GLayy^mPV44og2vnlh%tO#Z4NAn6f2WGEw?>tmd&D< z+gnza=(HM`T5+HanfBfi;H}{b1XRVr9ng0dRlHui2G~Kp?KU*A6Ssv>>AO(7%Ka;V zo+Uspi<$uZaID)Aidq&phPJ5oB9io^%^9=r)6Nc>PnM<0K@!&)V)5Ogx(3998p+rf ztrT?`CMo#$qVN6N(cb=H>Mx)aUg-^qvN>R=Eeiz*Ho966F6&W>#e=U~uvxXP;B&=TfuoqZ5tu32jUNZZ9^y*96dFrjnIECt;uRc4D3 zj~YFfD#WTs#4nGEy08$SMc7PEthA^H@_&$n>aa@&`zAQEnlC!;BeB;;* znlinf>~t|z1yH0d7{9(4^7DNVgzHFb^{`)PZ}g;ko7iqr!XnjquI%jUuE0;lc~f#N zkY*iiV6@T+*a#M+wi60e*-}@vDfEO9MShJhD`luvkvwF6LzVqEK0G}jk4}^OYlf7@ z&>15WL&1TeC?ioR1K@N{tfp;5g~1v)wBTMun6in2&VY!x|C7b;-oOCo;ew??k;Mre z4Zv^Bm|}*(=Ttf+Va1Kg4YI_{Zo&OS+RgWORnkUL5F>WX3w3t@Wn&iEGqGMD%8(_j z0(uH%Y!j;%*So2(*BVPG0BsFU;;U#zhX91YZ!xlWo$Nnf_AWB|O(krIlUlI7qz!k8 zglJMx%MGK8T8mUc#$Tv5P^M8{h$OWwcK8gu8Ld6fWf3J|!gZ@TOjQxUQMAGacp0+H zRHzCVd=F|?a~+fhZZdL{g-}pgs+L+OZ0qj4Fy+OdDTrNAVgU@gg>1SHEN;^lYS6B_ zuw4b!2Bwhwsm!>t_kyCcaWoN;RMX%n_MAM;K&FGs)#u=%%QhAl3<|@y9#hj8)1E~I z*Q&%o98;vH;JWC%slzIl7`>4@G7{*35==NJwKuyf#&Tg}igu4S#7?uD4#gHUCzG*- zGKfi;(At_DaI?0frzeo5xDyH)30z)+tbOLrFw=d|R~FMRNEe_$*s9y(Ar6f|oD-kg ze7CL=!5sm^&BlUg(`>-hg_8E`N*N{0MZ8c9V3C^bjw-R{Y*{N{mL&+M8dPo0iJFxi zo4#urP;;S@=%B~Uih?-kkNaW_lf)!}a0_`oB&oM4t%cM=MWetpoF4&Chkf^xQIga6djx<1$8k0^T1BlDe&$?0CPrDe*F7E@;)|FldHLAJT4^0;(A}}=8 zHF8J8L$!J>!ftgKIw&VSc58Hi40qb_R*;Akts>@&s*Q@$8d5rdXI+a7C(}+&5Ynv* zoE~^>k0i2}1CLiV+K6OK&jXb9c1_w}sE{b9x*HXh>kHl272^%45du({m~TLdPB?<| zX2`POoI6ILalhl*1!{-6HOX9Cc?1muVu3cH_O9iEd(b+?h)4R~f)ccHxXOwGpBVE(dkx}8 zXo^T<2ae*o6}udP{j~A84dz((7qcXVSuR^6mog=h@Hf=H$1N*yG?+9F2F zY!N+n7-}Jl|iS?AakMMIY`{=_aJOAI;e0A8N1*eLR=nVg#~8Jw9xIy ztxrRl6y2BoatZzyUx^V%1+dFEvL;V+drDd@wU}L&410druS9zuvP5Xuda5B{fd?os zF)k+tJPwRQN9h^my`3&-Z0G2NKm~9z6fk>9NniPuz?>lTm}aXkH66LdRc5qG6fn+R zEvh&!W|=I#8-6TA7Dr92T8Y92!r@k*DK$3a?(<3K=p329zP?cK^r7w6PAh~|tvEnc zOoY6bukI_g0TiTgI&c6=LFb@Fjo*WtQXrEe2{J<_tDRS7HEbY}BV8!@x<;2y%9F~F zs3?Ehs1vwM+S+N8nb-)U8XIPX@IJg!qIpGUU4z02MT_puviF#*o(ixFTjM*WE-Q{` zn@9R8+O>WLP653N>T~eJ{|CA8kMi;o@G5mkO@KN;5QG98F*+Fuu?k)p1w3F(habSr zt44dLa|~ruajpT!K(a7Tm*oh>UhvZeXAM`h<-u7PuYo7G%Eg#1m?k6!y)KEjJO)c- zDmJD}KNkx8(DlJQs$>M9sKT89v!REwokZLr2CYp&e4(_^f+cnKyRQn{9)>*3n-OU=OGuW|J4zJJpuxpBNOI4~#PdE04SdHA6DQGK@e$f15W! zN2{Kyn+Q+ z2~z^4sIH@4*GUBsQzP62sV-D#x`*TGh$aLU z@}hC#Hj15`+X#KqYC`r@*c+iFsKWMWmYgHT5exHz-5-L;iR(iIScf8Iq6#3owOu71 zG*y)rfiFlKrGUX;s5>))s-S4?3{$;mb7>z&02x+sum=0Z$x{GZw~;!qQp^Mp*+beT zlRc)Q^Eyu93cbFF=71zlYNMa9Oc_#otS8F&i7DpM$jz3-P^pfPQAhciW|DCqF;E4h zx5ciD!Ykh%x3OF(Y=@w?_amTTIN3dj89;ui-i$y5-1PS$Ix?!79mYi2j%iHTvQzICU_Z(@C`!3se?3dl4HUrbV4R5bR_Gtl*noU39Kv~B0+JGN7kT26-zUu zpe`mMv}<5bc43lQS9YL=HuMn88b;n8$D~_e!Ed0RVgjW>01=v%3F1w)tQK`1^~LV? z-0kyvwwSjt$>YcJC5*Khoho-!BiWNG`nb;rMg*qqA;o=2pr-ETdfsTKs(@r z03}7}3u-ax?@GJ`M}z6ttHP-?(h)%7UN*PIDM7IaO^z$JhN38HsI4?Az^C-#fl%%j z2RldyD>}IAS|8%{4j`FL2E7qDoCd0{Gh>OE%koMXBf*e>3H{zM&4RK63?7_~NX?Ex zP}fW&-hpqev};fi!F&TT0hKh$l_v`yhhvZhaM$~BFXlll)KQt=@kCJANC1Y^#1e>V z7#NTN0_=#R!bHS^5;GjRI*8`w`Z%f0kgP+VE}B~Bcohcuv`JHw@x!?`xucg8`}=`H1$r5JC1Rv*yTLa#9*Wch~5 zu!p6?@B%}Mu#f4l$o4O@EJ$IuJ@{M`j%ALd!O4(fmnBvxm2J$K3NW+?ktCci0(k|z zb3K=10gbVo-vGp@_SkHz%Y8hh&ox{B9<`=rlnNuZeBVDfZd0 zLoE~*W4;UQun>1)MGF@KHo3S3P2z*RSjq=Y-BiEZfuNkyOsW! zp+j6QI+-_1(Mco`S@ci8Y-80PWGP$IRjk``pblbj6PG)6IhPY zv(mZNsVB0@W66bu`5I$e>75AU4qFF#W=M%M(qt$~2={uZhOF$IQJBe~CzN`t zEQKtx34!PReKUlcfaKZ13a~Kd2O{8i(?Z_Xu3=$8eYltd7x@_KN@EO82z@d^rqvoN zypMaGBCb?P0^%H`DFHPLj)EnqF86E#CWIoC7|`MX113bGRd}Dy?vS9&yoPG+V2#Xr zSP4U0IP+}VDRg;|ENPaVWy`tEy3m}FxX5w@Xn7YDLug`65c_=elF+oUb_1F%cGn?* z0lc;FWbgUmjI?(8wQWdDz-bbW&I53aIjkf9D*xRsAO?PERb+bc&=_a zBIbQ?*9XT0djcVvheS;(cX>jV`xVP6FeS6}1iQ)gi1# zFGEecg#Su}U5y+Rl`|Cp7=vsZ1#H;Cu3FjVBg_T(?Tj2NTFiO)!WG#e>?Hz%$;ZYd z_|_}4BQM8!TB`$>7kG7bfL$?20}c#a3v*dcMe*IiH5wnqmL5qn`#APF0rlyuqINE2 z3RQ}pV<`Tg?e7s;*+(u!1>FRYP_eH=L|V)xIRW5|+N;cKa|-FNG8g3p)CUsmpCXX~ zM6d#4j)@OO)L~x2tGu}yy&eY#SY&yb%|sf$a&-=SKxj)MWlL)jkU%a`HOnBdD++c) z0a1*NMg|Db*skMv?cc4sGdghF`&TR96Sb`TkHmt|fBw%1oJY@0$A%C5cj1 zb(3Aq>E={(hP|cPRg= zMii5Q6>v7OLZ43o2bVUe^EHl6vI4U>#a!HWjxx|TbFc;O;o6|#Oh6^q0bwjO_o-G- z&!5}mg5VAZ1T%P3U6RPE`u%?I_ulvYt!~`08(9*4qRB*2qA7&A#t^hFhA#t1zf#2a z?n&mEIk$@jT{B|Ye4-#}27;AxESn^hdRi-It+ffonv^@(Dm-A&8-gmmXn~dyJ`gV+ z_{v)4J_*!X-anv)OgdXArPpG%weHX>CYBxWSm|ih?roFUE6x)tCaV)Dgs)Rmw+h*s zf3VcU0!{Yj?|pYWy6`ucmUz+u%M>~#fs>)3u|Rv^Vk@tiL& zlF_l~-ou#T;_7|*hBNgLylLkWWhv^A|JSBDMt-TYj`>?E62quv1v5gbuC(F|}2H^91E&Qx{dQ0SpSwBiSn`3LXx<7t|0w8^8b zkm;8>&eYq{^0pIn6p*w$dzjp=;mT+(Q$Rk8DbufGXx<+YFa|P_cp`#*rQT!m%FEqo zzCOBTN(6NYQY9)=zR9wnlGWI*EHynszCDO%vIAf3F{Z2CASo>_*Im7%GFchQ0TRBl z*3PnhOLWnxH{)XjJfNK6PW+wdc&~}`t4t zTccZQM=h;&-&xB&Y`v4SD?rSyTnE#aOx!D6!)|KTeGC7#`A##DoFPu-0E=(+JxB~Q9?OpE?PIFVmTILjR(XxZ*lR53ZERD@ zbz4k)GHm!X0#LKr@vX|NomtDH2Xgc4MSEx?bhcZXd5mNy{Rl-gPX;L?-YWZZ^_FiG zF9>xDZ#9y=ID6P+`x5I4^=2qWPSQ4n5N;jG5SMSrQ<&eDW@6`}#+Ouob**qKGo=~C z+42Y>ZM=r-Ju9@Hov~fiLjv43xG>t^b^si)!>&f-1K)a;ZPf=5#RoJXh{a=D=EEWu zP66M`XqNriyJyC6!^~==C~CRmrVT(Qf74u!Wz!lBU*Ksxsyw_&Dz)a+-C$USmL-p| zy0ZmawPRRQqouOlN%VaWEt17}nwGzDsM>fXN>v;WBfIsWOm%A~G&!H0Xn75lwMMon z#>@!i+YeO~|G);YEDeZ?7HI}hnQ@a`(u7o9n2aqua!Ya%0xgRD9JlbjN{FmH=c_CO zX!clqfOpKFsS|c#AW2`|Uo8#kxPi+%M#MG!unoFmauc)y~EER*Q?{TcuVE#L}9l9KQ*&3Wg035MW z)rLN!H1s=oN|MV1ItHZgcWk!t!=6U56Ulk^L9%3_TC_Z<_$?cco!z`|9d>**Dgu*- zG9Evw%iV0GhqwjkbCd1i#7u9E4jnB=5Uo#{j7jG7#O~Qh`+6=>#K++4dz#;P#|jQE z9=Gcx0$}E$N%xXSJOVJ;{&6$gE>*BPnx(PriZSA2Bz$dK$25m|T;a-%D7aXTw@DmS zv8j5jWn)?0D(|(3A&wvSZSjtYv~YC)u#^V#7qM+!6C{mmDx)RIILjL1FL6<`?f`RD zWO(ryxT%yJ8d@N!v4M)q8Q`Los7@b>XwU9$lQ=|blN*!L23zRb?P9i!QGoL2_ASr& z&1fvzsaxR*e#50Mw@-)gR??^eJs)?LdVY1UwPwJ2Jq=4ZtJ-Bv&bmdcMe@{&vuwti z+q=Wv%a#V9-);iui+(EO5WvQE-!`;lHW`vtqAG?Ttgns1*2$TA@eq)|>}1Cv4Z@8$ zqZ>L&EbfI2e?cn-GePS32M;E0OMo2degNgzQUfk*(>I0!Bynh>56q=PxbLI(WhiR} z9-~M6pkd<#Cau8|nYEawUtV(rUDwEA9U7i4jlG2Vpo|E|{2^Pa$)=T2(-ywdJT7bu z5_@DTagDls);szS#1_XlvtX(t(8pXhn_>#b0J1m3nKfw%m&raOsGXCsn4)Os3uS zk9@5?Qh}C6**v}&^DwsLtY_}!O6_fY+cVMLt(-V)B&rgD;vo?cx6@7r9poR9dLWu> zg?KsWwvix~n8)>W+|&BuK_J@pQ!reQ;UZirlzJE~op8ffYL#Ff1Ys3Bszlb5mcBJQ z>vPugY{)8cZn2Jp)7VOfA{6sQ-RMWD{4lRo5}7l{l~Dd1!Z0Jl7&Xm82scSp?Pn>s zWEOq$eH13DrZyYw>2YT-i7~e);>HbA?Dg=TiD2F$!sQ>3S~Up*@Jt_)11($4HbwiL zT-+*wPqXxSi&vwAW#pvKxo;j~y7(yc8ZG0Et{0b@jKB(86!xtlO-WU4;p4I|JSbtp&wTV0UN$LoteOZE_IGF}Ex;329?97toZUlBq8>KP;HMYX7OTh|} z3^Z*cB%jCMM}wJTfG5IyIIkJUt#IR9-CC>e<+Rr0##%qz{N$3kOhy78zNQ)%4+`tn zVVVS66LxKpZ-B#+u=2ukFIb*UsGc}21!TM>|>miS1<5OR@DB z@O0#dQ9?y!sS~&Ms;J<}dWKj@y|Y5B0_}q*NhUM05O&UOfijT!X&V8XEokj!2XAqG zuuOjv8UK6%=A3L5LX+5WF_c?8Zr#ru%cSsR4hzR)z=j;{dxk%M+j6sueY8?^kZt4n zdXFbfC#dKq8Z|G_MoNr8zIFDGRBcMqa zNK&sSk1K0^iav`dn%=FN1fDXM-4cpcy-K%lk^_wyKvkb4o93asXND{KRx#UI&qceT z!NqtE4Iu90xmcM9ycT(!czORA&4~3c*V9{M>|S`BKWrScb#5d*_GpKEoTlQZemJQ*!(#rTLs=<)2s;bD1;&LdIvFdd#~S~W-u0wX}y5H-9dCSxzdyk=P2v0Wwl zJ+$*rfM={kCHkJZm3i!7l+bY#fP|bzUsGEhDxQlM57h_7QXV%`2f&MCni!n}MjZHY zfeUl1R%a8CzM4gwJ#M0?yROX&pVDXo-O#eTcAof*Tq^o35=kfCZOpP@--m{2hm!M@ z_;;oslbs40$u{q`{TL6~7Opqy=j~CH;F^|g;fsjfH%ZFr5f8-;7D^JlBJiO`(;AZd zVL@}U&Qi>t_#4MGUGqQj(YvxJDbOq9-*xJ9c~J89xCxh zu=659A{VOa2x;P~=5~53Pg+E@xo7xejJ8app0*aF5V*FDtqyvpczY<8%6>Uo30r=A zMfSvQ`SSIGU#4{kZW)#=K5%Qa@E(*AvH6+f*gf;m(C{g(!*8;G5N!Hvw^(Y-c+#Zf zna0Cj6!e%KlzOg|T{6~dm2`bZoQ~-3k26#F~stywnXlvxkG=P|;$0lsEfH ziJx~bUBrfLlqfcr*K#XQ9%PshlArj8Mro_B?C-r(jW@6d09dSyqxH5%7~BKRT5quc zXpGYE&?2TJp10Z9_i+o|s0!{&C2PsRPhGoVOg}UZi@r9Z*?68>iFQ=nw_s$;rc!`t zy+KeELpGbEr<}?W_h6`YsYJ3A(ff{Xn~qTTV(erxx=Thdzd#zL<;HX|p8s4H+XFD^ zC}hsv6A#Pe=^ZCY8>XV9R-$j5;>rK)qF`U=7ev=qqnQ$U9y7sVC@e6kSb0=KnSDK8 zld}X;w4QH55CCUB9hb=VwBB*{VwCFL==j!KzQP^_KDHTl=1COn;FB36gQ=PF_uUW} z&35|IdV8y|Ucq$k1@q?#s9PjO4CMNFE6ERo2i0&3Tgg`ev{WeevFP4XmBU>bt06;+ zuOWRddzzW(dVt%ezl`USwcYBHj5LmmrN%Pg{dkaVty$|s67lS0y+r~*A0xm|8baK; zhw4Qgk6gQ6QUd# zXbjg+`M$7;0N6D;AC@K|L-*IRZIBRUv;@|R&mBAUvNi;;5ey)ygf6dkjy!pCk&<=; zXq&cWjWY7RVH{6Qy>L9l@mv5J6}8$rCXcUH>ef{sI(5g>}EFU>)#r9LFmi*|Wl zd!a5-CEu7RkMOr~$sH5$@O2K8TI<*(6)}|8meW}S1waKL?iB|ISmo=1q>@!NEVEf! z)taSpxW8~LuemFjv4{0xoTWZYFOL$9GxivrNI6VC)cHhkd?7Tt0GrA)A$69~6rLl% zU_Fe{SusBwA8p}LwME!bwpu`+WIP}3uFJ<*YWF5TTObu*IjT+HQy<2%#vWLZT5EEU z%A`jll3?BpSU|eEw zWP|ov)lX(NnJ#%O`8@vi)-CI7h%|RZM0ZWT)x`zJs$#C9?~wo9Z1KJmtj>Azzi``J z2McV=KpjHIbqkCxlj&U6$`qhvtx#+MS-XY#LKtme57(S{hqj#)g{mg9oLWA+ZPyar zGkzFy3Ge1I>5qq1_BqZ{AR+>q)R@baYFeD#_U)9p~8Atx|KlNb{s_y&ZMSAu`LEXl|L}_J_qi%U7)f zaQwx3`9Y0cQ4MCOn)kWin^iGi%+j71prJ6G`v!h;;5T-!ESc*5*SM<9<2Y z#eZMb@;g%$IgM|7SZFsMtsigvPM?(P8 zqtGPPq5?RoMw2Bs@*GPvrxCsI#VomnCZ4sus-M*8Y(8WuS$cku;+rO{S0$ymy;Y-! zQ|w*?{Zm^gjkif>1@G}o+x05Sdj;PZG3u;=%Id|sx=lEup4p?k#-9Wo@&LZUI)FPm zs#}Lcn%24j8ZzqfDlA5_ZN$4uWXlya2gl!(*~fj>}u4hl6NKoA^gsFO>Tf;kn^w zL*qz2&F&h5pLj8^Y1hcU>;RnmbwQ-B7)^&p_PU8Lk5+BNrI@*>Tr}rM#ro4cUA9ImdPj7NCgOWXYa?zKXWLtY-?>T zc|T1;x??v<%rl;|jfW!)5pyr+H`yxb<*MC^%J=h>8pT{MLUmiku!Rs z3HBjH*lieQEGPF|v0uS4+0gJYB^nZjY;t?DmB#m=J{|`t;-jNW-7U_tNH_Fbv?oR8 z$K8UHI^q8)|6>LemnT%O6_+x8bOV0lZnP7hJV+jP?Dj2nn#nHW-b-VU<(agCPQHI@ zJ+@{pH#D+!(GKp*sMvCV*Lo6Tp|w0$o`%R1ySBIK%T`*B2Jlt_Y4K(Lt zMjJeyN$v{4?%GVfyq-byW-*j{|w4f>|*4%yO0 ze=A$8o4(?$hInV-r|OaxkRcKB8S6y=(|Dc zI@Z#-gV%dliO4n5$TR*P5^n78Mse%cddy#h~!TvFw3N?X_W+;6$kVJf>-R zFCWirjY@9zioq7mg~~=)HKO@#qN{O{$av~>*4`#RUhA_}ED}MAH^$zFV}!FADlcp` z*JIfjLCN-V4P`2adtX91HksfFB_`!%VzKVVw?`#+$|t29KUDxq#d=;#FVQWGt(L#n z3Kt?;6Rl!}n^ryPCT}1=s9I*tv=|*`V>ZQJTt;P&S#vjP(}%e@j*P=*v{oN_%(Y-F zfZr9HzV&J-{TzM{*L`3R6d9=WZjtu~qi9XSAJqpV{EWqA*=OBv5xFVX+vtlHOK9+6O6$Xexl)nTUjhSh3uBdacH!$* zIh+aAGjI(#uW>dle;T2rOgoL8SJrBzztA{EQ(uEu zYbYM9|IpwkvgvJ{l2|*jLpq9QJ9c3kFypIk_wA5Lq*eL{# zz|Ta_Lb*9D`LJ!LndXAsA!n|ESE2wsG5sppVQIHOM(c-)8m_mI=IohxU5HyC(wo8M zk+1uZC<>EG`|CKD!#C*QXvYx~9o%Y7FQu1dSgjxKT(`T#!eh%+)rHD4lqojs0Uma3 zVD}I^1D`>sA9w)g6xzBUuPqHq`Nz~XXA8kG@UfnTQ)$76*VaE=>o5x{kS!f|>^6-H z+MfY4RDH)2%EBnzKsydks2jo?-u&6o^xBxndQM^o6Fi7pSaQ)xoTRJx#5Y0LDgLyD;K2&G7uy*YxIi8e-YJ-O)9YkwHB#OIix*Xq9BgDn!#SkELHbLSu3uR=4l1Hv)PK)hr=?6(xlxdGp>pdB4be!aTz6*i zMb}a69y0T;op~c%_z9|T8@`EMI7xdGX|Ii|d7k$WjHPU=G|VDN*)?r{GCFyfS;{k( z0=O09B@)lDwMK0AD*A%%Nlj@j&mGyC*)C~AARA)Lhw5JWevZbK!iQ%W?-I!gqb+mY zq6^4gH6(Z={Z9b*te*}WH-e+9;p#bT$RzWj5n#i<$DQ9Kud+Zx@N9J%Pt6T^I$pzI zRqcX*j1r#^j=MFQB9)+zC$F{CTpz9B{6MhRU?U<)1TyfEDz2IUh9rox`G*gBjH44A zT?@;88f$>vC~a|ZfV|a+_fT7i6hK7}a8MuGR*AaT*|ivqi8YJjW#I4^T9ZUWQuuht zXv2++2+X|WA~?t-Ltk95L2U{V*#fPTW13mwhBJ6MD=)cuzC=q(c~lQ-B-sB&><)wn zh@sT>^$8P>>{=aF%F6=E^j`@#W}?me4tbAd$_SELXV2OLh!Vl06@b5ZTqOlm_O(K_ zh(Ag4WwwiRqqAkilfmxHG9@<4#K%a&MS7q~Gus86o-KNlh13A+mChD5hb7LF9bKy2 zAdi-JY6#Epzvk@$Sr9VGe&I@LX>`~{Z1+k{$`|ay9s#vktx1Jge@{@PkR-C2_h)dE z#(ejSB#07P>Cb>z4Bv1MZ;M@Z9b0vR_bAN%(hyf{mt}9?)G~gOaBM;(K@r5inZCxa zyE!Av)Xhlh*Dh)dU2NhBKF2hg7?PXEN}p1ps7W=mCMI><|oc zosgsR*d3m^gNnkjfZI5np27Af#*GDtgD#uT;(G88@X{KI z9;P!9+x1MadlNrb@=~~WrC!B-Y|^2j@3S6)xwH;y8+}c3ZBN5Q&d&uKGwVIV2G&EC zOG_WNvQ;}q&jEl43Y;)IDFU=CUC)!n+$Wo(4R0oY;gDqB=y0#;Z$`)AE|6QR!A0TP zj9o2}$rK-yM%rL4Pl*pYuEjlRAxkh2H%850)(Sqnvz}57C|+jwXFFi6SZNTimc}^L zhO=3U8W3HzcoU%F9F|Kp-8|5%bB&-!*;1aEIXFy3-k{NYlK$^Q5zSBV}7bctNxK#zS{~JUtI2I1@5oO z0??>@j+O%$zoEh0b|*Tb4-^@c1>#m{4z#Sh-D>yAkRb~=-a-Ln>P3n&TBuI4ZGnqG z#%LMeI_@&xR?&}Bget~1(AGl5PqYEb0aaNC+}V};<0yfQ$8fH%woL>c&12#{wa5r+ z%EW5ZsND>MP97VmJz?j8?Lr9;=ju!<5;}8e*(U0z9nPC;s^4Uq+KSfR>*6YPp*_C7 ztN_pq9}MZ3;e6Ro+XZZ7Ct7s-{xx7i7e<1v-XSmGutVUx&91Hzk6bSv_OcUtG;z-{ z;Ht)QyBZ*)we7-#f<2@M2Ixt}!5<4Z<81K?Xf&?wHISLfg*~DLJ_`ThN#Ept7nza^ z_Rg&rvF&LJ0uGHG581U#bP=XiBmEsp2HXu6B7j53-*+sMpxj9?E)X#8khq3;12Uqm z>drBKzbqLhG-1kx2G|!r6(7D+22#PT9b|DOTFDNx1I+>kk#+!USD5(TJ>f6lh!2ft zvNGOXH^~ZB!f0fzyoGoU7Zn>BX=j2(E#4{(?ir=ra$76gEDZ){^N_M#6pup@H{hE5 zRYH|=u=9)T+439+xpPkZR{^dcyB!++6*{vY4nS_5Xw2>}gCG1=!~zvV^r4d1jbH-u1 zJ_6*m*HzaY;Hx}#kP~K!W{p~5y~=c%_Ha9g$}y2aqbQ!A^|amJ>ywSv(0UI5b2+z_ zD>^bm%4VFh@5gS+zjc4S*TWq{BXs4k<(5qTb%8$@|4rkI8_x9o@nP9^s01`07}%+! zQd%3}F&fY)%LWatx9Uv1mP2OK_|{MJ7)9kNPzg$pr#5DM>~TF*rL{xY1Dw;NQX z47TL$LWvHA;ock?X}rWcke&|@7NP}>o{A{;f`$%!5fM?)+(^uTR2?TgUdR?pti4TI z7rAxs7o*Xm38yBdKVM{etU0oVjq=#jkfrv(xoS$eJh0CI{uYW2;nHwwCho9C3eM zWsvRJlhU}9euvFTLF^i#Ma4(s3&3=21_7dG;qGmsbO;rb$bpHSI$EIO0ZajwY)2ce z*NV_2Zm*#>Vpp{fM@wDX0lLHRyxDFox!j~}w>q`gyftB<>-lKe^@0qd)vU2Sc7tj} zLVnSbD9~vIbMh@Qbv&{y8&u&pR!mY?>d6}p;b$7Bd@H2>IKh{)oePcUmyG!6+hehB z2}^vAsO9N@egGLSkUaXvN8ca$;(PCpJR=kd%zp~|#aZPa{O=SxCi!uPU#2DA<8#sx zI~}o65Gw<*;fNE{rF~K@N8+UG(tcNLv;{q=PE?n6RIxF5`mxgw@9>qe1nrC^t#vYx zp3x5Hmb7n4cJt{ihhm=#75_uDQxpOh!0gj+7}F;op^>yRlJ*PY#GvQXhp8g%^CU6x zLE4Yen)%*ng#nAHlce~+6TtSQIekhSoV2GtLL2<1@{~3>IUU7#>f}Az=cFm^H>LmZ z&`$q)ROJ0UGe>l0t4{y>Xr(%RRGT_sJsh#mF**G~+Tq71Bj4xP7AtM$bLtuGa9)vC zD$+?#oYD~|<*8Fz;iNB}(h(;Uu`xknE}SH%R+3XEJ+aXfC;1;iypl!C1P#NT$S2GF zkz-;kt@Nc8F|}`q6)s?Wlo;{8m||&mSfI?*2Wg)l2a?M!1+l}*@*wZg4ktBfr{?}C z!JmfGQ&J^*vYceN)cfRKr^q6FfRfV5{}b6sF&k_yBp8@Ey<<+(4&s#8_f;Y0ewwd8 znHA0LYp~CU22ZF0mr46EX{9Lm`zdL?m7!Q+W4f$$G-R>am?6yLnU>oV4RB?KlJa zk#v+3JGGbSqx4K>eB|V)!p@#rai(?#Qzw>yjyS$INnDdFob=#2>4;;;oo#4^V@=tq zDJvu8C^NI+kUD%0K3(B?#_G;kWrO^u(MCap2HJ{shP6DU9aeO3=5$3l+N0CSe0JO? zMQWrx`;n6knVYjFo+FOKnU&#;mOH7;{3p;+WoDxwpA0qekUE{toq|{?OK8H4E}c1C)wNM{<>P){Xv34QdDeZ9LT!oG}@~9u670w$T z=!oM7e$To4r?mGO?ck`gR`^sCo%KGs9TanH(x;SV$GZ%_(x2KlxINGD9tl@sol=66 z_EfgL&t8f>+1T>b$$OOGM3qiygOlPD6;*jMy1`SBu))cY!Me0z+&D5_O?Jd5%RfQ_ zGWk3C>7&+Dz4#F&efndfJ(cKl@xQw>_`9decX~_|Skr09o!g$2=t=DCQ*r#k;}Or? zVb6V-DlV%eD$-GU>c|m0LORNe9ZtA{^`mLU61=|V#A1K{2;xP$StAi+dY&p87m`2a z4f&&DFTrnyCgB(hkS`hvm?K4GLP2qb74130g6v{h`aJTI`fBS5)bwf!BS* zlCmb3*NQwDm)hV{r^;+7+;0Hcichb>Wo%===RVR3kCYQyj=g^rLn{ca&DRrYanyIQ z^iKN4XZ)dAC%FqyD0ktAZ*|>C)4C^BS05#+g5M>ar^q5PK+yk3kb3FlJzC+U!@mC! zl;D@vG<3vqc;=(zxT@%gV@D*QjMr6fy{F7`da}ZgBZ&?#&yd!BM(p!ThWunEGt(y@ zpq0$@M$W~7%)mz&^VyF(CJ=E-GfvV|&uAw-Wyf|{soeWgXoqv_M-f7Os3eBUPLpvl z^B+R&N9CtL>%e*a=UB^O=+OQ(EDq z^fM;V+aCpfBS_tmqf`^Wv_69!!2*pOTVSA4n!63wRy>Gamz{Ufx~5Imu0=9%4j zI#nxVDbFrG@0SIQLEfLeCo`GgSa}~TtyHGirlg8h`5b@aZ8J~x<`ExMUDQv9GSyWq zM;tq@`6?Ec96k+g@C5=$`|elI9J%=2S2_I)M}B(}JNrRO@MFf^$1BCB=qF*Ipr&26 zsZF2KJ||-^*88N5iQ8y0ER1x5VC0D_z@Cvk5%N2|XZJllK_2$ctn|+yGEQkDN5RS{lLgtI%($tfV4DEE}qZZ>mb2L7)-#NoJ--wAsQaUm~ zrQTE8>3MewXFtTC>Jtc~PVGP=u=3f{tL~`c$T-VpJf#gs95uWrjdRcn$F}Evg7bv5 z5ASxKewQoo^+Z!X#V>#tURG*&nQ><93Ay4@6+Ai523W5?+g2YrNuX=UkXO9ObeR-uXpEAP{Tv7J%RSd_3I;`LdWXSfkWx9W;Hw4p?`1xiinS_fbY{2%he#c3v^XPJ8B=Ck_Cr*4?fngoooR?-m_$v~0lv~x zN2tl1pmWcjbf-T^cJg#rB-k4@aa2Q#oIgcW8%X0OQb4RU*&HiHFz(FCXolUglA~Vk zbd#KP&Otk!b9GmX9ig7i1j}zc<-0cKleELJ}0N8ufdFnoKm3qw;;oS2?%tJdI zpL~+z5hwZSqx=(Z)zCE#jyQHcWE4L8QR|!}J*RZU$;5R~oze~`6(6+1vGE~o?(%YO zekC`5I-r%Zy8_S-$L`co7xTdnC0c$SFi{PhGL3sIYa3+^#^1<`qu%>iOmool8x@v8 zKdS1Oct>Rs%KM+rh3v}n6^_NJjpEc%9`$o}Wqg*K@8raW1y-AbRyek>0ohL6=PB*< zlnow7KKqPzoG4DlYMR*TX;qroSW}^OE}** z@veK1lkZ=3xUex?Sdl;E9NOuhx%81BL_hQI4MI$P1Ps1^?I&yg@sA(Rz{fN2@eF)C z10T=8$20Kp417ETAJ4$YGw`3&4D8Fo^RW@|)L;3Z)53l{mXBxP;~DsP20osFk7wXN zeg-yV@j>t54}<=HEPcp%xKDdC+>3*Uk|a+4U!~ud1WD8XzufU($R8PF@!1%KI(~P0 z{OCUlTc(JSXo;Z}F>%mkz!EZAO}#7C(Gma=s>sEAw9qiX;C0F}eHh zwDbirzOpRnz$?BW%@CRpk+sW`SdjeDza{f9-oN9O#4p|Yr1+*J7F+5Mx&K6oc&1Mj zd5)VVB;(&nLisP=kc9Z5@`s+2Uy?3~%9S_7Scyu1cUdynn&{byqqo{nfACmb8cB zhtfZ~EM0sp^7kU$$p8A&X@=>IuKt@VXJ2ve;=L@5zwzcPmxNdH{`X!f^2W>D_b>Q< z`3BSRPhL&D?1+os)&7s4mW1@VpOu97f0U$)zwv5QlKhT8FUF;&nD{>{Nz%1zl8F4; z3Rmp@=F9%CYrn7jlJb+;Kk&a9`HRY>Ip$}4?dwbqSJi$>k>;5CY2RO#cuSAu72?&m zU;RmG=AtBJna!}&T5Kqin3i5kNaB^BY&2Rq7`Im1=vAKWx*m_Yi zSwZt!k_&xt<|n_Tq&+{lDXDYkHvcTfKX1MDRzh;-hS|(!n}T^Y_@zy3R4;!(KISDCVG zIT4d&cO+ktEN|L<^gsHo;^!s9+f!6;$DNuVldKYtA${|7X1*#3&(D4Hin&=4zx$gG zPp-&|Bk`##vAM7P+IK&}lT_JVxTZ&#SNfg@-@j53UsWv6 z;TyiB_?(oP`_|1jGCcXed~7YzaPe1f@p;xe(vp*3Ur5DRw1pIl#)w|=nO=47o8SDC zFT1Vx`TwhuDqefFw%DetzAQ3Q)0^3?dcOO*B<~iLiFjEu6R>lS5lf>3UJE?O;I4Bk zCfKOW4%y4QVHDlePA{d?ZgZj6VQVuD$7 zIVmZr-WZp@Qa+g24QA)xu&Mi=-+o0J9LSNPxV5UfmwHo;sA3$c^4#BiUFvDlwX|ok z$Fx7;`}2~;hyEy4l$kKyklpn<$T8TLzZ@;{_5#cE^;=TK9sC!rIHk(9##AKfabB0q z&5Zc=tG^`P+H4#&z2=`*BIDh*@TNNMjQ_Y~a=910wauiKT$H_rw|HeozWzH)Qc`9} zx-9?h#8cr@?j}krj(E95O_7vO<&>O=uqvN|Ct%^vNapFV?B<>H>S(olHz}5+L8=iD zGZ9B-H@|5p-`VZ5f-|301|Dxl%E&xw#AU;uPf6BlH&PLs2N`*8@sBt&w+b?s^%f&N zZ^QHRUwtwKt+YfB5qEYzD`hsjN`=Mh9SoEn%jdZ4UaEO{QBF!;tX<5f`rnyI|L!*} z%}A-@<)zx-!$NEU|cHqiyj6_5Ec3)rT_{ zrQRd+PV$;@W=9g6as)kBH^q-h`PCjQ{hPUiOv+qL3P&nFay8`b7>#LpWHzUDkoGo& ziXFFgKJeq z`zPAqQOq;*3BR#w9VR_9pS_ZIZ+!J-Q$%t~I>es;F)8gW*kdKHtFrFU^Hs@jI%4>$ zP}=%H*)$dFUy947{BQr@g&PG~gtl_x*CGSvb3J9vG4G$3(yn0rGDA7{%T@0F&obgG zFa~>towi9U9HpNc)}55NZ9NitSq|i84-MYG;jkw%?#g-dCG~LPe&KU3r$wp&%!<-2L3Nt5%ukwo?cj;h@DV+p3U7rG!G+=`&ggi z1pLRRK4O0)qFow(ZwvU_4uS@ zxPs|XTF=jW%Ym{i&i!tAuc90CGBPPEr-qyI-TrElBk5C1>4W}D72f_PRlLpBn)psc zh`$vas^VL}+}9^Fyc5h@t~d3*63ESoms~ZV6yXb6ykE`Fcz){*Df3p(^ZE^+qfcfZ zsf+5hyiA99nDEZmk-(Yy1?Xj8b0`kXY-lXKte@#YazJm=or%i`{;uGqfevku03K@JF6yy2sV=Nup_ zrx#}2-UN+y5i@g*TtXc>YUzK@FraeXhjC0k&L+fUdG!N z-`9)2Y#QzVgU?*)ud3dW@FE)#p*r*PyKlzCZFcs-nyvA0tYqk$YXs* zl5Me>j~L=*-a;#`uhw3w&3Ma?OoYo95#nf^n-cu{i%Vn@>@I%Q=JH zif#pNHX^N?jpK2>s#h;auSgQxAuy6tmS^*(Ba>%Y`opqEnmv< znus~rzM>Zuy|Z92tkexs#u zJ@#Cvc}~wMNOCLHd%K#->xO|465d=YFTFC~^C7~oNzH?zmxdo={D`i;DSC&LYP1od z-n{TPx_U$Lnt}YBURIQ(Uhp5}78@s%g+G}rutU|wzFu1pe>5)^7R%!L6>-qu_We2W zucBR7^E@w2E=r3-G=lfGlzY_N&GCllRO>d6aZb<6yH21r8)@Elyj4A~d6R*LPX>kP z#q+wQXtmq1$Vjg&1o&VNmGOh%$R`eIP_vrToAPZ$VNkEA-a@E+-V;ju^NYQ!vAIy@ zLO`BljYfKPUfO>(f43q&?aQYoyWN{(uNo-_9KT-ndkf+))urs+p!PqN@h0z&^^RYY zVDy8$_+|>$RI1=$2hl^@r9|25H8#z~%9+fq7vdGQsWsb|iMe>5LEhuBD7e5BSLuMsF**69vb^d5-HPc;`Odiy+Y zER?U#{G{HvAOl~6t=YhMHf5BGKaKsE&c$e7mfEF^ zZl32o>XDXsL+@QcHlzlg%`dTW2l8S^FRRyJX6tHOx!c}Lt`@u!{d7MqHRQL8-_Z?4 zukh+Z)PkvlLA;YddQR^ti%mTzv&L}OZOK3-R}TgZ*jo&!=QkT(H86mu<@bOg_Q+cm z6q3{z|8%|=dOM>RXKX|7&1|Z|+01QL7n5%$Zzo~ima5PwT+jK%_vg#AN?v?kG9$Is zw78_30ey6FHSKvEzf#m2stYfzNb@d`V=@0zW2QhoA1VjDMd_cu18I46to#Oglm?!&n=Qq)HvWWZY}p0SdT!Ua+^g4gYi`pL zGy1@v;3kfoiiIm-tex0yCFO)N0r6)@+6X#Qym%$4cP}c-dij#h#%!iq;@4B-RYS?_ z4j+wD9S=rKT5rzWQT5EMKDn5RiSOtQpYA9JWwEn65^sacV}WYm)we;-=*=&~vAO5y zvT$2PbnJG$j(7{C!+I`ebmIkmw6GWxp8zLE`CxZeYkzMP%s(f7Td&TR*z)tMhUax# zvNlJoct4=$FJCsjom5r)A{*C*$}gZ~D=%E5qyS_qMI6t?SCxKXIIlatfb7$2aM{eF zrDycj^leqVz|%=u%$xDN9E+E|4Lv<4{Y6QFv#r@k+iL~P%ezUft;p!7ug%AvmqPQ&i%LN+p1T&C zQPO(*xzvy+enHPiyt&(kxQgbkURkY*$0^&+_%Y6Ov^*Mcr~(bea4 zWA0}9xVLHEamRHvcFyy{YoLgRw-)F}t_{yB{dct|(C}i!6xXjN11$Nvcd%+H=BoRwilL9@ydx!#iPv3hMPlN$`N6Eq@W>8J z>GCAr!m! zCSnG1k<^$eHI=)vh_EZ$flLrth2jqP;iKnL4UyG5=QqoDC;Bi<-*-L#g7lU7#$2|s znHInGPRoNOT+?fF2Wj!=dT~~~`GZcXCcJw2gel3s24On9Sn$By=r(TcVeO5 zIUR_RpN&+*g-<@pq~zO;gIuJds7UwN)z(7Tu)GeQ6GGt1Gq2PK(92-EpH<&^c(2!(v9G-A|rC$cVF`GsFjE*97nr)aA= zuMe%PtW@=!`KZA(=4R0WC_CH{#^zJ0gG;Qd5BS zcWX#|1cZ+V8OWh_aCQ9lKo{~s8II}uP~kk}^ykIR`y`BVmBYATx; zNFJO&AP*_=gH~!R{!nj)H#@0GAUD67_gZ%{Uh`2pkpBc~^KM1Y2V723^-f4HUS2d= z($fb8z0pls`f%U9;V3jZQ97@IKl<{Vw*K0~kfk;2?EUxo@|%SH6`Cs&(* z?B-?hMfRVP(OGLbvKR{$uh3jv^6>Lsn1tth+TXZ38A>JC z^?7hCRdjX!Fq0~yDJG^Cb9Kd66eHXX9(24|nUOvd|58D9oejG&Jvpo7-fG>c=>ylT zG}OnJc6-?3*QLhhLD!TSphY9G+m7zUw4}GaT4CQc=e-SuJ#|r^_+IHs z$K{_Y?xT)+;@5)>I>y6Z;;s44i%QuA50ePpL|AGkqW1zQzfVcc-S%!p{Hi{A8J5n7 zmsblB#N1)-b^)oSH^Y}RdP^%cr1|;bjJFdQ(A#OV`nH8R8_7hfce`A?c;0J@x4fN5 zo|Bv49es zJa?`894cZ#Pd?{O{*}DsI&KWfT$2OQ{_+4<><(q8BfTKk{|F zNv(R*&@-Q~^EX>)yT!Z+OLC;MnAH2c&||)LJ|Y6SaAYif^B?0&&Fwn6P{dE_qk+FIpdn*C?oIalQ_SvV&&%HEKknW-Ug;alWunT~hxiiokS3`{pXec&bOv*(H zw_AF1A!SHsg6$jV$~(q2Yo=6$oqwOt(7)$`K9aBX#NA1%p|x-iVcfo-uEiUHF;ws(?{(ezASfUhOODwXTGzYNrDYMcA)4_42G{&|D!8+l*#(S-CyF zYQ-kkRP}V}}PwS4JtvyoFf~u;QXZ}>+Bmm54mh>mw7jl8iwT86X zR{p-XgR0!ki^ssDMdH7Nr*+p=~?_q7|>L+YN!xB^i?QqDMW8M%$Z|Hp$ z!za>Yx~;`s^R}T+E@35URc~JWQLyl10VC**(SS0qv~<

_)}`Adqtbp_2)(Ry6f{ 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 z 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 Hx!>`>WOu+G literal 0 HcmV?d00001 diff --git a/eta_prediction/feature_engineering/README.md b/eta_prediction/feature_engineering/README.md new file mode 100644 index 0000000..99df0cd --- /dev/null +++ b/eta_prediction/feature_engineering/README.md @@ -0,0 +1,131 @@ + +# Feature Engineering Module + +Transforms GTFS Schedule + Realtime telemetry into model-ready ETA features. The package is structured so every feature family (temporal, spatial, weather) can be invoked on its own or orchestrated via the dataset builder. + +--- + +## 1. Architecture & Data Flow + +1. **Ingest** – Pull raw `VehiclePosition`, `Trip`, `StopTime`, and `Stop` records from the Django ORM that fronts the GTFS replicas (`dataset_builder.py`). +2. **Trip Stitching** – Join telemetry rows with trip metadata (route, direction, headsign) and ordered stop sequences so every position knows which stops remain. +3. **Target Construction** – For each VehiclePosition, locate up to `max_stops_ahead` downstream stops, estimate distance with a haversine helper, and mine subsequent VehiclePositions to timestamp the actual arrival that forms the regression target. +4. **Label Validation** – Compute `time_to_arrival_seconds`, drop negative or >2h horizons, and standardize timestamps. +5. **Feature Enrichment** – Attach temporal, spatial, and weather tensors. Each step is isolated so failures yield NaNs without aborting the run. +6. **Finalize** – Select the canonical column set, fill missing headers with NaNs, and return a tidy DataFrame ready for persistence. `save_dataset` writes Snappy-compressed Parquet when needed. + +``` +VehiclePosition ➜ Trip metadata ➜ Stops in sequence ➜ Distance/target labeling + ➜ {Temporal | Spatial | Weather} features ➜ Training frame +``` + +--- + +## 2. Dataset Builder (`dataset_builder.py`) + +**Signature:** +`build_vp_training_dataset(route_ids=None, start_date=None, end_date=None, distance_threshold=50.0, max_stops_ahead=5, attach_weather=True, tz_for_temporal="America/Costa_Rica", pg_conn=None) -> pd.DataFrame` + +**Inputs** + +- `route_ids` – subset of GTFS routes; omit for all. +- `start_date` / `end_date` – UTC datetimes bounding telemetry ingestion. +- `distance_threshold` – meters for β€œarrived” when mining actual arrival. +- `max_stops_ahead` – number of downstream stops to label per VehiclePosition. +- `attach_weather` – fetch Open‑Meteo hourly observations per VP position. +- `tz_for_temporal` – timezone used by temporal bins/peaks. +- `pg_conn` – optional psycopg connection used for shape loading; defaults to the Django connection when omitted. + +**Outputs** + +Each row represents a `(VehiclePosition, downstream stop)` pair with: + +| Category | Columns | +| --- | --- | +| Identity | `trip_id`, `route_id`, `vehicle_id`, `stop_id`, `stop_sequence` | +| Telemetry | `vp_ts`, `vp_lat`, `vp_lon`, `vp_bearing`, `vp_speed` | +| Geometry | `stop_lat`, `stop_lon`, `distance_to_stop`, `progress_on_segment`, `progress_ratio` | +| Targets | `actual_arrival`, `time_to_arrival_seconds` | +| Temporal | `hour`, `day_of_week`, `is_weekend`, `is_holiday`, `is_peak_hour` | +| Weather | `temperature_c`, `precipitation_mm`, `wind_speed_kmh` | + +`progress_ratio` approximates how far along the route the vehicle is by combining the ordinal of the closest stop with its progress within the current stop-to-stop segment. + +**Helper routines** + +- `haversine_distance` computes great-circle distances using a 6371 km Earth radius. +- `find_actual_arrival_time` scans future VehiclePositions for the first observation within `distance_threshold` meters (falls back to closest approach within 200 m). + +**Usage Example** + +```python +from datetime import datetime, timezone +from feature_engineering.dataset_builder import build_vp_training_dataset, save_dataset + +df = build_vp_training_dataset( + route_ids=["Green-B"], + start_date=datetime(2023, 10, 1, tzinfo=timezone.utc), + end_date=datetime(2023, 10, 2, tzinfo=timezone.utc), + max_stops_ahead=3, +) +save_dataset(df, "datasets/green_line_oct01.parquet") +``` + +Run inside the Django project context so ORM models resolve and DB settings are loaded. + +--- + +## 3. Temporal Features (`temporal.py`) + +`extract_temporal_features(timestamp, tz="America/New_York", region="US_MA") -> Dict[str, object>` + +- Naive timestamps are assumed UTC, converted to the requested tz via `zoneinfo`. +- Peak-hour logic (weekdays 07–09 & 16–19) complements coarse bins (`_tod_bin`) defined as morning/midday/afternoon/evening. +- Holiday detection relies on `holidays` with regional fallbacks; absence of the package silently downgrades to `False`. + +Unit tests validate timezone handling, binning, weekend overrides, and holiday stubs (`tests/test_temporal.py`). + +--- + +## 4. Spatial Utilities (`spatial.py`) + +- `calculate_distance_features(vehicle_position, stop, next_stop)` returns distances from vehicle to current/next stop, bearing alignment, and a normalized `progress_on_segment` proxy. +- `get_route_features(route_id, conn=None, stops_in_order=None)` aggregates route length and average stop spacing either via Postgres (`sch_pipeline_routestop`/`sch_pipeline_stop`) or an in-memory stop list. +- `distance_postgis_m` exposes `ST_DistanceSphere` for higher-precision metrics when PostGIS is available. + +Use these helpers when deriving spatial covariates outside the dataset builder. + +--- + +## 5. Weather Adapter (`weather.py`) + +`fetch_weather(lat, lon, timestamp) -> dict` pulls hourly Open‑Meteo aggregates aligned to the VehiclePosition timestamp. + +- Timestamps are normalized to the hour in UTC, forming the cache key `weather:{lat}:{lon}:{iso_hour}` stored in the configured Django cache for 1 hour. +- Only the relevant hour is requested via `start`/`end` query params to minimize payloads; API timeouts default to 8 seconds. +- Missing API data or coverage gaps yield a dict of `None`s, keeping the dataset builder resilient. + +--- + +## 6. Dependencies & Environment + +- **Python stack:** pandas, numpy, psycopg, requests, django, holidays (optional). +- **Database:** Postgres schemas `rt_pipeline_*` and `sch_pipeline_*` accessed either through the Django ORM or raw psycopg connections. Ensure indexes on `VehiclePosition(ts, route_id)` and `TripUpdate(route_id, stop_sequence, ts)` for performant scans. +- **Caching:** Uses the configured Django cache backend for weather responses. +- **Timezone & locale:** Default tz is `America/Costa_Rica` in the dataset builder (override via `tz_for_temporal`), but temporal helpers accept any IANA tz string. + +--- + +## 7. Testing & Validation + +Automated tests cover: +- Temporal calculations (`tests/test_temporal.py`) +- Spatial distances/progress (`tests/test_spatial.py`) +- Weather fetching/memoization (`tests/test_weather.py`) + +To perform tests, from the `eta_prediction` directory, run: +```bash +$ uv run pytest feature_engineering +``` +--- + diff --git a/eta_prediction/feature_engineering/__init__.py b/eta_prediction/feature_engineering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eta_prediction/feature_engineering/dataset_builder.py b/eta_prediction/feature_engineering/dataset_builder.py new file mode 100644 index 0000000..dc1efd5 --- /dev/null +++ b/eta_prediction/feature_engineering/dataset_builder.py @@ -0,0 +1,562 @@ +from __future__ import annotations + +from datetime import datetime, date, time, timedelta +from typing import Optional, List, Any, Dict +import numpy as np +import pandas as pd +from math import radians, cos, sin, asin, sqrt +from pathlib import Path + +from django.db import connection as django_connection +from django.db.models import F, Q +from sch_pipeline.models import StopTime, Stop, Trip +from rt_pipeline.models import VehiclePosition +from feature_engineering.temporal import extract_temporal_features +from feature_engineering.spatial import calculate_distance_features_with_shape, load_shape_for_trip +from feature_engineering.weather import fetch_weather + + +def haversine_distance(lat1, lon1, lat2, lon2): + """ + Calculate the great circle distance in meters between two points + on the earth (specified in decimal degrees) + """ + # Convert decimal degrees to radians + lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) + + # Haversine formula + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 + c = 2 * asin(sqrt(a)) + r = 6371000 # Radius of earth in meters + return c * r + + +def find_actual_arrival_time(vp_df_for_trip, stop_lat, stop_lon, distance_threshold=50): + """ + Find the ts when a vehicle arrived at a stop. + + Strategy: + 1. Calculate distance from each VP to the stop + 2. Find the first VP within distance_threshold meters + 3. Return its ts as arrival time + + Args: + vp_df_for_trip: DataFrame of VehiclePositions for a specific trip, sorted by ts + stop_lat: Stop lat + stop_lon: Stop lon + distance_threshold: Distance in meters to consider "arrived" (default 50m) + + Returns: + datetime or None if vehicle never arrived + """ + if vp_df_for_trip.empty: + return None + + # Calculate distances to stop + distances = vp_df_for_trip.apply( + lambda row: haversine_distance(row['lat'], row['lon'], stop_lat, stop_lon), + axis=1 + ) + + # Find first position within threshold + arrived_mask = distances <= distance_threshold + if arrived_mask.any(): + first_arrival_idx = arrived_mask.idxmax() # First True index + return vp_df_for_trip.loc[first_arrival_idx, 'ts'] + + # Alternative: If never within threshold, find closest approach + closest_idx = distances.idxmin() + min_distance = distances.min() + + # Only use closest approach if reasonably close (e.g., within 200m) + if min_distance <= 200: + return vp_df_for_trip.loc[closest_idx, 'ts'] + + return None + + +def build_vp_training_dataset( + route_ids: Optional[List[str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + distance_threshold: float = 50.0, + max_stops_ahead: int = 5, + attach_weather: bool = True, + use_shapes: bool = True, + tz_for_temporal: str = "America/New_York", + pg_conn: Optional[Any] = None, +) -> pd.DataFrame: + """ + Build training dataset from VehiclePosition data only. + + For each VehiclePosition: + - Identify current trip and route + - Find remaining stops in trip + - Calculate distance to each remaining stop (up to max_stops_ahead) + - Find actual arrival times from subsequent VehiclePositions + - Extract temporal, operational, and weather features + + Args: + route_ids: List of route IDs to include (None = all routes) + start_date: Start of date range + end_date: End of date range + distance_threshold: Distance in meters to consider "arrived at stop" + max_stops_ahead: Maximum number of future stops to include per VP + attach_weather: Whether to fetch weather data + use_shapes: Whether to use GTFS shapes for accurate progress calculation + tz_for_temporal: Timezone for temporal features + pg_conn: PostgreSQL connection for shape loading (defaults to Django connection) + + Returns: + DataFrame with columns: + - trip_id, route_id, vehicle_id + - vp_ts, vp_lat, vp_lon + - stop_id, stop_sequence, stop_lat, stop_lon + - distance_to_stop (meters), progress_on_segment, progress_ratio + - scheduled_arrival, actual_arrival, delay_seconds + - temporal features (hour, day_of_week, etc.) + - operational features (headway, congestion proxies) + - weather features (temperature, precipitation, wind_speed) + """ + + print("=" * 70) + print("VP-BASED DATASET BUILDER") + if use_shapes: + print(" Shape-informed progress: ENABLED") + print("=" * 70) + + # Setup database connection for shape loading + if use_shapes and pg_conn is None: + try: + django_connection.ensure_connection() + pg_conn = django_connection.connection + print(" Using Django database connection for shape loading") + except Exception as exc: + print(f" WARNING: Could not establish DB connection ({exc})") + print(" Shape features will be disabled") + use_shapes = False + + # ============================================================ + # STEP 1: Fetch VehiclePositions + # ============================================================ + print("\nStep 1: Fetching VehiclePositions...") + + vp_qs = VehiclePosition.objects.exclude( + Q(trip_id__isnull=True) | + Q(lat__isnull=True) | + Q(lon__isnull=True) + ) + + if start_date: + print(f" Filtering start_date >= {start_date}") + vp_qs = vp_qs.filter(ts__gte=start_date) + + if end_date: + print(f" Filtering end_date < {end_date}") + vp_qs = vp_qs.filter(ts__lt=end_date) + + # Filter by route if specified + if route_ids: + print(f" Filtering for routes: {route_ids}") + trip_ids_for_routes = list( + Trip.objects.filter(route_id__in=route_ids) + .values_list("trip_id", flat=True) + ) + vp_qs = vp_qs.filter(trip_id__in=trip_ids_for_routes) + + print(f" Fetching VehiclePosition records...") + vp_data = vp_qs.values( + 'trip_id', + 'vehicle_id', + 'ts', + 'lat', + 'lon', + 'bearing', + 'speed', + ).order_by('trip_id', 'ts') + + vp_df = pd.DataFrame.from_records(vp_data) + print(f" Retrieved {len(vp_df):,} VehiclePosition records") + + if vp_df.empty: + print("\nWARNING: No VehiclePosition data found!") + return pd.DataFrame() + + # ============================================================ + # STEP 2: Get trip metadata + # ============================================================ + print("\nStep 2: Getting trip metadata...") + + trip_ids = vp_df['trip_id'].unique() + trips_data = Trip.objects.filter(trip_id__in=trip_ids).values( + 'trip_id', 'route_id', 'direction_id', 'trip_headsign', 'service_id' + ) + trips_df = pd.DataFrame.from_records(trips_data) + + vp_df = vp_df.merge(trips_df, on='trip_id', how='left') + print(f" Added route info for {len(vp_df):,} records") + + # Drop VPs without route info + initial_count = len(vp_df) + vp_df = vp_df.dropna(subset=['route_id']) + print(f" Dropped {initial_count - len(vp_df):,} VPs without route info") + + # ============================================================ + # STEP 2b: Load shapes for trips (if enabled) + # ============================================================ + shape_cache: Dict[str, Any] = {} + + if use_shapes: + print("\nStep 2b: Loading GTFS shapes for trips...") + unique_trip_ids = vp_df['trip_id'].unique() + loaded_count = 0 + failed_count = 0 + + for trip_id in unique_trip_ids: + try: + shape = load_shape_for_trip(trip_id, pg_conn) + if shape is not None: + shape_cache[trip_id] = shape + loaded_count += 1 + except Exception as exc: + # Silently skip trips without shapes + failed_count += 1 + continue + + print(f" Loaded shapes for {loaded_count:,} trips") + if failed_count > 0: + print(f" Skipped {failed_count:,} trips without shapes") + + # ============================================================ + # STEP 3: Get stop sequences for each trip + # ============================================================ + print("\nStep 3: Loading stop sequences for trips...") + + stoptimes_data = StopTime.objects.filter( + trip_id__in=trip_ids + ).values( + 'trip_id', + 'stop_sequence', + 'stop_id', + 'arrival_time', + ).order_by('trip_id', 'stop_sequence') + + st_df = pd.DataFrame.from_records(stoptimes_data) + print(f" Retrieved {len(st_df):,} StopTime records") + + if st_df.empty: + print("\nWARNING: No StopTime data found!") + return pd.DataFrame() + + # Get stop coordinates + stop_ids = st_df['stop_id'].unique() + # remove any NaN / None values and coerce to STRING + stop_ids = [str(s) for s in stop_ids if pd.notna(s)] + + stops_data = Stop.objects.filter(stop_id__in=stop_ids).values( + 'stop_id', 'stop_lat', 'stop_lon', 'stop_name' + ) + stops_df = pd.DataFrame.from_records(stops_data) + + st_df = st_df.merge(stops_df, on='stop_id', how='left') + print(f" Added coordinates for {st_df['stop_lat'].notna().sum():,} stops") + + # ============================================================ + # STEP 4: For each VP, find remaining stops and distances + # ============================================================ + print(f"\nStep 4: Calculating distances to remaining stops (max {max_stops_ahead})...") + + training_rows = [] + total_vps = len(vp_df) + + # Group VPs by trip for efficient processing + vp_grouped = vp_df.groupby('trip_id') + st_grouped = st_df.groupby('trip_id') + + processed_trips = 0 + for trip_id, trip_vps in vp_grouped: + processed_trips += 1 + if processed_trips % 100 == 0: + print(f" Processing trip {processed_trips}/{len(vp_grouped)} ({len(training_rows):,} rows generated)") + + # Get stop sequence for this trip + if trip_id not in st_grouped.groups: + continue + + trip_stops = ( + st_grouped.get_group(trip_id) + .sort_values('stop_sequence') + .reset_index(drop=True) + ) + trip_stops['stop_order'] = trip_stops.index + trip_stops['next_stop_id'] = trip_stops['stop_id'].shift(-1) + trip_stops['next_stop_lat'] = trip_stops['stop_lat'].shift(-1) + trip_stops['next_stop_lon'] = trip_stops['stop_lon'].shift(-1) + trip_total_segments = max(len(trip_stops) - 1, 1) + + # Get shape for this trip (if available) + trip_shape = shape_cache.get(trip_id) + + # For each VP in this trip + for vp_idx, vp_row in trip_vps.iterrows(): + vp_lat = vp_row['lat'] + vp_lon = vp_row['lon'] + vp_ts = vp_row['ts'] + vp_position = { + 'lat': float(vp_lat), + 'lon': float(vp_lon), + 'bearing': vp_row.get('bearing'), + } + + # Find which stops are ahead of this VP + # Strategy: Calculate distance to all stops, take the closest N + distances_to_stops = trip_stops.apply( + lambda stop: haversine_distance(vp_lat, vp_lon, stop['stop_lat'], stop['stop_lon']), + axis=1 + ) + + trip_stops_with_dist = trip_stops.copy() + trip_stops_with_dist['distance_to_stop'] = distances_to_stops + + # Sort by stop_sequence and take upcoming stops + # A stop is "upcoming" if it hasn't been passed yet + # Simple heuristic: stops that are ahead in sequence from the closest stop + closest_stop_idx = distances_to_stops.idxmin() + closest_stop_seq = trip_stops.loc[closest_stop_idx, 'stop_sequence'] + closest_stop_order = trip_stops.loc[closest_stop_idx, 'stop_order'] + + # Get stops with sequence >= closest (upcoming stops) + upcoming_stops = trip_stops_with_dist[ + trip_stops_with_dist['stop_sequence'] >= closest_stop_seq + ].head(max_stops_ahead) + + # For each upcoming stop, find actual arrival time + for stop_idx, stop_row in upcoming_stops.iterrows(): + # Find actual arrival from future VPs + future_vps = trip_vps[trip_vps['ts'] > vp_ts] + + stop_payload = { + 'stop_id': stop_row['stop_id'], + 'lat': stop_row['stop_lat'], + 'lon': stop_row['stop_lon'], + 'stop_order': stop_row.get('stop_order'), + 'total_segments': trip_total_segments, + } + next_stop_payload = None + if pd.notna(stop_row['next_stop_id']): + next_stop_payload = { + 'stop_id': stop_row['next_stop_id'], + 'lat': stop_row['next_stop_lat'], + 'lon': stop_row['next_stop_lon'], + } + + # Calculate spatial features (with shape if available) + spatial_feats = calculate_distance_features_with_shape( + vp_position, + stop_payload, + next_stop_payload, + shape=trip_shape if use_shapes else None, + vehicle_stop_order=int(closest_stop_order) if pd.notna(closest_stop_order) else None, + total_segments=trip_total_segments + ) + + actual_arrival = find_actual_arrival_time( + future_vps, + stop_row['stop_lat'], + stop_row['stop_lon'], + distance_threshold=distance_threshold + ) + + # Only include if we found an actual arrival + if actual_arrival is not None: + row_data = { + 'trip_id': trip_id, + 'route_id': vp_row['route_id'], + 'vehicle_id': vp_row['vehicle_id'], + 'vp_ts': vp_ts, + 'vp_lat': vp_lat, + 'vp_lon': vp_lon, + 'vp_bearing': vp_row.get('bearing'), + 'vp_speed': vp_row.get('speed'), + 'stop_id': stop_row['stop_id'], + 'stop_sequence': stop_row['stop_sequence'], + 'stop_lat': stop_row['stop_lat'], + 'stop_lon': stop_row['stop_lon'], + 'progress_on_segment': spatial_feats.get('progress_on_segment'), + 'progress_ratio': spatial_feats.get('progress_ratio'), + 'distance_to_stop': spatial_feats.get('distance_to_stop'), + 'scheduled_arrival': stop_row['arrival_time'], + 'actual_arrival': actual_arrival, + } + + # Add shape-specific features if available + if use_shapes and trip_shape is not None: + row_data.update({ + 'distance_to_stop': spatial_feats.get('shape_distance_to_stop'), + 'cross_track_error': spatial_feats.get('cross_track_error'), + }) + + training_rows.append(row_data) + + print(f" Generated {len(training_rows):,} training samples") + + if not training_rows: + print("\nWARNING: No training rows generated!") + return pd.DataFrame() + + df = pd.DataFrame(training_rows) + + # ============================================================ + # STEP 5: Compute time_to_arrival_seconds + # ============================================================ + print("\nStep 5: Computing time_to_arrival_seconds...") + + # Convert timestamps to datetime + df['vp_ts'] = pd.to_datetime(df['vp_ts'], utc=True) + df['actual_arrival'] = pd.to_datetime(df['actual_arrival'], utc=True) + + # Compute time to arrival (prediction target) + df['time_to_arrival_seconds'] = ( + df['actual_arrival'] - df['vp_ts'] + ).dt.total_seconds() + + # Filter out negative or unrealistic values + initial_count = len(df) + df = df[ + (df['time_to_arrival_seconds'] >= 0) & + (df['time_to_arrival_seconds'] <= 7200) # Max 2 hours ahead + ] + print(f" Dropped {initial_count - len(df):,} rows with invalid time_to_arrival") + print(f" Remaining: {len(df):,} rows") + + # ============================================================ + # STEP 6: Temporal features + # ============================================================ + print("\nStep 6: Extracting temporal features...") + + temporal_data = [] + for idx, ts in enumerate(df['vp_ts']): + if idx % 10000 == 0 and idx > 0: + print(f" Processed {idx:,}/{len(df):,} temporal features") + + try: + feats = extract_temporal_features(ts, tz=tz_for_temporal, region="US_MA") + temporal_data.append(feats) + except Exception: + temporal_data.append({}) + + tf = pd.DataFrame(temporal_data) + keep_temporal = ["hour", "day_of_week", "is_weekend", "is_holiday", "is_peak_hour"] + for k in keep_temporal: + if k in tf.columns: + df[k] = tf[k].values + else: + df[k] = np.nan + + # ============================================================ + # STEP 7: Operational features + # ============================================================ + # Commented out for now - uncomment if you want operational features + # print("\nStep 7: Computing operational features...") + # ... (operational feature code) + + # Use VP speed directly if available + if 'vp_speed' in df.columns: + df['current_speed_kmh'] = df['vp_speed'] * 3.6 # m/s to km/h + else: + df['current_speed_kmh'] = np.nan + + # ============================================================ + # STEP 8: Weather features + # ============================================================ + def _get_weather_for_row(row): + """Wraps fetch_weather with error handling for use in df.apply.""" + try: + w = fetch_weather( + float(row['vp_lat']), + float(row['vp_lon']), + row['vp_ts'].to_pydatetime() + ) + return w or {} + except Exception: + return {} + + if attach_weather: + print("\nStep 8: Fetching weather data...") + + weather_series = df.apply(_get_weather_for_row, axis=1) + wx_df = pd.json_normalize(weather_series) + + target_keys = ["temperature_c", "precipitation_mm", "wind_speed_kmh"] + for k in target_keys: + if k in wx_df.columns: + df[k] = wx_df[k].values + else: + df[k] = np.nan + + print(f" Finished fetching weather for {len(df):,} rows.") + else: + df["temperature_c"] = np.nan + df["precipitation_mm"] = np.nan + df["wind_speed_kmh"] = np.nan + + # ============================================================ + # STEP 9: Final column selection + # ============================================================ + print("\nStep 9: Preparing final dataset...") + + wanted_cols = [ + "trip_id", "route_id", "vehicle_id", "stop_id", "stop_sequence", + "vp_ts", "vp_lat", "vp_lon", "vp_bearing", + "stop_lat", "stop_lon", + "distance_to_stop", "progress_on_segment", "progress_ratio", + "actual_arrival", "time_to_arrival_seconds", + "hour", "day_of_week", "is_weekend", "is_holiday", "is_peak_hour", + "current_speed_kmh", + "temperature_c", "precipitation_mm", "wind_speed_kmh", + ] + + # Add shape features if they were computed + # if use_shapes: + # wanted_cols.extend([ + # "shape_progress", + # "shape_distance_to_stop", + # "cross_track_error", + # ]) + + for c in wanted_cols: + if c not in df.columns: + df[c] = np.nan + + result = df[wanted_cols].reset_index(drop=True) + + print("\n" + "=" * 70) + print(f"βœ“ Final dataset: {len(result):,} rows Γ— {len(wanted_cols)} columns") + print(f" Unique trips: {result['trip_id'].nunique():,}") + print(f" Unique routes: {result['route_id'].nunique():,}") + print(f" Unique stops: {result['stop_id'].nunique():,}") + print(f" Avg time_to_arrival: {result['time_to_arrival_seconds'].mean():.1f}s ({result['time_to_arrival_seconds'].mean()/60:.1f} min)") + + # if use_shapes: + # shape_coverage = result['shape_progress'].notna().sum() / len(result) * 100 + # print(f" Shape coverage: {shape_coverage}%") + # if shape_coverage > 0: + # print(f" Avg cross-track error: {result['cross_track_error'].mean():.1f}m") + + print("=" * 70) + + return result + + +def save_dataset(df: pd.DataFrame, output_path: str): + """Save to parquet with compression, creating directories if needed.""" + output_path = Path(output_path) + + # Create parent directory if it doesn't exist + output_path.parent.mkdir(parents=True, exist_ok=True) + + print(f"\nSaving to {output_path}...") + df.to_parquet(output_path, compression="snappy", index=False) + print(f"βœ“ Saved {len(df):,} rows") diff --git a/eta_prediction/feature_engineering/pyproject.toml b/eta_prediction/feature_engineering/pyproject.toml new file mode 100644 index 0000000..8e1026d --- /dev/null +++ b/eta_prediction/feature_engineering/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "feature_engineering" +version = "0.1.0" +description = "Feature engineering utilities for GTFS realtime and schedule data (part of gtfs-django repository)" +readme = "README.md" +requires-python = ">=3.10" +authors = [ + { name = "maintainer", email = "maintainer@example.com" } +] +license = { text = "MIT" } + +dependencies = [ + "numpy>=1.26,<2", + "pandas>=2.2,<3", + "scikit-learn>=1.3,<2", + "scipy>=1.11,<2", + "joblib>=1.3,<2", + "tqdm>=4.66,<5", + "pyproj>=3.6,<4", + "shapely>=2.1,<3", + "geopandas>=0.14,<1", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4,<8", + "black>=24.3,<25", + "isort>=5.13,<6", + "mypy>=1.11,<2", + "pre-commit>=3.4,<4", +] + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/eta_prediction/feature_engineering/spatial.py b/eta_prediction/feature_engineering/spatial.py new file mode 100644 index 0000000..1149ae2 --- /dev/null +++ b/eta_prediction/feature_engineering/spatial.py @@ -0,0 +1,372 @@ +# feature_engineering/spatial.py - Shape-informed progress extension +from __future__ import annotations +import math +from typing import Dict, List, Tuple, Optional + +EARTH_RADIUS_M = 6_371_000.0 + +def _deg2rad(x: float) -> float: + return x * math.pi / 180.0 + +def _haversine_m(lat1: float, lon1: float, lat2: float, lon2: float) -> float: + """Great-circle distance in meters.""" + Ο†1, Ο†2 = _deg2rad(lat1), _deg2rad(lat2) + dΟ† = Ο†2 - Ο†1 + dΞ» = _deg2rad(lon2 - lon1) + a = math.sin(dΟ† / 2) ** 2 + math.cos(Ο†1) * math.cos(Ο†2) * math.sin(dΞ» / 2) ** 2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + return EARTH_RADIUS_M * c + + +class ShapePolyline: + """ + Represents a route shape as an ordered sequence of (lat, lon) points. + Provides methods to project vehicle positions onto the polyline and compute + accurate progress along the route. + """ + + def __init__(self, points: List[Tuple[float, float]]): + """ + Args: + points: List of (lat, lon) tuples in route order + """ + if len(points) < 2: + raise ValueError("Shape must have at least 2 points") + + self.points = points + self._segment_lengths = self._compute_segment_lengths() + self._cumulative_distances = self._compute_cumulative_distances() + self.total_length = self._cumulative_distances[-1] + + def _compute_segment_lengths(self) -> List[float]: + """Compute distance for each segment between consecutive points.""" + lengths = [] + for i in range(len(self.points) - 1): + lat1, lon1 = self.points[i] + lat2, lon2 = self.points[i + 1] + lengths.append(_haversine_m(lat1, lon1, lat2, lon2)) + return lengths + + def _compute_cumulative_distances(self) -> List[float]: + """Compute cumulative distance from start to each point.""" + cumulative = [0.0] + for length in self._segment_lengths: + cumulative.append(cumulative[-1] + length) + return cumulative + + def project_point(self, lat: float, lon: float) -> Dict: + """ + Project a point onto the polyline, finding the closest position. + + Args: + lat, lon: Point to project + + Returns: + { + 'distance_along_shape': meters from shape start, + 'cross_track_distance': perpendicular distance from shape (meters), + 'closest_segment_idx': index of nearest segment, + 'progress': normalized progress [0, 1] + } + """ + min_dist = float('inf') + best_segment_idx = 0 + best_projection_dist = 0.0 + + # Check each segment + for i in range(len(self.points) - 1): + lat1, lon1 = self.points[i] + lat2, lon2 = self.points[i + 1] + + # Project point onto this segment + proj_info = self._project_onto_segment( + lat, lon, lat1, lon1, lat2, lon2 + ) + + if proj_info['distance'] < min_dist: + min_dist = proj_info['distance'] + best_segment_idx = i + best_projection_dist = proj_info['distance_along_segment'] + + # Calculate total distance along shape to projection point + distance_along_shape = ( + self._cumulative_distances[best_segment_idx] + best_projection_dist + ) + + # Normalized progress + progress = distance_along_shape / self.total_length if self.total_length > 0 else 0.0 + + return { + 'distance_along_shape': distance_along_shape, + 'cross_track_distance': min_dist, + 'closest_segment_idx': best_segment_idx, + 'progress': min(1.0, max(0.0, progress)) + } + + def _project_onto_segment( + self, + lat: float, lon: float, + lat1: float, lon1: float, + lat2: float, lon2: float + ) -> Dict: + """ + Project point onto a single segment, finding perpendicular distance + and position along segment. + + Uses simplified planar approximation (accurate for segments < 10km). + """ + # Convert to approximate planar coordinates (meters from segment start) + # This is accurate enough for typical transit segment lengths + avg_lat = (lat1 + lat2) / 2 + meters_per_deg_lat = 111320.0 + meters_per_deg_lon = 111320.0 * math.cos(_deg2rad(avg_lat)) + + # Segment vector in meters + seg_x = (lon2 - lon1) * meters_per_deg_lon + seg_y = (lat2 - lat1) * meters_per_deg_lat + seg_length_sq = seg_x**2 + seg_y**2 + + if seg_length_sq < 1e-6: # Degenerate segment + dist = _haversine_m(lat, lon, lat1, lon1) + return {'distance': dist, 'distance_along_segment': 0.0} + + # Vector from segment start to point + dx = (lon - lon1) * meters_per_deg_lon + dy = (lat - lat1) * meters_per_deg_lat + + # Project onto segment: dot product / length^2 + t = (dx * seg_x + dy * seg_y) / seg_length_sq + t = max(0.0, min(1.0, t)) # Clamp to segment + + # Closest point on segment + proj_x = lon1 + t * (lon2 - lon1) + proj_y = lat1 + t * (lat2 - lat1) + + # Distance from point to projection + dist = _haversine_m(lat, lon, proj_y, proj_x) + + # Distance along segment to projection + seg_length = math.sqrt(seg_length_sq) + distance_along_segment = t * seg_length + + return { + 'distance': dist, + 'distance_along_segment': distance_along_segment + } + + def get_distance_between_stops( + self, + stop1_lat: float, + stop1_lon: float, + stop2_lat: float, + stop2_lon: float + ) -> float: + """ + Get shape distance between two stops (more accurate than haversine). + """ + proj1 = self.project_point(stop1_lat, stop1_lon) + proj2 = self.project_point(stop2_lat, stop2_lon) + return abs(proj2['distance_along_shape'] - proj1['distance_along_shape']) + + +def calculate_distance_features_with_shape( + vehicle_position: Dict, + stop: Dict, + next_stop: Optional[Dict], + shape: Optional[ShapePolyline] = None, + vehicle_stop_order: Optional[int] = None, + total_segments: Optional[int] = None, +) -> Dict: + """ + Enhanced version that uses shape data when available. + + Args: + vehicle_position: {'lat': float, 'lon': float, 'bearing': Optional[float]} + stop: {'stop_id': str, 'lat': float, 'lon': float, 'stop_order': Optional[int]} + next_stop: {'stop_id': str, 'lat': float, 'lon': float} or None + shape: ShapePolyline instance or None + vehicle_stop_order: 0-based index of the closest upstream stop (optional) + total_segments: Total number of stop-to-stop segments in trip (optional) + + Returns: + Same as calculate_distance_features() but with additional shape-based fields: + - 'shape_progress': accurate progress along route shape [0, 1] + - 'shape_distance_to_stop': along-shape distance to stop (meters) + - 'cross_track_error': perpendicular distance from route (meters) + - 'progress_ratio': coarse fallback progress when shape is missing + """ + vlat, vlon = float(vehicle_position["lat"]), float(vehicle_position["lon"]) + slat, slon = float(stop["lat"]), float(stop["lon"]) + + # Base features (always computed) + result = { + 'distance_to_stop': _haversine_m(vlat, vlon, slat, slon), + 'distance_to_next_stop': None, + 'progress_on_segment': None, + 'progress_ratio': None, + 'shape_progress': None, + 'shape_distance_to_stop': None, + 'cross_track_error': None, + } + + if next_stop is not None: + nlat, nlon = float(next_stop["lat"]), float(next_stop["lon"]) + seg_len = _haversine_m(slat, slon, nlat, nlon) + if seg_len == 0.0: + result['distance_to_next_stop'] = 0.0 + else: + result['distance_to_next_stop'] = _haversine_m(vlat, vlon, nlat, nlon) + + # Simple progress proxy if we have next stop but no shape + if result['progress_on_segment'] is None and next_stop is not None and result['distance_to_next_stop'] is not None: + nlat, nlon = float(next_stop["lat"]), float(next_stop["lon"]) + seg_len = _haversine_m(slat, slon, nlat, nlon) + if seg_len > 0: + progress = 1.0 - (result['distance_to_next_stop'] / seg_len) + result['progress_on_segment'] = max(0.0, min(1.0, progress)) + else: + result['progress_on_segment'] = 0.0 + + # Shape-based features (if shape available) + if shape is not None: + vehicle_proj = shape.project_point(vlat, vlon) + stop_proj = shape.project_point(slat, slon) + + # Distance along shape from vehicle to stop + shape_dist_to_stop = stop_proj['distance_along_shape'] - vehicle_proj['distance_along_shape'] + + result.update({ + 'shape_progress': vehicle_proj['progress'], + 'shape_distance_to_stop': max(0, shape_dist_to_stop), # Don't allow negative + 'cross_track_error': vehicle_proj['cross_track_distance'], + 'progress_ratio': vehicle_proj['progress'], + }) + + # If we have next_stop, compute segment progress along shape + if next_stop is not None: + next_proj = shape.project_point(nlat, nlon) + segment_length = next_proj['distance_along_shape'] - stop_proj['distance_along_shape'] + + if segment_length > 0: + # How far past current stop along shape + past_stop = vehicle_proj['distance_along_shape'] - stop_proj['distance_along_shape'] + result['progress_on_segment'] = max(0.0, min(1.0, past_stop / segment_length)) + else: + result['progress_on_segment'] = 0.0 + # Fallback progress_ratio using stop order metadata + if result['progress_ratio'] is None: + order = vehicle_stop_order + if order is None: + order = stop.get("vehicle_stop_order") or stop.get("stop_order") + segments = total_segments + if segments is None: + segments = stop.get("total_segments") + if order is not None and segments: + completed_segments = max(float(order), 0.0) + progress_within = result['progress_on_segment'] or 0.0 + denom = max(float(segments), 1.0) + ratio = (completed_segments + progress_within) / denom + result['progress_ratio'] = max(0.0, min(1.0, ratio)) + + return result + + +# ==================== Helper: Load shapes from GTFS ==================== + +def load_shape_from_gtfs(shape_id: str, conn) -> ShapePolyline: + """ + Load a shape polyline from GTFS shapes table. + + Args: + shape_id: GTFS shape_id + conn: Database connection (psycopg2/asyncpg) + + Returns: + ShapePolyline instance + """ + with conn.cursor() as cur: + cur.execute( + """ + SELECT shape_pt_lat, shape_pt_lon + FROM sch_pipeline_shape + WHERE shape_id = %s + ORDER BY shape_pt_sequence + """, + (shape_id,) + ) + rows = cur.fetchall() + + if not rows: + raise ValueError(f"No shape found for shape_id: {shape_id}") + + points = [(float(row[0]), float(row[1])) for row in rows] + return ShapePolyline(points) + + +def load_shape_for_trip(trip_id: str, conn) -> Optional[ShapePolyline]: + """ + Load shape for a trip, returns None if trip has no shape. + """ + with conn.cursor() as cur: + cur.execute( + """ + SELECT shape_id + FROM sch_pipeline_trip + WHERE trip_id = %s + """, + (trip_id,) + ) + row = cur.fetchone() + + if not row or not row[0]: + return None + + return load_shape_from_gtfs(row[0], conn) + + +# ==================== Usage Example ==================== + +def example_usage(): + """ + Example showing how to use shape-informed features in practice. + """ + # 1. Load shape once per trip (cache this!) + from psycopg2 import connect + conn = connect("postgresql://user:pass@localhost/gtfs") + + shape = load_shape_for_trip("trip_123", conn) + + # 2. For each vehicle position update + vehicle_position = { + 'lat': 42.3601, + 'lon': -71.0589, + 'bearing': 180.0 + } + + current_stop = { + 'stop_id': 'stop_A', + 'lat': 42.3598, + 'lon': -71.0592 + } + + next_stop = { + 'stop_id': 'stop_B', + 'lat': 42.3620, + 'lon': -71.0580 + } + + # 3. Get shape-informed features + features = calculate_distance_features_with_shape( + vehicle_position, + current_stop, + next_stop, + shape=shape + ) + + print(f"Shape progress: {features['shape_progress']:.2%}") + print(f"Distance to stop (along shape): {features['shape_distance_to_stop']:.0f}m") + print(f"Cross-track error: {features['cross_track_error']:.1f}m") + print(f"Segment progress: {features['progress_on_segment']:.2%}") + +if __name__ == "__main__": + example_usage() diff --git a/eta_prediction/feature_engineering/temporal.py b/eta_prediction/feature_engineering/temporal.py new file mode 100644 index 0000000..5b32a58 --- /dev/null +++ b/eta_prediction/feature_engineering/temporal.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, Optional + +try: + import zoneinfo # py3.9+ +except ImportError: # pragma: no cover + from backports import zoneinfo # type: ignore + +def _get_holiday_calendar(region: str): + """ + Try to build a holiday calendar. Falls back to empty set if 'holidays' isn't installed. + region: + - 'US_MA' β†’ U.S. w/ Massachusetts state holidays (good for MBTA) + - 'CR' β†’ Costa Rica + """ + try: + import holidays + except Exception: + return None + + if region.upper() == "US_MA": + return holidays.US(state="MA") + if region.upper() == "CR": + # Requires holidays>=0.52 which includes CostaRica + try: + return holidays.CostaRica() + except Exception: + return None + # Fallback: US federal only + return holidays.US() + +def _to_local(dt: datetime, tz: str) -> datetime: + """Ensure timezone-aware datetime localized to tz.""" + tzinfo = zoneinfo.ZoneInfo(tz) + if dt.tzinfo is None: + # assume input is UTC if naive (common for feeds); adjust if your code stores local + return dt.replace(tzinfo=zoneinfo.ZoneInfo("UTC")).astimezone(tzinfo) + return dt.astimezone(tzinfo) + +def _tod_bin(hour: int) -> str: + """ + Map hourβ†’time-of-day bin. + Spec requires: 'morning' | 'midday' | 'afternoon' | 'evening'. + We bucket: + 05–09 β†’ morning + 10–13 β†’ midday + 14–17 β†’ afternoon + 18–04 β†’ evening (covers late night/overnight to keep labels strict) + """ + if 5 <= hour <= 9: + return "morning" + if 10 <= hour <= 13: + return "midday" + if 14 <= hour <= 17: + return "afternoon" + return "evening" + +def extract_temporal_features( + timestamp: datetime, + *, + tz: str = "America/New_York", # MBTA default + region: str = "US_MA", # MBTA default (US + Massachusetts) +) -> Dict[str, object]: + """ + Returns: + - hour: 0-23 + - day_of_week: 0-6 (Monday=0) + - is_weekend: bool + - is_holiday: bool (US-MA by default; set region='CR' for Costa Rica) + - time_of_day_bin: 'morning'|'midday'|'afternoon'|'evening' + - is_peak_hour: bool (7-9am, 4-7pm; weekdays only) + """ + dt_local = _to_local(timestamp, tz) + hour = dt_local.hour + dow = dt_local.weekday() # Monday=0 + is_weekend = dow >= 5 + + cal = _get_holiday_calendar(region) + # Holidays lib checks by date() + is_holiday = bool(cal and (dt_local.date() in cal)) + + # Peak windows (commuter assumption), weekdays only + is_peak_hour = (dow < 5) and ((7 <= hour <= 9) or (16 <= hour <= 19)) + + return { + "hour": hour, + "day_of_week": dow, + "is_weekend": is_weekend, + "is_holiday": is_holiday, + "time_of_day_bin": _tod_bin(hour), + "is_peak_hour": is_peak_hour, + } diff --git a/eta_prediction/feature_engineering/tests/__init__.py b/eta_prediction/feature_engineering/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/eta_prediction/feature_engineering/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/eta_prediction/feature_engineering/tests/test_spatial.py b/eta_prediction/feature_engineering/tests/test_spatial.py new file mode 100644 index 0000000..cc2ee1d --- /dev/null +++ b/eta_prediction/feature_engineering/tests/test_spatial.py @@ -0,0 +1,124 @@ +# tests/test_spatial.py +import pytest + +from feature_engineering.spatial import ( + calculate_distance_features_with_shape, + ShapePolyline, +) + + +def approx_equal(a, b, tol): + return abs(a - b) <= tol + + +def test_basic_distance_and_progress_without_shape(): + vp = {"lat": 0.0, "lon": 0.0} + stop = {"stop_id": "A", "lat": 0.0, "lon": 1.0, "stop_order": 2} + next_stop = {"stop_id": "B", "lat": 0.0, "lon": 2.0} + + feats = calculate_distance_features_with_shape( + vp, + stop, + next_stop, + shape=None, + vehicle_stop_order=2, + total_segments=10, + ) + + assert approx_equal(feats["distance_to_stop"], 111_320, 600) + assert 0.0 <= feats["progress_on_segment"] <= 1.0 + assert 0.15 <= feats["progress_ratio"] <= 0.25 # 2/10 with some within segment progress + + +def test_progress_clamped_when_outside_segment(): + stop = {"stop_id": "A", "lat": 0.0, "lon": 0.0, "stop_order": 5} + next_stop = {"stop_id": "B", "lat": 0.0, "lon": 1.0} + + vp_before = {"lat": 0.0, "lon": -1.0} + feats_before = calculate_distance_features_with_shape( + vp_before, + stop, + next_stop, + shape=None, + vehicle_stop_order=5, + total_segments=8, + ) + assert feats_before["progress_on_segment"] == 0.0 + + vp_past = {"lat": 0.0, "lon": 3.0} + feats_past = calculate_distance_features_with_shape( + vp_past, + stop, + next_stop, + shape=None, + vehicle_stop_order=5, + total_segments=8, + ) + assert feats_past["progress_on_segment"] == 0.0 + + +def test_zero_length_segment_defaults(): + stop = {"stop_id": "S", "lat": 9.9, "lon": -84.0, "stop_order": 3} + next_stop = {"stop_id": "S", "lat": 9.9, "lon": -84.0} + vp = {"lat": 9.9, "lon": -84.001} + + feats = calculate_distance_features_with_shape( + vp, + stop, + next_stop, + shape=None, + vehicle_stop_order=3, + total_segments=5, + ) + + assert feats["progress_on_segment"] == 0.0 + assert feats["distance_to_next_stop"] == 0.0 + assert approx_equal(feats["progress_ratio"], 3 / 5, 0.01) + + +def test_shape_based_progress_used_when_available(): + shape = ShapePolyline([ + (0.0, 0.0), + (0.0, 1.0), + (0.0, 2.0), + ]) + vp = {"lat": 0.0, "lon": 0.5} + stop = {"stop_id": "B", "lat": 0.0, "lon": 1.0, "stop_order": 1} + next_stop = {"stop_id": "C", "lat": 0.0, "lon": 2.0} + + feats = calculate_distance_features_with_shape( + vp, + stop, + next_stop, + shape=shape, + vehicle_stop_order=1, + total_segments=2, + ) + + assert 0.24 <= feats["shape_progress"] <= 0.26 + assert feats["progress_ratio"] == pytest.approx(feats["shape_progress"]) + assert feats["shape_distance_to_stop"] > 0 + assert feats["cross_track_error"] < 50 + + +def test_shape_progress_overrides_progress_on_segment(): + shape = ShapePolyline([ + (0.0, 0.0), + (0.5, 0.5), + (1.0, 1.0), + ]) + vp = {"lat": 0.25, "lon": 0.25} + stop = {"stop_id": "B", "lat": 0.5, "lon": 0.5, "stop_order": 1} + next_stop = {"stop_id": "C", "lat": 1.0, "lon": 1.0} + + feats = calculate_distance_features_with_shape( + vp, + stop, + next_stop, + shape=shape, + vehicle_stop_order=1, + total_segments=2, + ) + + assert feats["progress_on_segment"] == pytest.approx(0.0) + assert 0.24 <= feats["shape_progress"] <= 0.26 diff --git a/eta_prediction/feature_engineering/tests/test_temporal.py b/eta_prediction/feature_engineering/tests/test_temporal.py new file mode 100644 index 0000000..6bda9fd --- /dev/null +++ b/eta_prediction/feature_engineering/tests/test_temporal.py @@ -0,0 +1,108 @@ +# feature_engineering/tests/test_temporal.py +from datetime import datetime, timezone, date +import pytest + +import feature_engineering.temporal as temporal + + +class DummyCalendar: + """Minimal calendar stub that supports `date in cal` checks.""" + def __init__(self, holiday_dates): + self._dates = set(holiday_dates) + + def __contains__(self, d): + return d in self._dates + + +def test_naive_timestamp_assumed_utc_and_converted_to_mbta_local(): + # Oct 8, 2025 12:30:00 UTC β†’ America/New_York (EDT, UTC-4) = 08:30 + ts = datetime(2025, 10, 8, 12, 30, 0) # naive -> treated as UTC + feats = temporal.extract_temporal_features(ts, tz="America/New_York", region="US_MA") + assert feats["hour"] == 8 + assert feats["day_of_week"] == 2 # 0=Mon -> Wed=2 + assert feats["is_weekend"] is False + # 08:30 β†’ morning bin per spec + assert feats["time_of_day_bin"] == "morning" + # Weekday 8am β†’ peak + assert feats["is_peak_hour"] is True + + +def test_aware_utc_timestamp_converts_to_local_consistently(): + # Same instant as previous test, but explicitly aware in UTC + ts = datetime(2025, 10, 8, 12, 30, 0, tzinfo=timezone.utc) + feats = temporal.extract_temporal_features(ts, tz="America/New_York", region="US_MA") + assert feats["hour"] == 8 + assert feats["day_of_week"] == 2 + assert feats["is_peak_hour"] is True + +@pytest.mark.parametrize( + "hour,expected_bin", + [ + (5, "morning"), + (9, "morning"), + (10, "midday"), + (13, "midday"), + (14, "afternoon"), + (17, "afternoon"), + (18, "evening"), + (23, "evening"), + (0, "evening"), + (3, "evening"), + (4, "evening"), + ], +) + +def test_time_of_day_bins(hour, expected_bin): + # Build a timestamp that is ALREADY in the target local tz (America/New_York). + # That way, `hour` is the local hour we want to assert on. + ny_tz = temporal.zoneinfo.ZoneInfo("America/New_York") + local_ts = datetime(2025, 9, 30, hour, 0, 0, tzinfo=ny_tz) # Tuesday + feats = temporal.extract_temporal_features(local_ts, tz="America/New_York", region="US_MA") + assert feats["time_of_day_bin"] == expected_bin + +def test_peak_hours_windows_and_weekend_rule(): + # Weekday 17:00 local β†’ peak + ts_local_peak = datetime(2025, 9, 30, 17, 0, 0, tzinfo=temporal.zoneinfo.ZoneInfo("America/New_York")) # Tue + feats = temporal.extract_temporal_features(ts_local_peak, tz="America/New_York", region="US_MA") + assert feats["is_peak_hour"] is True + + # Weekday 20:00 local β†’ not peak + ts_local_offpeak = datetime(2025, 9, 30, 20, 0, 0, tzinfo=temporal.zoneinfo.ZoneInfo("America/New_York")) + feats2 = temporal.extract_temporal_features(ts_local_offpeak, tz="America/New_York", region="US_MA") + assert feats2["is_peak_hour"] is False + + # Weekend 08:00 local β†’ not peak even though within 7–9 + ts_weekend = datetime(2025, 10, 4, 8, 0, 0, tzinfo=temporal.zoneinfo.ZoneInfo("America/New_York")) # Sat + feats3 = temporal.extract_temporal_features(ts_weekend, tz="America/New_York", region="US_MA") + assert feats3["day_of_week"] == 5 and feats3["is_weekend"] is True + assert feats3["is_peak_hour"] is False + + +def test_cr_timezone_conversion_and_weekday(): + # Costa Rica is UTC-6 all year (no DST). 14:00 UTC -> 08:00 local + ts_utc = datetime(2025, 8, 12, 14, 0, 0, tzinfo=timezone.utc) # Tue + feats = temporal.extract_temporal_features(ts_utc, tz="America/Costa_Rica", region="CR") + assert feats["hour"] == 8 + assert feats["day_of_week"] == 1 # Tuesday + assert feats["is_weekend"] is False + + +def test_holiday_true_via_monkeypatched_calendar(monkeypatch): + # Pick an arbitrary date and mark it as a holiday through a stub calendar + holiday_d = date(2025, 7, 4) + def fake_get_cal(region: str): + return DummyCalendar({holiday_d}) + + monkeypatch.setattr(temporal, "_get_holiday_calendar", fake_get_cal) + + ts_local = datetime(2025, 7, 4, 9, 0, 0, tzinfo=temporal.zoneinfo.ZoneInfo("America/New_York")) + feats = temporal.extract_temporal_features(ts_local, tz="America/New_York", region="US_MA") + assert feats["is_holiday"] is True + + +def test_holiday_false_when_calendar_missing(monkeypatch): + # Simulate absence/failure of holidays package: calendar returns None + monkeypatch.setattr(temporal, "_get_holiday_calendar", lambda region: None) + ts_local = datetime(2025, 7, 4, 9, 0, 0, tzinfo=temporal.zoneinfo.ZoneInfo("America/New_York")) + feats = temporal.extract_temporal_features(ts_local, tz="America/New_York", region="US_MA") + assert feats["is_holiday"] is False diff --git a/eta_prediction/feature_engineering/tests/test_weather.py b/eta_prediction/feature_engineering/tests/test_weather.py new file mode 100644 index 0000000..558eb03 --- /dev/null +++ b/eta_prediction/feature_engineering/tests/test_weather.py @@ -0,0 +1,192 @@ +from datetime import datetime, timezone, timedelta +from unittest.mock import Mock, patch +import pytest + +from feature_engineering.weather import fetch_weather, OPEN_METEO_URL + + +class FakeCache: + """Tiny in-memory cache with the subset of the Django cache API we use.""" + def __init__(self): + self._d = {} + def get(self, key, default=None): + return self._d.get(key, default) + def set(self, key, value, timeout=None): + self._d[key] = value + def clear(self): + self._d.clear() + + +def _mock_response(payload: dict, status_code: int = 200): + resp = Mock() + resp.status_code = status_code + resp.json = Mock(return_value=payload) + resp.raise_for_status = Mock() + if status_code >= 400: + resp.raise_for_status.side_effect = Exception(f"HTTP {status_code}") + return resp + + +@pytest.fixture() +def fake_cache(monkeypatch): + # Patch the cache object inside the weather module (so no Django needed) + from feature_engineering import weather + fc = FakeCache() + monkeypatch.setattr(weather, "cache", fc, raising=True) + return fc + + +def test_fetch_weather_success_exact_hour(fake_cache): + fake_cache.clear() + lat, lon = 9.935, -84.091 + ts = datetime(2025, 1, 1, 12, 34, tzinfo=timezone.utc) + target_str = ts.replace(minute=0, second=0, microsecond=0).strftime("%Y-%m-%dT%H:00") + + payload = { + "hourly": { + "time": [target_str], + "temperature_2m": [23.4], + "precipitation": [0.2], + "wind_speed_10m": [12.0], + "weather_code": [80], + "visibility": [9800.0], + } + } + + with patch("feature_engineering.weather.requests.get") as mget: + mget.return_value = _mock_response(payload) + out = fetch_weather(lat, lon, ts) + + assert out == { + "temperature_c": 23.4, + "precipitation_mm": 0.2, + "wind_speed_kmh": 12.0, + "weather_code": 80, + "visibility_m": 9800.0, + } + + assert mget.call_count == 1 + args, kwargs = mget.call_args + assert args[0] == OPEN_METEO_URL + params = kwargs["params"] + assert params["latitude"] == lat + assert params["longitude"] == lon + assert params["timezone"] == "UTC" + assert "temperature_2m" in params["hourly"] + assert "start" in params and "end" in params + + start_dt = datetime.fromisoformat(params["start"].replace("Z", "+00:00")) + end_dt = datetime.fromisoformat(params["end"].replace("Z", "+00:00")) + assert start_dt.strftime("%Y-%m-%dT%H:00") == target_str + assert end_dt - start_dt == timedelta(hours=1) + + +def test_fetch_weather_naive_timestamp_treated_as_utc(fake_cache): + fake_cache.clear() + lat, lon = 9.0, -84.0 + naive_ts = datetime(2025, 1, 1, 7, 15) # treated as UTC in implementation + target_str = naive_ts.replace(minute=0, second=0, microsecond=0).strftime("%Y-%m-%dT%H:00") + + payload = { + "hourly": { + "time": [target_str], + "temperature_2m": [30.0], + "precipitation": [0.0], + "wind_speed_10m": [5.0], + "weather_code": [0], + "visibility": [10000.0], + } + } + + with patch("feature_engineering.weather.requests.get") as mget: + mget.return_value = _mock_response(payload) + out = fetch_weather(lat, lon, naive_ts) + + assert out["temperature_c"] == 30.0 + assert mget.call_count == 1 + + +def test_fetch_weather_caches_result(fake_cache): + fake_cache.clear() + lat, lon = 10.0, -84.0 + ts = datetime(2025, 2, 2, 3, 59, tzinfo=timezone.utc) + target_str = ts.replace(minute=0, second=0, microsecond=0).strftime("%Y-%m-%dT%H:00") + + payload = { + "hourly": { + "time": [target_str], + "temperature_2m": [21.1], + "precipitation": [1.0], + "wind_speed_10m": [8.0], + "weather_code": [51], + "visibility": [7500.0], + } + } + + with patch("feature_engineering.weather.requests.get") as mget: + mget.return_value = _mock_response(payload) + + out1 = fetch_weather(lat, lon, ts) + assert mget.call_count == 1 + assert out1["temperature_c"] == 21.1 + + # Second call should hit the cache (no extra HTTP) + mget.side_effect = Exception("Should not be called due to cache") + out2 = fetch_weather(lat, lon, ts) + assert out2 == out1 + assert mget.call_count == 1 + + +def test_fetch_weather_missing_hour_returns_nones(fake_cache): + fake_cache.clear() + lat, lon = 9.5, -83.9 + ts = datetime(2025, 3, 3, 10, 10, tzinfo=timezone.utc) + wrong_hour = ts.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1) + + payload = { + "hourly": { + "time": [wrong_hour.strftime("%Y-%m-%dT%H:00")], + "temperature_2m": [25.0], + "precipitation": [0.0], + "wind_speed_10m": [10.0], + "weather_code": [1], + "visibility": [9000.0], + } + } + + with patch("feature_engineering.weather.requests.get") as mget: + mget.return_value = _mock_response(payload) + out = fetch_weather(lat, lon, ts) + + assert out == { + "temperature_c": None, + "precipitation_mm": None, + "wind_speed_kmh": None, + "weather_code": None, + "visibility_m": None, + } + + +def test_fetch_weather_network_error_returns_nones_and_caches(fake_cache): + fake_cache.clear() + lat, lon = 9.9, -84.2 + ts = datetime(2025, 4, 4, 6, 0, tzinfo=timezone.utc) + + with patch("feature_engineering.weather.requests.get") as mget: + from requests import RequestException + mget.side_effect = RequestException("boom") + out1 = fetch_weather(lat, lon, ts) + + assert out1 == { + "temperature_c": None, + "precipitation_mm": None, + "wind_speed_kmh": None, + "weather_code": None, + "visibility_m": None, + } + + # Second call should return from cache without HTTP + with patch("feature_engineering.weather.requests.get") as mget2: + out2 = fetch_weather(lat, lon, ts) + assert mget2.call_count == 0 + assert out2 == out1 diff --git a/eta_prediction/feature_engineering/weather.py b/eta_prediction/feature_engineering/weather.py new file mode 100644 index 0000000..da43c37 --- /dev/null +++ b/eta_prediction/feature_engineering/weather.py @@ -0,0 +1,103 @@ +from datetime import datetime, timedelta, timezone +import requests +from django.core.cache import cache + +OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast" + +def fetch_weather(lat: float, lon: float, timestamp: datetime) -> dict: + """ + Returns a dict with: + - temperature_c: float + - precipitation_mm: float + - wind_speed_kmh: float + - weather_code: int (WMO code) + - visibility_m: float + + Caching: + Key: weather:{lat}:{lon}:{timestamp_hour_utc_iso} + TTL: 3600s + """ + # Normalize timestamp to UTC and truncate to the start of the hour + if timestamp.tzinfo is None: + ts_utc = timestamp.replace(tzinfo=timezone.utc) + else: + ts_utc = timestamp.astimezone(timezone.utc) + ts_hour = ts_utc.replace(minute=0, second=0, microsecond=0) + + cache_key = f"weather:{lat:.5f}:{lon:.5f}:{ts_hour.isoformat()}" + cached = cache.get(cache_key) + if cached is not None: + return cached + + # Query a 1-hour window [ts_hour, ts_hour+1h) so the array has exactly one item + ts_end = ts_hour + timedelta(hours=1) + target_time_str = ts_hour.strftime("%Y-%m-%dT%H:00") + + params = { + "latitude": lat, + "longitude": lon, + "hourly": ",".join([ + "temperature_2m", + "precipitation", + "wind_speed_10m", + "weather_code", + "visibility" + ]), + # Use explicit start/end to avoid a full-day fetch; keep timezone consistent + "start": ts_hour.isoformat().replace("+00:00", "Z"), + "end": ts_end.isoformat().replace("+00:00", "Z"), + "timezone": "UTC", + "windspeed_unit": "kmh", # ensures wind speed is km/h + "precipitation_unit": "mm" # (default is mm, set explicitly for clarity) + } + + try: + resp = requests.get(OPEN_METEO_URL, params=params, timeout=8) + resp.raise_for_status() + data = resp.json() + except requests.RequestException: + # Network/API error β€” return Nones (caller can decide fallback) + result = { + "temperature_c": None, + "precipitation_mm": None, + "wind_speed_kmh": None, + "weather_code": None, + "visibility_m": None, + } + cache.set(cache_key, result, timeout=3600) + return result + + hourly = data.get("hourly") or {} + times = hourly.get("time") or [] + + # Find the index for the exact hour + try: + idx = times.index(target_time_str) + except ValueError: + # Hour not found (e.g., API model coverage gap) β€” return Nones + result = { + "temperature_c": None, + "precipitation_mm": None, + "wind_speed_kmh": None, + "weather_code": None, + "visibility_m": None, + } + cache.set(cache_key, result, timeout=3600) + return result + + def _get(series_name, default=None): + series = hourly.get(series_name) + if not series or idx >= len(series): + return default + return series[idx] + + result = { + "temperature_c": _get("temperature_2m"), + "precipitation_mm": _get("precipitation"), + "wind_speed_kmh": _get("wind_speed_10m"), + "weather_code": _get("weather_code"), + "visibility_m": _get("visibility"), + } + + cache.set(cache_key, result, timeout=3600) + return result From fca3404c83b4558b233574aac418c857e7cda322 Mon Sep 17 00:00:00 2001 From: Jae Date: Thu, 27 Nov 2025 22:11:57 -0600 Subject: [PATCH 3/5] feat(models): PR3 - Add Models Module (Depends on PR2) --- eta_prediction/models/README.md | 162 +++++ eta_prediction/models/__init__.py | 196 ++++++ eta_prediction/models/check_registry.py | 198 ++++++ eta_prediction/models/common/data.py | 230 +++++++ eta_prediction/models/common/keys.py | 257 ++++++++ eta_prediction/models/common/metrics.py | 245 ++++++++ eta_prediction/models/common/registry.py | 379 +++++++++++ eta_prediction/models/common/utils.py | 276 ++++++++ .../models/evaluation/leaderboard.py | 229 +++++++ .../models/evaluation/roll_validate.py | 260 ++++++++ eta_prediction/models/ewma/predict.py | 155 +++++ eta_prediction/models/ewma/train.py | 324 ++++++++++ .../models/historical_mean/predict.py | 136 ++++ .../models/historical_mean/train.py | 243 +++++++ .../models/polyreg_distance/predict.py | 112 ++++ .../models/polyreg_distance/train.py | 318 ++++++++++ eta_prediction/models/polyreg_time/predict.py | 99 +++ eta_prediction/models/polyreg_time/train.py | 539 ++++++++++++++++ eta_prediction/models/train_all_models.py | 393 ++++++++++++ eta_prediction/models/xgb/predict.py | 113 ++++ eta_prediction/models/xgb/train.py | 592 ++++++++++++++++++ 21 files changed, 5456 insertions(+) create mode 100644 eta_prediction/models/README.md create mode 100644 eta_prediction/models/__init__.py create mode 100644 eta_prediction/models/check_registry.py create mode 100644 eta_prediction/models/common/data.py create mode 100644 eta_prediction/models/common/keys.py create mode 100644 eta_prediction/models/common/metrics.py create mode 100644 eta_prediction/models/common/registry.py create mode 100644 eta_prediction/models/common/utils.py create mode 100644 eta_prediction/models/evaluation/leaderboard.py create mode 100644 eta_prediction/models/evaluation/roll_validate.py create mode 100644 eta_prediction/models/ewma/predict.py create mode 100644 eta_prediction/models/ewma/train.py create mode 100644 eta_prediction/models/historical_mean/predict.py create mode 100644 eta_prediction/models/historical_mean/train.py create mode 100644 eta_prediction/models/polyreg_distance/predict.py create mode 100644 eta_prediction/models/polyreg_distance/train.py create mode 100644 eta_prediction/models/polyreg_time/predict.py create mode 100644 eta_prediction/models/polyreg_time/train.py create mode 100644 eta_prediction/models/train_all_models.py create mode 100644 eta_prediction/models/xgb/predict.py create mode 100644 eta_prediction/models/xgb/train.py diff --git a/eta_prediction/models/README.md b/eta_prediction/models/README.md new file mode 100644 index 0000000..263b601 --- /dev/null +++ b/eta_prediction/models/README.md @@ -0,0 +1,162 @@ +# ETA Prediction Models + +Comprehensive modeling stack that ingests feature-engineered GTFS-RT datasets, trains multiple ETA estimators, evaluates them with consistent metrics, and persists artifacts to a registry for online or batch inference. + +--- + +## Architecture & Workflow + +1. **Dataset management** – `common/data.py` loads `datasets/*.parquet`, performs cleaning, and produces temporal or route-based splits. All models rely on `ETADataset.clean_data`, `temporal_split`, and `route_split` to prevent leakage and keep outliers consistent. +2. **Training entry points** – Each model family exposes a `train_*` routine (e.g., `historical_mean/train.py`, `polyreg_time/train.py`, `xgb/train.py`) that handles filtering, splitting, fitting, and evaluation. +3. **Evaluation & metrics** – `common/metrics.py` computes the canonical metric suite (MAE/RMSE/RΒ²/bias/coverage). `evaluation/leaderboard.py` and `evaluation/roll_validate.py` enable cross-model comparison and walk-forward validation over time. +4. **Registry** – `common/registry.py` persists `{model_key}.pkl` artifacts plus `{model_key}_meta.json` metadata while maintaining `trained/registry.json`. `common/keys.py` standardizes identifiers (dataset, feature groups, scope) for reproducibility. +5. **Inference** – Each model directory contains a `predict.py` that loads registry artifacts, validates required features, and formats outputs for downstream services. + +--- + +## Shared Utilities (`models/common/`) + +| Module | Responsibilities | +| --- | --- | +| `data.py` | Defines `ETADataset`, cleaning rules (drop missing targets, enforce distance thresholds), splits (`temporal_split`, `route_split`), and feature-selection helpers such as `prepare_features_target`. | +| `keys.py` | Generates descriptive model keys and experiment identifiers (dataset, feature groups, version, route scope) and parses keys to recover metadata when routing predictions. | +| `metrics.py` | Implements MAE/RMSE/MAPE/quantile/bias plus `compute_all_metrics`, segmentation tooling (`error_analysis`), and prediction intervals from residuals. | +| `registry.py` | Saves/loads pickled estimators + JSON metadata, lists/filter models, selects best models per metric/route, and handles deletion/cleanup. | +| `utils.py` | Logging setup, clipping/smoothing helpers, lag feature generators, formatted metric tables, and convenience functions like `train_test_summary`. | + +--- + +## Model Families + +### Historical Mean (`historical_mean/`) +- Groups ETAs by configurable dimensions (default `['route_id', 'stop_sequence', 'hour']`) and stores lookup tables with coverage metrics. +- Training pipeline filters datasets, performs temporal splits, and reports validation/test performance before saving to the registry. +- Prediction API reports whether the output was backed by historical data or fell back to the global mean, making it ideal for baseline comparisons and cold-start monitoring. + +### Polynomial Regression – Distance (`polyreg_distance/`) +- Fits Ridge-regularized polynomial features on `distance_to_stop`. Supports route-specific models (`route_specific=True`) with an optional global fallback. +- Metadata records polynomial degree, regularization strength, and coefficient samples for transparency. +- Prediction helper exposes coefficients and supports batch inference with automatic route routing. + +### Polynomial Regression – Time Enhanced (`polyreg_time/`) +- Extends distance regression with optional temporal (`hour`, `day_of_week`, `is_peak_hour`, etc.), spatial (segment progress + identifiers), and weather inputs. +- Uses `ColumnTransformer` + `PolynomialFeatures` for distance and `StandardScaler` for dense features, with configurable NaN strategies (`drop`, `impute`, or strict `error`). +- Provides coefficient-based feature importance summaries to highlight influential covariates per dataset/route. + +### Exponentially Weighted Moving Average (`ewma/`) +- Maintains streaming EWMA statistics per `(route_id, stop_sequence [, hour])` key with configurable `alpha` and minimum observation thresholds. +- Offers online learning through `predict_and_update`, enabling real-time adaptation when ground truth becomes available. +- Ideal for highly non-stationary congestion patterns where recency outweighs historical aggregates. + +### XGBoost Gradient Boosted Trees (`xgb/`) +- `XGBTimeModel` mirrors the feature-flag system from the time polynomial model (temporal/spatial toggles) but leverages `xgboost.XGBRegressor` for nonlinear interactions. +- Cleans datasets using the same missing-value audits, exposes feature importance from the trained booster, and supports hyper-parameter tuning (`max_depth`, `n_estimators`, `learning_rate`, subsampling knobs). +- Prediction API aligns with the polynomial time model so switching model keys requires no payload changes. + +--- + +## Training Orchestration (`train_all_models.py`) + +`python models/train_all_models.py --dataset sample_dataset [--by-route] [--models ...] [--no-save]` + +- Loads the dataset once, optionally filtered by route, and delegates to each `train_*` routine. Default models: historical mean, distance polyreg, time polyreg, EWMA, and XGBoost. +- **Global mode** – trains one model per type and prints MAE/RMSE/RΒ² summaries. +- **Route-specific mode** – iterates each route present in the dataset, trains the selected model types, and prints per-route performance plus correlations between trip volume and error. +- Pass `--no-save` for dry runs; otherwise results land in the registry with enriched metadata (sample counts, coverage, and configurations). + +--- + +## Evaluation Toolkit (`models/evaluation/`) + +- **Leaderboard (`leaderboard.py`)** – Loads trained models from the registry, evaluates each on a consistent test split (temporal or route-based), and prints a ranked table with MAE/RMSE/RΒ², coverage, and bias. Use `quick_compare([...], dataset)` for one-liners. +- **Rolling validation (`roll_validate.py`)** – Implements walk-forward validation across sliding temporal windows to measure stability over time. Accepts custom `train_fn`/`predict_fn` callables so any model type can be stress-tested. +- **Plotting helpers** – Optional Matplotlib visualizations to inspect MAE drift, coverage trends, or metric distributions across windows. + +--- + +## Model Registry & Keys + +- `ModelKey.generate(...)` assembles identifiers of the form `polyreg_time_sample_temporal-spatial_global_20250126_143022_degree=2`. Keys capture dataset, feature groups, scope (global vs. `route_{id}`), timestamp, and supplemental hyper-parameters to simplify filtering and reproducibility. +- `ModelRegistry.save_model` writes `{model_key}.pkl` (pickled estimator) and `{model_key}_meta.json` (config + metrics) while updating `trained/registry.json`. Metadata contains dataset info, route scope, sample counts, evaluation results, and custom training attributes. +- Consumers can: + ```python + from models.common.registry import get_registry + registry = get_registry() + df = registry.list_models(model_type='polyreg_time') + best_key = registry.get_best_model(metric='test_mae_seconds', route_id='global') + model = registry.load_model(best_key) + meta = registry.load_metadata(best_key) + ``` +- `check_registry.py` provides diagnostics (permissions, file counts, distribution of model types) and an optional save/load smoke test. + +--- + +## Prediction Interfaces + +Each model package ships with a `predict.py` tailored to its feature requirements: + +| Module | Required Inputs | Notes | +| --- | --- | --- | +| `historical_mean.predict.predict_eta` | `route_id`, `stop_sequence`, `hour` (+ optional weekday/peak flags) | Returns coverage flag and fallback status. | +| `polyreg_distance.predict.predict_eta` | `distance_to_stop` (+ `route_id` for route-specific models) | Exposes polynomial coefficients for transparency. | +| `polyreg_time.predict.predict_eta` | `distance_to_stop` plus any temporal/spatial/weather signals enabled during training | `features_used` documents the expected payload. | +| `ewma.predict.predict_eta` | `route_id`, `stop_sequence` (+ optional `hour`) | Supports `predict_and_update` for online learning. | +| `xgb.predict.predict_eta` | Same schema as the time polynomial model | Uses XGBoost’s native handling for missing optional fields. | + +Batch helpers (`batch_predict`) are available in every module when you need to score pandas DataFrames efficiently. + +--- + +## Example Workflow + +```python +from models.train_all_models import train_all_models +from models.common.registry import get_registry +from models.evaluation.leaderboard import quick_compare +from models.xgb.predict import predict_eta as predict_xgb + +# 1. Train baselines on pre-built parquet +train_all_models( + dataset_name="sample_dataset", + by_route=False, + model_types=["historical_mean", "polyreg_time", "xgboost"] +) + +# 2. Inspect registry and select best global model +registry = get_registry() +best_key = registry.get_best_model(metric="test_mae_seconds", route_id="global") + +# 3. Run inference +prediction = predict_xgb( + model_key=best_key, + distance_to_stop=1200, + hour=8, + is_peak_hour=True +) +print(prediction["eta_formatted"]) + +# 4. Compare candidates on a hold-out split +candidate_keys = registry.list_models().head(4)["model_key"].tolist() +leaderboard_df = quick_compare(candidate_keys, dataset_name="sample_dataset") +``` + +--- + +## Directory Structure + +``` +models/ +β”œβ”€β”€ common/ # Dataset + registry + metric utilities +β”œβ”€β”€ evaluation/ # Leaderboard + rolling validation + plotting +β”œβ”€β”€ historical_mean/ # Baseline mean model (train/predict) +β”œβ”€β”€ polyreg_distance/ # Distance-only polynomial regression +β”œβ”€β”€ polyreg_time/ # Distance + temporal/spatial regression +β”œβ”€β”€ ewma/ # Exponential smoothing model with online updates +β”œβ”€β”€ xgb/ # Gradient-boosted tree regressor +β”œβ”€β”€ train_all_models.py # CLI orchestrator +β”œβ”€β”€ example_workflow.py # Import barrel + quick-start helpers +β”œβ”€β”€ check_registry.py # Diagnostics for trained/ registry folder +└── trained/ # Auto-created artifacts (PKL + JSON + registry index) +``` + +Place datasets under `datasets/{name}.parquet`, run the feature-engineering builder beforehand, and execute the modeling scripts within the same Django/ORM environment as the ETA service so shared settings and caches are available. diff --git a/eta_prediction/models/__init__.py b/eta_prediction/models/__init__.py new file mode 100644 index 0000000..06633ae --- /dev/null +++ b/eta_prediction/models/__init__.py @@ -0,0 +1,196 @@ +""" +ETA Prediction Models Package + +Comprehensive modeling framework for transit ETA prediction. + +Example usage: + >>> from models import train_all_baselines, get_registry + >>> models = train_all_baselines("sample_dataset") + >>> registry = get_registry() + >>> best_key = registry.get_best_model() +""" + +__version__ = "1.0.0" +__author__ = "SIMOVI Team" + +# Core utilities +from .common.data import load_dataset, ETADataset, prepare_features_target +from .common.registry import get_registry, ModelRegistry +from .common.keys import ModelKey, PredictionKey +from .common.metrics import ( + compute_all_metrics, + mae_minutes, + rmse_minutes, + within_threshold +) +from .common.utils import ( + format_seconds, + haversine_distance, + clip_predictions, + setup_logging +) + +# Training functions +from .historical_mean.train import train_historical_mean, HistoricalMeanModel +from .polyreg_distance.train import train_polyreg_distance, PolyRegDistanceModel +from .polyreg_time.train import train_polyreg_time, PolyRegTimeModel +from .ewma.train import train_ewma, EWMAModel + +# Prediction functions +from .historical_mean.predict import predict_eta as predict_historical_mean +from .polyreg_distance.predict import predict_eta as predict_polyreg_distance +from .polyreg_time.predict import predict_eta as predict_polyreg_time +from .ewma.predict import predict_eta as predict_ewma + +# Evaluation +from .evaluation.leaderboard import ModelLeaderboard, quick_compare +from .evaluation.roll_validate import RollingValidator, quick_rolling_validate + +# Main training pipeline +# from .train_all_models import train_all_baselines, train_advanced_configurations +from .train_all_models import train_all_models + + +__all__ = [ + # Core utilities + 'load_dataset', + 'ETADataset', + 'prepare_features_target', + 'get_registry', + 'ModelRegistry', + 'ModelKey', + 'PredictionKey', + 'compute_all_metrics', + 'mae_minutes', + 'rmse_minutes', + 'within_threshold', + 'format_seconds', + 'haversine_distance', + 'clip_predictions', + 'setup_logging', + + # Models + 'HistoricalMeanModel', + 'PolyRegDistanceModel', + 'PolyRegTimeModel', + 'EWMAModel', + + # Training + 'train_historical_mean', + 'train_polyreg_distance', + 'train_polyreg_time', + 'train_ewma', + # 'train_all_baselines', + 'train_all_models' + 'train_advanced_configurations', + + # Prediction + 'predict_historical_mean', + 'predict_polyreg_distance', + 'predict_polyreg_time', + 'predict_ewma', + + # Evaluation + 'ModelLeaderboard', + 'quick_compare', + 'RollingValidator', + 'quick_rolling_validate', +] + + +# Package info +MODELS = { + 'historical_mean': { + 'description': 'Historical average baseline', + 'typical_mae': '2-4 minutes', + 'features': ['route_id', 'stop_sequence', 'temporal'] + }, + 'polyreg_distance': { + 'description': 'Polynomial regression on distance', + 'typical_mae': '1.5-3 minutes', + 'features': ['distance_to_stop'] + }, + 'polyreg_time': { + 'description': 'Polynomial regression with time features', + 'typical_mae': '1-2.5 minutes', + 'features': ['distance_to_stop', 'temporal', 'operational'] + }, + 'ewma': { + 'description': 'Exponentially weighted moving average', + 'typical_mae': '1.5-3 minutes', + 'features': ['route_id', 'stop_sequence', 'temporal'], + 'online_learning': True + } +} + + +def list_models(): + """List available model types.""" + print("\nAvailable Model Types:") + print("=" * 70) + for name, info in MODELS.items(): + print(f"\n{name}:") + print(f" Description: {info['description']}") + print(f" Typical MAE: {info['typical_mae']}") + print(f" Features: {', '.join(info['features'])}") + if info.get('online_learning'): + print(f" Online Learning: Yes") + print("\n" + "=" * 70) + + +def quick_start_guide(): + """Print quick start guide.""" + guide = """ + ETA Prediction Models - Quick Start + ==================================== + + 1. Train all baselines: + >>> from models import train_all_baselines + >>> results = train_all_baselines("sample_dataset") + + 2. Compare models: + >>> from models import quick_compare, get_registry + >>> registry = get_registry() + >>> model_keys = registry.list_models()['model_key'].tolist() + >>> comparison = quick_compare(model_keys) + + 3. Load and use best model: + >>> best_key = registry.get_best_model(metric='test_mae_seconds') + >>> model = registry.load_model(best_key) + >>> predictions = model.predict(your_data) + + 4. Make single prediction: + >>> from models import predict_polyreg_time + >>> result = predict_polyreg_time( + ... model_key=best_key, + ... distance_to_stop=1500.0, + ... hour=8, + ... is_peak_hour=True + ... ) + >>> print(f"ETA: {result['eta_formatted']}") + + For more details, see models/README.md + """ + print(guide) + + +# Auto-create directories +def _setup_directories(): + """Create necessary directories if they don't exist.""" + from pathlib import Path + base_dir = Path(__file__).parent + + dirs = [ + base_dir / 'trained', + base_dir.parent / 'datasets', + base_dir.parent / 'datasets' / 'metadata', + base_dir.parent / 'datasets' / 'production', + base_dir.parent / 'datasets' / 'experimental' + ] + + for dir_path in dirs: + dir_path.mkdir(parents=True, exist_ok=True) + + +# Run setup on import +_setup_directories() \ No newline at end of file diff --git a/eta_prediction/models/check_registry.py b/eta_prediction/models/check_registry.py new file mode 100644 index 0000000..01afcd7 --- /dev/null +++ b/eta_prediction/models/check_registry.py @@ -0,0 +1,198 @@ +""" +Diagnostic script to check model registry and troubleshoot saving issues. +""" + +import sys +from pathlib import Path +import json +import os + +sys.path.append(str(Path(__file__).parent)) + +from common.registry import get_registry + + +def check_registry_status(): + """Check registry status and list all models.""" + print("="*80) + print("MODEL REGISTRY DIAGNOSTICS".center(80)) + print("="*80 + "\n") + + # Get registry + registry = get_registry() + + # Check directory exists + print(f"Registry Location: {registry.base_dir}") + print(f"Directory exists: {os.path.exists(registry.base_dir)}") + print(f"Is writable: {os.access(registry.base_dir, os.W_OK)}") + + # List all files + if os.path.exists(registry.base_dir): + all_files = list(Path(registry.base_dir).rglob('*')) + print(f"\nTotal files in registry: {len(all_files)}") + + # Count by type + pkl_files = [f for f in all_files if f.suffix == '.pkl'] + json_files = [f for f in all_files if f.suffix == '.json'] + + print(f" - .pkl files: {len(pkl_files)}") + print(f" - .json files: {len(json_files)}") + + # Show directory structure + print("\nDirectory structure:") + for root, dirs, files in os.walk(registry.base_dir): + level = root.replace(str(registry.base_dir), '').count(os.sep) + indent = ' ' * 2 * level + print(f'{indent}{os.path.basename(root)}/') + subindent = ' ' * 2 * (level + 1) + for file in files[:10]: # Show first 10 files per dir + size_kb = os.path.getsize(os.path.join(root, file)) / 1024 + print(f'{subindent}{file} ({size_kb:.1f} KB)') + if len(files) > 10: + print(f'{subindent}... and {len(files) - 10} more files') + + # Get model list from registry + print("\n" + "="*80) + print("MODELS IN REGISTRY") + print("="*80 + "\n") + + models_df = registry.list_models() + + if models_df.empty: + print("❌ No models found in registry!") + print("\nPossible issues:") + print("1. Models not being saved (check save_model=True)") + print("2. Registry directory path incorrect") + print("3. Permission issues writing files") + print("4. Errors during model saving (check training logs)") + else: + print(f"βœ… Found {len(models_df)} models\n") + print(models_df.to_string()) + + # Show details of most recent model + print("\n" + "="*80) + print("MOST RECENT MODEL DETAILS") + print("="*80 + "\n") + + latest = models_df.iloc[0] + model_key = latest['model_key'] + + print(f"Model Key: {model_key}") + print(f"Type: {latest['model_type']}") + print(f"Saved: {latest['saved_at']}") + print(f"Dataset: {latest['dataset']}") + + # Try to load metadata + try: + metadata_path = Path(registry.base_dir) / f"{model_key}_metadata.json" + if metadata_path.exists(): + with open(metadata_path) as f: + metadata = json.load(f) + print("\nMetadata:") + print(json.dumps(metadata, indent=2)) + except Exception as e: + print(f"\n⚠️ Could not load metadata: {e}") + + # Check model file + model_path = Path(registry.base_dir) / f"{model_key}.pkl" + if model_path.exists(): + size_mb = model_path.stat().st_size / (1024 * 1024) + print(f"\nModel file: {model_path.name} ({size_mb:.2f} MB)") + else: + print(f"\n⚠️ Model file not found: {model_path}") + + +def test_save_load(): + """Test saving and loading a simple model.""" + print("\n" + "="*80) + print("SAVE/LOAD TEST") + print("="*80 + "\n") + + registry = get_registry() + + # Create a dummy model + test_model = {"type": "test", "value": 42} + test_metadata = { + "test": True, + "model_type": "diagnostic_test", + "dataset": "test_dataset", + "metrics": {"mae": 1.0} + } + test_key = "test_model_diagnostic_123" + + # Try to save + print("Attempting to save test model...") + try: + registry.save_model(test_key, test_model, test_metadata) + print("βœ… Save successful!") + + # Try to load + print("\nAttempting to load test model...") + loaded_model, loaded_metadata = registry.load_model(test_key) + + if loaded_model == test_model: + print("βœ… Load successful! Model matches.") + else: + print("⚠️ Load successful but model doesn't match.") + print(f" Expected: {test_model}") + print(f" Got: {loaded_model}") + + # Clean up + print("\nCleaning up test files...") + model_path = Path(registry.base_dir) / f"{test_key}.pkl" + metadata_path = Path(registry.base_dir) / f"{test_key}_metadata.json" + + if model_path.exists(): + model_path.unlink() + if metadata_path.exists(): + metadata_path.unlink() + + print("βœ… Test complete!") + + except Exception as e: + print(f"❌ Test failed: {e}") + import traceback + traceback.print_exc() + + +def check_model_type_distribution(): + """Show distribution of model types in registry.""" + registry = get_registry() + models_df = registry.list_models() + + if models_df.empty: + return + + print("\n" + "="*80) + print("MODEL TYPE DISTRIBUTION") + print("="*80 + "\n") + + type_counts = models_df['model_type'].value_counts() + + for model_type, count in type_counts.items(): + print(f"{model_type:30s} {count:3d} models") + + print(f"\n{'Total':30s} {len(models_df):3d} models") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Check model registry status") + parser.add_argument('--test', action='store_true', help='Run save/load test') + + args = parser.parse_args() + + # Always show status + check_registry_status() + + # Show distribution if models exist + check_model_type_distribution() + + # Optional test + if args.test: + test_save_load() + + print("\n" + "="*80) + print("DIAGNOSTICS COMPLETE".center(80)) + print("="*80) \ No newline at end of file diff --git a/eta_prediction/models/common/data.py b/eta_prediction/models/common/data.py new file mode 100644 index 0000000..a6a3788 --- /dev/null +++ b/eta_prediction/models/common/data.py @@ -0,0 +1,230 @@ +""" +Data loading and preprocessing utilities for ETA prediction models. +Handles dataset splitting, feature engineering, and train/test preparation. +""" + +import pandas as pd +import numpy as np +from pathlib import Path +from typing import Tuple, List, Optional, Dict +from datetime import datetime, timedelta + + +class ETADataset: + """ + Manages ETA prediction datasets with consistent preprocessing and splitting. + + Expected columns from VP dataset builder: + - Identifiers: trip_id, route_id, vehicle_id, stop_id, stop_sequence + - Position: vp_ts, vp_lat, vp_lon, vp_bearing + - Stop: stop_lat, stop_lon, distance_to_stop + - Target: actual_arrival, time_to_arrival_seconds + - Temporal: hour, day_of_week, is_weekend, is_holiday, is_peak_hour + - Operational: headway_seconds, current_speed_kmh + - Weather (optional): temperature_c, precipitation_mm, wind_speed_kmh + """ + + FEATURE_GROUPS = { + 'identifiers': ['trip_id', 'route_id', 'vehicle_id', 'stop_id', 'stop_sequence'], + 'position': ['vp_lat', 'vp_lon', 'vp_bearing', 'distance_to_stop'], + 'temporal': ['hour', 'day_of_week', 'is_weekend', 'is_holiday', 'is_peak_hour'], + 'operational': ['headway_seconds', 'current_speed_kmh'], + 'weather': ['temperature_c', 'precipitation_mm', 'wind_speed_kmh'], + 'target': ['time_to_arrival_seconds'] + } + + def __init__(self, data_path: str): + """ + Initialize dataset from parquet file. + + Args: + data_path: Path to parquet file from VP dataset builder + """ + self.data_path = Path(data_path) + self.df = pd.read_parquet(data_path) + self.df['vp_ts'] = pd.to_datetime(self.df['vp_ts']) + + # Store original size + self.original_size = len(self.df) + + def clean_data(self, + drop_missing_target: bool = True, + max_eta_seconds: float = 3600 * 2, # 2 hours + min_distance: float = 10.0) -> 'ETADataset': + """ + Clean dataset by removing invalid rows. + + Args: + drop_missing_target: Remove rows without valid ETA target + max_eta_seconds: Maximum reasonable ETA (filter outliers) + min_distance: Minimum distance to stop (meters) to keep + + Returns: + Self for chaining + """ + initial_rows = len(self.df) + + if drop_missing_target: + self.df = self.df.dropna(subset=['time_to_arrival_seconds']) + + # Filter outliers + self.df = self.df[ + (self.df['time_to_arrival_seconds'] >= 0) & + (self.df['time_to_arrival_seconds'] <= max_eta_seconds) + ] + + # Filter too-close stops (likely already passed) + if 'distance_to_stop' in self.df.columns: + self.df = self.df[self.df['distance_to_stop'] >= min_distance] + + print(f"Cleaned: {initial_rows} β†’ {len(self.df)} rows " + f"({100 * len(self.df) / initial_rows:.1f}% retained)") + + return self + + def get_features(self, feature_groups: List[str]) -> List[str]: + """ + Get list of feature columns from specified groups. + + Args: + feature_groups: List of group names (e.g., ['temporal', 'position']) + + Returns: + List of column names that exist in the dataset + """ + features = [] + for group in feature_groups: + if group in self.FEATURE_GROUPS: + features.extend(self.FEATURE_GROUPS[group]) + + # Return only columns that exist + return [f for f in features if f in self.df.columns] + + def temporal_split(self, + train_frac: float = 0.7, + val_frac: float = 0.15) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: + """ + Split dataset by time (avoids data leakage). + + Args: + train_frac: Fraction for training + val_frac: Fraction for validation (remainder goes to test) + + Returns: + train_df, val_df, test_df + """ + # Sort by timestamp + df_sorted = self.df.sort_values('vp_ts').reset_index(drop=True) + + n = len(df_sorted) + train_end = int(n * train_frac) + val_end = int(n * (train_frac + val_frac)) + + train_df = df_sorted.iloc[:train_end].copy() + val_df = df_sorted.iloc[train_end:val_end].copy() + test_df = df_sorted.iloc[val_end:].copy() + + print(f"Temporal split: train={len(train_df)}, val={len(val_df)}, test={len(test_df)}") + + return train_df, val_df, test_df + + def route_split(self, + test_routes: Optional[List[str]] = None, + test_frac: float = 0.2) -> Tuple[pd.DataFrame, pd.DataFrame]: + """ + Split by route for cross-route generalization testing. + + Args: + test_routes: Specific routes for test set, or None to sample + test_frac: Fraction of routes for testing if test_routes is None + + Returns: + train_df, test_df + """ + if test_routes is None: + routes = self.df['route_id'].unique() + n_test = max(1, int(len(routes) * test_frac)) + test_routes = np.random.choice(routes, size=n_test, replace=False) + + test_df = self.df[self.df['route_id'].isin(test_routes)].copy() + train_df = self.df[~self.df['route_id'].isin(test_routes)].copy() + + print(f"Route split: {len(test_routes)} test routes") + print(f" Train: {len(train_df)} samples") + print(f" Test: {len(test_df)} samples") + + return train_df, test_df + + def get_route_stats(self) -> pd.DataFrame: + """Get statistics per route.""" + return self.df.groupby('route_id').agg({ + 'time_to_arrival_seconds': ['count', 'mean', 'std'], + 'distance_to_stop': ['mean', 'std'], + 'trip_id': 'nunique' + }).round(2) + + def summary(self) -> Dict: + """Get dataset summary statistics.""" + return { + 'total_samples': len(self.df), + 'date_range': (self.df['vp_ts'].min(), self.df['vp_ts'].max()), + 'routes': self.df['route_id'].nunique(), + 'trips': self.df['trip_id'].nunique(), + 'vehicles': self.df['vehicle_id'].nunique(), + 'stops': self.df['stop_id'].nunique(), + 'eta_mean_minutes': self.df['time_to_arrival_seconds'].mean() / 60, + 'eta_std_minutes': self.df['time_to_arrival_seconds'].std() / 60, + 'missing_weather': self.df['temperature_c'].isna().sum() if 'temperature_c' in self.df.columns else 'N/A' + } + + +def load_dataset(dataset_name: str = "sample_dataset") -> ETADataset: + """ + Load dataset from datasets directory. + + Args: + dataset_name: Name of dataset (without .parquet extension) + + Returns: + ETADataset instance + """ + datasets_dir = Path(__file__).parent.parent.parent / "datasets" + data_path = datasets_dir / f"{dataset_name}.parquet" + + if not data_path.exists(): + raise FileNotFoundError(f"Dataset not found: {data_path}") + + return ETADataset(str(data_path)) + + +def prepare_features_target(df: pd.DataFrame, + feature_cols: List[str], + target_col: str = 'time_to_arrival_seconds', + fill_na: bool = True) -> Tuple[pd.DataFrame, pd.Series]: + """ + Extract features and target, handle missing values. + + Args: + df: DataFrame with features and target + feature_cols: List of feature column names + target_col: Name of target column + fill_na: Whether to fill NaN values + + Returns: + X (features), y (target) + """ + # Get features that exist + available_features = [f for f in feature_cols if f in df.columns] + + X = df[available_features].copy() + y = df[target_col].copy() + + if fill_na: + # Fill numeric features with median, boolean with False + for col in X.columns: + if X[col].dtype == 'bool': + X[col] = X[col].fillna(False) + elif X[col].dtype in ['int64', 'float64']: + X[col] = X[col].fillna(X[col].median()) + + return X, y \ No newline at end of file diff --git a/eta_prediction/models/common/keys.py b/eta_prediction/models/common/keys.py new file mode 100644 index 0000000..796b7f0 --- /dev/null +++ b/eta_prediction/models/common/keys.py @@ -0,0 +1,257 @@ +""" +Model key generation and identification utilities. +Ensures consistent naming and versioning across the modeling pipeline. +""" + +from typing import Dict, Optional +from datetime import datetime + + +class ModelKey: + """ + Generates unique, descriptive keys for trained models. + + Format: {model_type}_{dataset}_{features}_{route_id}_{timestamp} + Example: polyreg_distance_sample_temporal-position_route_1_20250126_143022 + polyreg_distance_sample_temporal-position_global_20250126_143022 + """ + + @staticmethod + def generate(model_type: str, + dataset_name: str, + feature_groups: list, + route_id: Optional[str] = None, + version: Optional[str] = None, + **kwargs) -> str: + """ + Generate a unique model key. + + Args: + model_type: Type of model (e.g., 'polyreg_distance', 'ewma') + dataset_name: Name of training dataset + feature_groups: List of feature group names used + route_id: Optional route ID for route-specific models + version: Optional version string, defaults to timestamp + **kwargs: Additional metadata to include in key + + Returns: + Unique model key string + """ + # Create features string + features_str = '-'.join(sorted(feature_groups)) + + # Create version string + if version is None: + version = datetime.now().strftime("%Y%m%d_%H%M%S") + + # Base key parts + key_parts = [ + model_type, + dataset_name, + features_str + ] + + # Add route scope (route-specific or global) + if route_id is not None: + key_parts.append(f"route_{route_id}") + else: + key_parts.append("global") + + # Add version + key_parts.append(version) + + # Add optional kwargs + for k, v in sorted(kwargs.items()): + if v is not None: + key_parts.append(f"{k}={v}") + + return '_'.join(key_parts) + + @staticmethod + def parse(key: str) -> Dict[str, str]: + """ + Parse a model key back into components. + + Args: + key: Model key string + + Returns: + Dictionary with parsed components + """ + parts = key.split('_') + + if len(parts) < 5: + raise ValueError(f"Invalid model key format: {key}") + + parsed = { + 'model_type': parts[0], + 'dataset': parts[1], + 'features': parts[2], + } + + # Parse route scope + if parts[3] == 'route' and len(parts) >= 5: + # Route-specific model: ..._route_1_20250126_143022 + parsed['route_id'] = parts[4] + parsed['scope'] = 'route' + parsed['version'] = '_'.join(parts[5:7]) if len(parts) >= 7 else parts[5] + extra_start = 7 + elif parts[3] == 'global': + # Global model: ..._global_20250126_143022 + parsed['route_id'] = None + parsed['scope'] = 'global' + parsed['version'] = '_'.join(parts[4:6]) if len(parts) >= 6 else parts[4] + extra_start = 6 + else: + # Legacy format without scope + parsed['route_id'] = None + parsed['scope'] = 'global' + parsed['version'] = '_'.join(parts[3:5]) if len(parts) >= 5 else parts[3] + extra_start = 5 + + # Parse additional kwargs (key=value format) + if len(parts) > extra_start: + for part in parts[extra_start:]: + if '=' in part: + k, v = part.split('=', 1) + parsed[k] = v + + return parsed + + @staticmethod + def is_route_specific(key: str) -> bool: + """ + Check if a model key is route-specific. + + Args: + key: Model key string + + Returns: + True if route-specific, False if global + """ + try: + parsed = ModelKey.parse(key) + return parsed.get('scope') == 'route' and parsed.get('route_id') is not None + except (ValueError, IndexError): + return False + + @staticmethod + def extract_route_id(key: str) -> Optional[str]: + """ + Extract route_id from a model key. + + Args: + key: Model key string + + Returns: + Route ID string or None if global model + """ + try: + parsed = ModelKey.parse(key) + return parsed.get('route_id') + except (ValueError, IndexError): + return None + + +class PredictionKey: + """ + Generates keys for prediction requests to enable caching and deduplication. + + Format: {route_id}_{stop_id}_{vp_hash} + """ + + @staticmethod + def generate(route_id: str, + stop_id: str, + vehicle_lat: float, + vehicle_lon: float, + timestamp: datetime, + stop_sequence: Optional[int] = None) -> str: + """ + Generate prediction key for a vehicle-stop pair. + + Args: + route_id: Route identifier + stop_id: Stop identifier + vehicle_lat: Vehicle latitude + vehicle_lon: Vehicle longitude + timestamp: Timestamp of vehicle position + stop_sequence: Optional stop sequence number + + Returns: + Prediction key string + """ + # Round coordinates to ~10m precision for caching + lat_rounded = round(vehicle_lat, 4) + lon_rounded = round(vehicle_lon, 4) + + # Create position hash + vp_hash = f"{lat_rounded},{lon_rounded}" + + # Build key + if stop_sequence is not None: + return f"{route_id}_{stop_id}_{stop_sequence}_{vp_hash}" + else: + return f"{route_id}_{stop_id}_{vp_hash}" + + +class ExperimentKey: + """ + Generates keys for experiments and model comparisons. + """ + + @staticmethod + def generate(experiment_name: str, + models: list, + dataset: str, + timestamp: Optional[str] = None) -> str: + """ + Generate experiment key. + + Args: + experiment_name: Name of experiment + models: List of model types being compared + dataset: Dataset name + timestamp: Optional timestamp, defaults to now + + Returns: + Experiment key string + """ + if timestamp is None: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + + models_str = '-'.join(sorted(models)) + + return f"exp_{experiment_name}_{models_str}_{dataset}_{timestamp}" + + +def model_filename(model_key: str, extension: str = "pkl") -> str: + """ + Generate consistent filename for model artifacts. + + Args: + model_key: Model key from ModelKey.generate() + extension: File extension (pkl, joblib, json, etc.) + + Returns: + Filename string + """ + return f"{model_key}.{extension}" + + +def validate_model_key(key: str) -> bool: + """ + Validate that a string is a properly formatted model key. + + Args: + key: String to validate + + Returns: + True if valid, False otherwise + """ + try: + parsed = ModelKey.parse(key) + required = ['model_type', 'dataset', 'features', 'version'] + return all(k in parsed for k in required) + except (ValueError, IndexError): + return False \ No newline at end of file diff --git a/eta_prediction/models/common/metrics.py b/eta_prediction/models/common/metrics.py new file mode 100644 index 0000000..70814de --- /dev/null +++ b/eta_prediction/models/common/metrics.py @@ -0,0 +1,245 @@ +""" +Evaluation metrics for ETA prediction models. +Provides domain-specific metrics beyond standard regression metrics. +""" + +import numpy as np +import pandas as pd +from typing import Dict, Optional, Tuple +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + + +def mae_seconds(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """Mean Absolute Error in seconds.""" + return mean_absolute_error(y_true, y_pred) + + +def mae_minutes(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """Mean Absolute Error in minutes.""" + return mean_absolute_error(y_true, y_pred) / 60 + + +def rmse_seconds(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """Root Mean Squared Error in seconds.""" + return np.sqrt(mean_squared_error(y_true, y_pred)) + + +def rmse_minutes(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """Root Mean Squared Error in minutes.""" + return np.sqrt(mean_squared_error(y_true, y_pred)) / 60 + + +def mape(y_true: np.ndarray, y_pred: np.ndarray, epsilon: float = 1.0) -> float: + """ + Mean Absolute Percentage Error. + + Args: + y_true: True values + y_pred: Predicted values + epsilon: Small value to avoid division by zero + + Returns: + MAPE as percentage + """ + return 100 * np.mean(np.abs((y_true - y_pred) / (y_true + epsilon))) + + +def median_ae(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """Median Absolute Error in seconds (robust to outliers).""" + return np.median(np.abs(y_true - y_pred)) + + +def within_threshold(y_true: np.ndarray, + y_pred: np.ndarray, + threshold_seconds: float = 60) -> float: + """ + Fraction of predictions within threshold. + + Args: + y_true: True values in seconds + y_pred: Predicted values in seconds + threshold_seconds: Acceptable error threshold + + Returns: + Fraction of predictions within threshold (0-1) + """ + errors = np.abs(y_true - y_pred) + return np.mean(errors <= threshold_seconds) + + +def late_penalty_mae(y_true: np.ndarray, + y_pred: np.ndarray, + late_multiplier: float = 2.0) -> float: + """ + MAE with higher penalty for late predictions (user-centric). + + Args: + y_true: True values + y_pred: Predicted values + late_multiplier: Multiplier for underprediction errors + + Returns: + Weighted MAE + """ + errors = y_pred - y_true + weights = np.where(errors < 0, late_multiplier, 1.0) + return np.mean(np.abs(errors) * weights) + + +def quantile_error(y_true: np.ndarray, + y_pred: np.ndarray, + quantiles: list = [0.5, 0.9, 0.95]) -> Dict[str, float]: + """ + Absolute errors at different quantiles. + + Args: + y_true: True values + y_pred: Predicted values + quantiles: List of quantiles to compute + + Returns: + Dictionary mapping quantile to error + """ + errors = np.abs(y_true - y_pred) + return {f"q{int(q*100)}": np.quantile(errors, q) for q in quantiles} + + +def bias(y_true: np.ndarray, y_pred: np.ndarray) -> float: + """ + Mean bias (positive = overprediction, negative = underprediction). + + Returns: + Mean bias in seconds + """ + return np.mean(y_pred - y_true) + + +def compute_all_metrics(y_true: np.ndarray, + y_pred: np.ndarray, + prefix: str = "") -> Dict[str, float]: + """ + Compute comprehensive set of metrics. + + Args: + y_true: True values in seconds + y_pred: Predicted values in seconds + prefix: Optional prefix for metric names (e.g., "val_") + + Returns: + Dictionary of all metrics + """ + metrics = { + f"{prefix}mae_seconds": mae_seconds(y_true, y_pred), + f"{prefix}mae_minutes": mae_minutes(y_true, y_pred), + f"{prefix}rmse_seconds": rmse_seconds(y_true, y_pred), + f"{prefix}rmse_minutes": rmse_minutes(y_true, y_pred), + f"{prefix}mape": mape(y_true, y_pred), + f"{prefix}median_ae": median_ae(y_true, y_pred), + f"{prefix}bias_seconds": bias(y_true, y_pred), + f"{prefix}r2": r2_score(y_true, y_pred), + f"{prefix}within_60s": within_threshold(y_true, y_pred, 60), + f"{prefix}within_120s": within_threshold(y_true, y_pred, 120), + f"{prefix}within_300s": within_threshold(y_true, y_pred, 300), + f"{prefix}late_penalty_mae": late_penalty_mae(y_true, y_pred), + } + + # Add quantile errors + quantile_errs = quantile_error(y_true, y_pred) + for k, v in quantile_errs.items(): + metrics[f"{prefix}error_{k}"] = v + + return metrics + + +def compare_models(results: Dict[str, Dict[str, float]], + metric: str = "mae_seconds") -> pd.DataFrame: + """ + Compare multiple models on a metric. + + Args: + results: Dict mapping model_name -> metrics_dict + metric: Metric to compare (or 'all' for all metrics) + + Returns: + DataFrame with comparison + """ + if metric == 'all': + df = pd.DataFrame(results).T + else: + df = pd.DataFrame({ + 'model': list(results.keys()), + metric: [results[m].get(metric, np.nan) for m in results.keys()] + }) + df = df.sort_values(metric) + + return df + + +def error_analysis(y_true: np.ndarray, + y_pred: np.ndarray, + feature_df: Optional[pd.DataFrame] = None, + group_by: Optional[str] = None) -> pd.DataFrame: + """ + Analyze errors by different segments. + + Args: + y_true: True values + y_pred: Predicted values + feature_df: DataFrame with features for grouping + group_by: Column name to group by + + Returns: + DataFrame with error statistics per group + """ + errors = np.abs(y_true - y_pred) + + if feature_df is None or group_by is None: + # Overall statistics + return pd.DataFrame({ + 'count': [len(errors)], + 'mae': [np.mean(errors)], + 'median_ae': [np.median(errors)], + 'rmse': [np.sqrt(np.mean(errors**2))], + 'max_error': [np.max(errors)], + }) + + # Group-wise statistics + df = feature_df.copy() + df['error'] = errors + + stats = df.groupby(group_by)['error'].agg([ + ('count', 'count'), + ('mae', 'mean'), + ('median_ae', 'median'), + ('std', 'std'), + ('max', 'max') + ]).round(2) + + return stats.sort_values('mae', ascending=False) + + +def prediction_intervals(y_pred: np.ndarray, + residuals: np.ndarray, + confidence: float = 0.95) -> Tuple[np.ndarray, np.ndarray]: + """ + Compute prediction intervals based on residual distribution. + + Args: + y_pred: Point predictions + residuals: Training residuals (y_true - y_pred) + confidence: Confidence level (e.g., 0.95 for 95% interval) + + Returns: + lower_bound, upper_bound arrays + """ + alpha = 1 - confidence + lower_q = alpha / 2 + upper_q = 1 - alpha / 2 + + lower_percentile = np.percentile(residuals, lower_q * 100) + upper_percentile = np.percentile(residuals, upper_q * 100) + + lower_bound = y_pred + lower_percentile + upper_bound = y_pred + upper_percentile + + return lower_bound, upper_bound \ No newline at end of file diff --git a/eta_prediction/models/common/registry.py b/eta_prediction/models/common/registry.py new file mode 100644 index 0000000..fa9bd02 --- /dev/null +++ b/eta_prediction/models/common/registry.py @@ -0,0 +1,379 @@ +""" +Model registry for managing trained models and their metadata. +Provides save/load functionality with consistent structure. +""" + +import json +import pickle +from pathlib import Path +from typing import Dict, Any, Optional, List +from datetime import datetime +import pandas as pd + + +class ModelRegistry: + """ + Manages model artifacts and metadata in a structured directory. + + Structure: + models/ + β”œβ”€β”€ trained/ + β”‚ β”œβ”€β”€ {model_key}.pkl # Serialized model + β”‚ └── {model_key}_meta.json # Model metadata + └── registry.json # Index of all models + """ + + def __init__(self, base_dir: str = "models/trained"): + """ + Initialize registry. + + Args: + base_dir: Base directory for model storage + """ + self.base_dir = Path(base_dir) + self.base_dir.mkdir(parents=True, exist_ok=True) + + self.registry_file = self.base_dir / "registry.json" + self._load_registry() + + def _load_registry(self): + """Load registry index from disk.""" + if self.registry_file.exists(): + with open(self.registry_file, 'r') as f: + self.registry = json.load(f) + else: + self.registry = {} + + def _save_registry(self): + """Save registry index to disk.""" + with open(self.registry_file, 'w') as f: + json.dump(self.registry, f, indent=2) + + def save_model(self, + model_key: str, + model: Any, + metadata: Dict[str, Any], + overwrite: bool = False) -> Path: + """ + Save model and metadata to registry. + + Args: + model_key: Unique model identifier + model: Trained model object (must be picklable) + metadata: Model metadata (training metrics, config, etc.) + overwrite: Whether to overwrite existing model + + Returns: + Path to saved model file + """ + model_path = self.base_dir / f"{model_key}.pkl" + meta_path = self.base_dir / f"{model_key}_meta.json" + + if model_path.exists() and not overwrite: + raise FileExistsError(f"Model {model_key} already exists. Set overwrite=True to replace.") + + # Save model artifact + with open(model_path, 'wb') as f: + pickle.dump(model, f) + + # Enrich metadata + metadata['model_key'] = model_key + metadata['saved_at'] = datetime.now().isoformat() + metadata['model_path'] = str(model_path) + + # Save metadata + with open(meta_path, 'w') as f: + json.dump(metadata, f, indent=2) + + # Update registry + self.registry[model_key] = { + 'model_path': str(model_path), + 'meta_path': str(meta_path), + 'saved_at': metadata['saved_at'], + 'model_type': metadata.get('model_type', 'unknown'), + 'route_id': metadata.get('route_id'), # Track route scope + 'dataset': metadata.get('dataset', 'unknown') + } + self._save_registry() + + route_info = f" (route: {metadata.get('route_id')})" if metadata.get('route_id') else " (global)" + print(f"βœ“ Saved model: {model_key}{route_info}") + return model_path + + def load_model(self, model_key: str) -> Any: + """ + Load model from registry. + + Args: + model_key: Unique model identifier + + Returns: + Loaded model object + """ + if model_key not in self.registry: + raise KeyError(f"Model {model_key} not found in registry") + + model_path = Path(self.registry[model_key]['model_path']) + + with open(model_path, 'rb') as f: + model = pickle.load(f) + + return model + + def load_metadata(self, model_key: str) -> Dict[str, Any]: + """ + Load model metadata. + + Args: + model_key: Unique model identifier + + Returns: + Metadata dictionary + """ + if model_key not in self.registry: + raise KeyError(f"Model {model_key} not found in registry") + + meta_path = Path(self.registry[model_key]['meta_path']) + + with open(meta_path, 'r') as f: + metadata = json.load(f) + + return metadata + + def list_models(self, + model_type: Optional[str] = None, + route_id: Optional[str] = None, + sort_by: str = 'saved_at') -> pd.DataFrame: + """ + List all models in registry. + + Args: + model_type: Filter by model type (e.g., 'polyreg_distance') + route_id: Filter by route_id (None for global models, 'all' for all models) + sort_by: Column to sort by + + Returns: + DataFrame with model information + """ + models = [] + + for key, info in self.registry.items(): + # Filter by model type + if model_type and info.get('model_type') != model_type: + continue + + # Filter by route_id + model_route_id = info.get('route_id') + if route_id is not None and route_id != 'all': + if route_id == 'global' and model_route_id is not None: + continue + elif route_id != 'global' and model_route_id != route_id: + continue + + # Load metadata for richer info + try: + meta = self.load_metadata(key) + models.append({ + 'model_key': key, + 'model_type': info.get('model_type', 'unknown'), + 'route_id': model_route_id or 'global', + 'saved_at': info['saved_at'], + 'dataset': meta.get('dataset', 'unknown'), + 'n_samples': meta.get('n_samples'), + 'mae_seconds': meta.get('metrics', {}).get('test_mae_seconds', None), + 'mae_minutes': meta.get('metrics', {}).get('test_mae_minutes', None), + 'rmse_seconds': meta.get('metrics', {}).get('test_rmse_seconds', None), + 'r2': meta.get('metrics', {}).get('test_r2', None), + }) + except Exception as e: + print(f"Warning: Could not load metadata for {key}: {e}") + models.append({ + 'model_key': key, + 'model_type': info.get('model_type', 'unknown'), + 'route_id': model_route_id or 'global', + 'saved_at': info['saved_at'], + }) + + df = pd.DataFrame(models) + if not df.empty and sort_by in df.columns: + df = df.sort_values(sort_by, ascending=False) + + return df + + def delete_model(self, model_key: str) -> bool: + """ + Delete model and metadata from registry. + + Args: + model_key: Unique model identifier + + Returns: + True if deleted successfully + """ + if model_key not in self.registry: + raise KeyError(f"Model {model_key} not found in registry") + + # Delete files + model_path = Path(self.registry[model_key]['model_path']) + meta_path = Path(self.registry[model_key]['meta_path']) + + if model_path.exists(): + model_path.unlink() + if meta_path.exists(): + meta_path.unlink() + + # Remove from registry + del self.registry[model_key] + self._save_registry() + + print(f"βœ“ Deleted model: {model_key}") + return True + + def get_best_model(self, + model_type: Optional[str] = None, + route_id: Optional[str] = None, + metric: str = 'test_mae_seconds', + minimize: bool = True) -> Optional[str]: + """ + Get best model by metric. + + Args: + model_type: Filter by model type + route_id: Filter by route_id (None = prefer route-specific if exists, else global) + metric: Metric to optimize (e.g., 'test_mae_seconds', 'test_rmse_seconds') + minimize: Whether to minimize (True) or maximize (False) metric + + Returns: + Model key of best model, or None if no models found + """ + candidates = [] + + for key in self.registry.keys(): + # Filter by model type + if model_type and self.registry[key].get('model_type') != model_type: + continue + + # Filter by route + model_route_id = self.registry[key].get('route_id') + + if route_id is not None: + # Explicit route requested + if route_id == 'global' and model_route_id is not None: + continue + elif route_id != 'global' and model_route_id != route_id: + continue + # If route_id is None, we'll prefer route-specific in the sorting logic + + try: + meta = self.load_metadata(key) + metric_value = meta.get('metrics', {}).get(metric) + + if metric_value is not None: + candidates.append({ + 'key': key, + 'metric_value': metric_value, + 'route_id': model_route_id, + 'is_route_specific': model_route_id is not None + }) + except Exception: + continue + + if not candidates: + return None + + # Sort by metric, with preference for route-specific when route_id is None + if route_id is None: + # Smart routing: prefer route-specific models if they exist + route_specific = [c for c in candidates if c['is_route_specific']] + global_models = [c for c in candidates if not c['is_route_specific']] + + # If route-specific models exist, use them; otherwise fall back to global + candidates_to_sort = route_specific if route_specific else global_models + else: + candidates_to_sort = candidates + + # Sort by metric value + candidates_to_sort.sort(key=lambda x: x['metric_value'], reverse=not minimize) + + return candidates_to_sort[0]['key'] if candidates_to_sort else None + + def get_routes(self, model_type: Optional[str] = None) -> List[str]: + """ + Get list of all routes that have trained models. + + Args: + model_type: Filter by model type + + Returns: + List of route IDs (excludes None/global) + """ + routes = set() + + for key, info in self.registry.items(): + if model_type and info.get('model_type') != model_type: + continue + + route_id = info.get('route_id') + if route_id is not None: + routes.add(route_id) + + return sorted(list(routes)) + + def compare_routes(self, + model_type: str, + metric: str = 'test_mae_minutes') -> pd.DataFrame: + """ + Compare model performance across routes. + + Args: + model_type: Model type to compare + metric: Metric to display + + Returns: + DataFrame with route comparison + """ + results = [] + + routes = self.get_routes(model_type) + + # Add global model if exists + global_key = self.get_best_model(model_type=model_type, route_id='global', metric=f'test_{metric}') + if global_key: + meta = self.load_metadata(global_key) + results.append({ + 'route_id': 'global', + 'n_samples': meta.get('n_samples'), + 'n_trips': meta.get('n_trips'), + metric: meta.get('metrics', {}).get(f'test_{metric}'), + 'model_key': global_key + }) + + # Add route-specific models + for route_id in routes: + best_key = self.get_best_model(model_type=model_type, route_id=route_id, metric=f'test_{metric}') + if best_key: + meta = self.load_metadata(best_key) + results.append({ + 'route_id': route_id, + 'n_samples': meta.get('n_samples'), + 'n_trips': meta.get('n_trips'), + metric: meta.get('metrics', {}).get(f'test_{metric}'), + 'model_key': best_key + }) + + df = pd.DataFrame(results) + if not df.empty: + df = df.sort_values('n_trips', ascending=False) + + return df + + +# Global registry instance +_registry = None + +def get_registry() -> ModelRegistry: + """Get or create global registry instance.""" + global _registry + if _registry is None: + _registry = ModelRegistry() + return _registry \ No newline at end of file diff --git a/eta_prediction/models/common/utils.py b/eta_prediction/models/common/utils.py new file mode 100644 index 0000000..0f1566a --- /dev/null +++ b/eta_prediction/models/common/utils.py @@ -0,0 +1,276 @@ +""" +Utility functions for models package. +""" + +import numpy as np +import pandas as pd +from typing import Any, Dict, List, Optional +from datetime import datetime +import logging + + +def setup_logging(name: str = "eta_models", level: str = "INFO") -> logging.Logger: + """ + Setup consistent logging for models. + + Args: + name: Logger name + level: Logging level + + Returns: + Configured logger + """ + logger = logging.getLogger(name) + logger.setLevel(getattr(logging, level)) + + if not logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + +def safe_divide(numerator: np.ndarray, + denominator: np.ndarray, + fill_value: float = 0.0) -> np.ndarray: + """ + Safe division that handles division by zero. + + Args: + numerator: Numerator array + denominator: Denominator array + fill_value: Value to use when denominator is zero + + Returns: + Result array + """ + result = np.full_like(numerator, fill_value, dtype=float) + mask = denominator != 0 + result[mask] = numerator[mask] / denominator[mask] + return result + + +def clip_predictions(predictions: np.ndarray, + min_value: float = 0.0, + max_value: float = 7200.0) -> np.ndarray: + """ + Clip predictions to reasonable range. + + Args: + predictions: Raw predictions + min_value: Minimum ETA (seconds) + max_value: Maximum ETA (seconds, default 2 hours) + + Returns: + Clipped predictions + """ + return np.clip(predictions, min_value, max_value) + + +def add_lag_features(df: pd.DataFrame, + columns: List[str], + lags: List[int], + group_by: Optional[str] = None) -> pd.DataFrame: + """ + Add lagged features to dataframe. + + Args: + df: Input dataframe + columns: Columns to lag + lags: List of lag values (e.g., [1, 2, 3]) + group_by: Optional column to group by before lagging + + Returns: + DataFrame with lag features added + """ + df_copy = df.copy() + + for col in columns: + for lag in lags: + lag_col_name = f"{col}_lag{lag}" + + if group_by: + df_copy[lag_col_name] = df_copy.groupby(group_by)[col].shift(lag) + else: + df_copy[lag_col_name] = df_copy[col].shift(lag) + + return df_copy + + +def smooth_predictions(predictions: np.ndarray, + window_size: int = 3, + method: str = 'ewma', + alpha: float = 0.3) -> np.ndarray: + """ + Smooth predictions using moving average. + + Args: + predictions: Array of predictions + window_size: Window size for smoothing + method: 'mean', 'median', or 'ewma' + alpha: Alpha parameter for EWMA + + Returns: + Smoothed predictions + """ + if len(predictions) < window_size: + return predictions + + if method == 'mean': + return pd.Series(predictions).rolling(window_size, min_periods=1).mean().values + elif method == 'median': + return pd.Series(predictions).rolling(window_size, min_periods=1).median().values + elif method == 'ewma': + return pd.Series(predictions).ewm(alpha=alpha).mean().values + else: + raise ValueError(f"Unknown smoothing method: {method}") + + +def calculate_speed_kmh(distance_m: float, time_s: float) -> float: + """ + Calculate speed in km/h from distance and time. + + Args: + distance_m: Distance in meters + time_s: Time in seconds + + Returns: + Speed in km/h + """ + if time_s <= 0: + return 0.0 + return (distance_m / 1000) / (time_s / 3600) + + +def haversine_distance(lat1: float, lon1: float, + lat2: float, lon2: float) -> float: + """ + Calculate great-circle distance between two points. + + Args: + lat1, lon1: First point coordinates + lat2, lon2: Second point coordinates + + Returns: + Distance in meters + """ + R = 6371000 # Earth radius in meters + + phi1 = np.radians(lat1) + phi2 = np.radians(lat2) + delta_phi = np.radians(lat2 - lat1) + delta_lambda = np.radians(lon2 - lon1) + + a = np.sin(delta_phi / 2) ** 2 + \ + np.cos(phi1) * np.cos(phi2) * np.sin(delta_lambda / 2) ** 2 + c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) + + return R * c + + +def format_seconds(seconds: float) -> str: + """ + Format seconds as human-readable string. + + Args: + seconds: Time in seconds + + Returns: + Formatted string (e.g., "2m 30s", "1h 15m") + """ + if seconds < 60: + return f"{int(seconds)}s" + elif seconds < 3600: + minutes = int(seconds / 60) + secs = int(seconds % 60) + return f"{minutes}m {secs}s" + else: + hours = int(seconds / 3600) + minutes = int((seconds % 3600) / 60) + return f"{hours}h {minutes}m" + + +def print_metrics_table(metrics: Dict[str, float], title: str = "Metrics"): + """ + Pretty print metrics as a table. + + Args: + metrics: Dictionary of metric names to values + title: Table title + """ + print(f"\n{'='*50}") + print(f"{title:^50}") + print(f"{'='*50}") + + for name, value in metrics.items(): + if isinstance(value, float): + print(f"{name:.<40} {value:.4f}") + else: + print(f"{name:.<40} {value}") + + print(f"{'='*50}\n") + + +def train_test_summary(train_df: pd.DataFrame, + test_df: pd.DataFrame, + val_df: Optional[pd.DataFrame] = None): + """ + Print summary of train/test split. + + Args: + train_df: Training dataframe + test_df: Test dataframe + val_df: Optional validation dataframe + """ + print("\n" + "="*60) + print("DATASET SPLIT SUMMARY".center(60)) + print("="*60) + + print(f"\nTrain: {len(train_df):,} samples") + print(f" Date range: {train_df['vp_ts'].min()} to {train_df['vp_ts'].max()}") + print(f" Routes: {train_df['route_id'].nunique()}") + print(f" Mean ETA: {train_df['time_to_arrival_seconds'].mean()/60:.1f} min") + + if val_df is not None: + print(f"\nValidation: {len(val_df):,} samples") + print(f" Date range: {val_df['vp_ts'].min()} to {val_df['vp_ts'].max()}") + print(f" Routes: {val_df['route_id'].nunique()}") + print(f" Mean ETA: {val_df['time_to_arrival_seconds'].mean()/60:.1f} min") + + print(f"\nTest: {len(test_df):,} samples") + print(f" Date range: {test_df['vp_ts'].min()} to {test_df['vp_ts'].max()}") + print(f" Routes: {test_df['route_id'].nunique()}") + print(f" Mean ETA: {test_df['time_to_arrival_seconds'].mean()/60:.1f} min") + + print("="*60 + "\n") + + +def create_feature_importance_df(feature_names: List[str], + importances: np.ndarray, + top_n: int = 20) -> pd.DataFrame: + """ + Create sorted feature importance dataframe. + + Args: + feature_names: List of feature names + importances: Array of importance values + top_n: Number of top features to return + + Returns: + DataFrame sorted by importance + """ + df = pd.DataFrame({ + 'feature': feature_names, + 'importance': importances + }) + + df = df.sort_values('importance', ascending=False) + + if top_n: + df = df.head(top_n) + + return df \ No newline at end of file diff --git a/eta_prediction/models/evaluation/leaderboard.py b/eta_prediction/models/evaluation/leaderboard.py new file mode 100644 index 0000000..292a9f4 --- /dev/null +++ b/eta_prediction/models/evaluation/leaderboard.py @@ -0,0 +1,229 @@ +""" +Model leaderboard for comparing performance across models. +""" + +import pandas as pd +import numpy as np +from typing import List, Optional, Dict +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.data import load_dataset +from common.metrics import compute_all_metrics + + +class ModelLeaderboard: + """ + Compare multiple models on standardized test sets. + """ + + def __init__(self): + self.registry = get_registry() + self.results = [] + + def evaluate_model(self, + model_key: str, + test_df: pd.DataFrame, + target_col: str = 'time_to_arrival_seconds') -> Dict: + """ + Evaluate single model on test set. + + Args: + model_key: Model identifier + test_df: Test dataframe + target_col: Target column name + + Returns: + Dictionary with metrics + """ + print(f"Evaluating {model_key}...") + + # Load model and metadata + model = self.registry.load_model(model_key) + metadata = self.registry.load_metadata(model_key) + + # Predict + y_true = test_df[target_col].values + y_pred = model.predict(test_df) + + # Compute metrics + metrics = compute_all_metrics(y_true, y_pred) + + # Add model info + result = { + 'model_key': model_key, + 'model_type': metadata.get('model_type', 'unknown'), + 'dataset': metadata.get('dataset', 'unknown'), + **metrics + } + + return result + + def compare_models(self, + model_keys: List[str], + dataset_name: str = "sample_dataset", + test_routes: Optional[List[str]] = None) -> pd.DataFrame: + """ + Compare multiple models on same test set. + + Args: + model_keys: List of model keys to compare + dataset_name: Dataset to evaluate on + test_routes: Specific routes for testing + + Returns: + DataFrame with comparison results + """ + print(f"\n{'='*60}") + print(f"MODEL LEADERBOARD".center(60)) + print(f"{'='*60}\n") + print(f"Dataset: {dataset_name}") + print(f"Models: {len(model_keys)}\n") + + # Load dataset + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Get test set + if test_routes: + _, test_df = dataset.route_split(test_routes=test_routes) + else: + _, _, test_df = dataset.temporal_split(train_frac=0.7, val_frac=0.15) + + print(f"Test set: {len(test_df)} samples\n") + + # Evaluate each model + results = [] + for model_key in model_keys: + try: + result = self.evaluate_model(model_key, test_df) + results.append(result) + except Exception as e: + print(f" Error evaluating {model_key}: {e}") + + # Create comparison dataframe + df = pd.DataFrame(results) + + # Sort by MAE (primary metric) + if 'mae_seconds' in df.columns: + df = df.sort_values('mae_seconds') + + self.results = results + + return df + + def print_leaderboard(self, + df: pd.DataFrame, + metrics: List[str] = ['mae_minutes', 'rmse_minutes', 'r2', + 'within_60s', 'bias_seconds']): + """ + Pretty print leaderboard. + + Args: + df: Results dataframe + metrics: Metrics to display + """ + print(f"\n{'='*80}") + print(f"LEADERBOARD RESULTS".center(80)) + print(f"{'='*80}\n") + + # Select columns + display_cols = ['model_type'] + [m for m in metrics if m in df.columns] + display_df = df[display_cols].copy() + + # Format numbers + for col in metrics: + if col in display_df.columns: + if col.startswith('within_'): + display_df[col] = (display_df[col] * 100).round(1).astype(str) + '%' + else: + display_df[col] = display_df[col].round(3) + + # Add rank + display_df.insert(0, 'rank', range(1, len(display_df) + 1)) + + print(display_df.to_string(index=False)) + print(f"\n{'='*80}\n") + + # Highlight winner + winner = df.iloc[0] + print(f"πŸ† Best Model: {winner['model_type']}") + print(f" MAE: {winner['mae_minutes']:.3f} minutes") + print(f" RMSE: {winner['rmse_minutes']:.3f} minutes") + print(f" RΒ²: {winner['r2']:.3f}") + + def model_comparison_summary(self, df: pd.DataFrame) -> str: + """ + Generate text summary of model comparison. + + Args: + df: Results dataframe + + Returns: + Summary string + """ + best = df.iloc[0] + worst = df.iloc[-1] + + improvement = (worst['mae_seconds'] - best['mae_seconds']) / worst['mae_seconds'] * 100 + + summary = f""" +Model Comparison Summary +======================== + +Total Models Evaluated: {len(df)} +Test Samples: {df.iloc[0].get('test_samples', 'N/A')} + +Best Model: {best['model_type']} + - MAE: {best['mae_minutes']:.2f} minutes + - RMSE: {best['rmse_minutes']:.2f} minutes + - RΒ²: {best['r2']:.3f} + - Within 60s: {best['within_60s']*100:.1f}% + +Baseline (Worst): {worst['model_type']} + - MAE: {worst['mae_minutes']:.2f} minutes + +Improvement: {improvement:.1f}% reduction in MAE from baseline to best model. +""" + return summary + + +def quick_compare(model_keys: List[str], + dataset_name: str = "sample_dataset") -> pd.DataFrame: + """ + Quick comparison function. + + Args: + model_keys: List of model keys + dataset_name: Dataset name + + Returns: + Comparison dataframe + """ + leaderboard = ModelLeaderboard() + df = leaderboard.compare_models(model_keys, dataset_name) + leaderboard.print_leaderboard(df) + + return df + + +if __name__ == "__main__": + # Example: Compare all model types + + # You would need to train these first + model_keys = [ + "historical_mean_sample_dataset_temporal-route_20250126_143022", + "polyreg_distance_sample_dataset_distance_20250126_143022_degree=2", + "polyreg_time_sample_dataset_distance-operational-temporal_20250126_143022_degree=2", + "ewma_sample_dataset_temporal-route_20250126_143022_alpha=0_3" + ] + + # Run comparison + results_df = quick_compare(model_keys) + + # Save results + results_df.to_csv("models/leaderboard_results.csv", index=False) + print("\nResults saved to models/leaderboard_results.csv") \ No newline at end of file diff --git a/eta_prediction/models/evaluation/roll_validate.py b/eta_prediction/models/evaluation/roll_validate.py new file mode 100644 index 0000000..4df4837 --- /dev/null +++ b/eta_prediction/models/evaluation/roll_validate.py @@ -0,0 +1,260 @@ +""" +Rolling window (walk-forward) validation for time series models. +Evaluates model performance over time with realistic train/test splits. +""" + +import pandas as pd +import numpy as np +from typing import Dict, List, Callable, Optional +from datetime import timedelta +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset +from common.metrics import compute_all_metrics +from common.utils import print_metrics_table + + +class RollingValidator: + """ + Perform rolling window validation on time series data. + """ + + def __init__(self, + train_window_days: int = 7, + test_window_days: int = 1, + step_days: int = 1): + """ + Initialize validator. + + Args: + train_window_days: Size of training window in days + test_window_days: Size of test window in days + step_days: Step size between windows + """ + self.train_window = timedelta(days=train_window_days) + self.test_window = timedelta(days=test_window_days) + self.step = timedelta(days=step_days) + + self.results = [] + + def validate(self, + dataset_name: str, + train_fn: Callable, + predict_fn: Callable, + target_col: str = 'time_to_arrival_seconds') -> pd.DataFrame: + """ + Perform rolling window validation. + + Args: + dataset_name: Dataset to validate on + train_fn: Function(train_df) -> model + predict_fn: Function(model, test_df) -> predictions + target_col: Target column name + + Returns: + DataFrame with results per window + """ + print(f"\n{'='*60}") + print(f"ROLLING WINDOW VALIDATION".center(60)) + print(f"{'='*60}\n") + print(f"Train window: {self.train_window.days} days") + print(f"Test window: {self.test_window.days} days") + print(f"Step size: {self.step.days} days\n") + + # Load dataset + dataset = load_dataset(dataset_name) + dataset.clean_data() + + df = dataset.df.sort_values('vp_ts').reset_index(drop=True) + + # Get date range + start_date = df['vp_ts'].min() + end_date = df['vp_ts'].max() + + print(f"Data range: {start_date} to {end_date}") + print(f"Total duration: {(end_date - start_date).days} days\n") + + # Generate windows + current_start = start_date + window_num = 1 + results = [] + + while current_start + self.train_window + self.test_window <= end_date: + train_end = current_start + self.train_window + test_start = train_end + test_end = test_start + self.test_window + + print(f"Window {window_num}:") + print(f" Train: {current_start.date()} to {train_end.date()}") + print(f" Test: {test_start.date()} to {test_end.date()}") + + # Split data + train_df = df[(df['vp_ts'] >= current_start) & (df['vp_ts'] < train_end)] + test_df = df[(df['vp_ts'] >= test_start) & (df['vp_ts'] < test_end)] + + print(f" Train samples: {len(train_df)}, Test samples: {len(test_df)}") + + if len(train_df) == 0 or len(test_df) == 0: + print(f" Skipping (insufficient data)\n") + current_start += self.step + window_num += 1 + continue + + try: + # Train model + model = train_fn(train_df) + + # Predict + y_true = test_df[target_col].values + y_pred = predict_fn(model, test_df) + + # Compute metrics + metrics = compute_all_metrics(y_true, y_pred) + + # Store results + result = { + 'window': window_num, + 'train_start': current_start, + 'train_end': train_end, + 'test_start': test_start, + 'test_end': test_end, + 'train_samples': len(train_df), + 'test_samples': len(test_df), + **metrics + } + results.append(result) + + print(f" MAE: {metrics['mae_minutes']:.2f} min, RMSE: {metrics['rmse_minutes']:.2f} min") + print() + + except Exception as e: + print(f" Error: {e}\n") + + # Move to next window + current_start += self.step + window_num += 1 + + # Create results dataframe + results_df = pd.DataFrame(results) + + if not results_df.empty: + self._print_summary(results_df) + + self.results = results_df + return results_df + + def _print_summary(self, results_df: pd.DataFrame): + """Print summary statistics.""" + print(f"\n{'='*60}") + print(f"VALIDATION SUMMARY".center(60)) + print(f"{'='*60}\n") + + print(f"Total windows: {len(results_df)}") + print(f"\nAverage Metrics:") + print(f" MAE: {results_df['mae_minutes'].mean():.3f} Β± {results_df['mae_minutes'].std():.3f} minutes") + print(f" RMSE: {results_df['rmse_minutes'].mean():.3f} Β± {results_df['rmse_minutes'].std():.3f} minutes") + print(f" RΒ²: {results_df['r2'].mean():.3f} Β± {results_df['r2'].std():.3f}") + print(f" Within 60s: {results_df['within_60s'].mean()*100:.1f}%") + + print(f"\nBest Window: {results_df.loc[results_df['mae_minutes'].idxmin(), 'window']}") + print(f" MAE: {results_df['mae_minutes'].min():.3f} minutes") + + print(f"\nWorst Window: {results_df.loc[results_df['mae_minutes'].idxmax(), 'window']}") + print(f" MAE: {results_df['mae_minutes'].max():.3f} minutes") + + print(f"\n{'='*60}\n") + + def plot_results(self, results_df: Optional[pd.DataFrame] = None, + metric: str = 'mae_minutes'): + """ + Plot metric over time (requires matplotlib). + + Args: + results_df: Results dataframe (uses self.results if None) + metric: Metric to plot + """ + if results_df is None: + results_df = self.results + + if results_df.empty: + print("No results to plot") + return + + try: + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(figsize=(12, 6)) + + ax.plot(results_df['window'], results_df[metric], marker='o') + ax.set_xlabel('Window Number') + ax.set_ylabel(metric.replace('_', ' ').title()) + ax.set_title(f'Rolling Window Validation: {metric}') + ax.grid(True, alpha=0.3) + + # Add mean line + mean_val = results_df[metric].mean() + ax.axhline(mean_val, color='r', linestyle='--', + label=f'Mean: {mean_val:.3f}') + ax.legend() + + plt.tight_layout() + plt.savefig(f'rolling_validation_{metric}.png', dpi=150) + print(f"Plot saved to rolling_validation_{metric}.png") + + except ImportError: + print("matplotlib not available for plotting") + + +def quick_rolling_validate(model_class, + model_params: Dict, + dataset_name: str = "sample_dataset", + train_window_days: int = 7) -> pd.DataFrame: + """ + Quick rolling validation for a model class. + + Args: + model_class: Model class to instantiate + model_params: Parameters for model initialization + dataset_name: Dataset name + train_window_days: Training window size + + Returns: + Results dataframe + """ + def train_fn(train_df): + model = model_class(**model_params) + model.fit(train_df) + return model + + def predict_fn(model, test_df): + return model.predict(test_df) + + validator = RollingValidator( + train_window_days=train_window_days, + test_window_days=1, + step_days=1 + ) + + results_df = validator.validate(dataset_name, train_fn, predict_fn) + + return results_df + + +if __name__ == "__main__": + # Example: Rolling validation for EWMA model + import sys + sys.path.append(str(Path(__file__).parent.parent)) + from ewma.train import EWMAModel + + results = quick_rolling_validate( + model_class=EWMAModel, + model_params={'alpha': 0.3, 'group_by': ['route_id', 'stop_sequence']}, + train_window_days=7 + ) + + # Save results + results.to_csv("models/rolling_validation_results.csv", index=False) + print("Results saved to models/rolling_validation_results.csv") \ No newline at end of file diff --git a/eta_prediction/models/ewma/predict.py b/eta_prediction/models/ewma/predict.py new file mode 100644 index 0000000..14ba453 --- /dev/null +++ b/eta_prediction/models/ewma/predict.py @@ -0,0 +1,155 @@ +""" +Prediction interface for EWMA model. +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.utils import format_seconds + + +def predict_eta(model_key: str, + route_id: str, + stop_sequence: int, + hour: Optional[int] = None) -> Dict: + """ + Predict ETA using EWMA model. + + Args: + model_key: Model identifier + route_id: Route ID + stop_sequence: Stop sequence number + hour: Hour of day (if model uses hourly grouping) + + Returns: + Dictionary with prediction and metadata + """ + # Load model + registry = get_registry() + model = registry.load_model(model_key) + metadata = registry.load_metadata(model_key) + + # Prepare input + input_data = { + 'route_id': [route_id], + 'stop_sequence': [stop_sequence] + } + + if hour is not None and 'hour' in model.group_by: + input_data['hour'] = [hour] + + input_df = pd.DataFrame(input_data) + + # Predict + eta_seconds = model.predict(input_df)[0] + + # Check if EWMA value exists + key = tuple(input_df.iloc[0][col] for col in model.group_by) + has_ewma = key in model.ewma_values + n_observations = model.observation_counts.get(key, 0) if has_ewma else 0 + + return { + 'eta_seconds': float(eta_seconds), + 'eta_minutes': float(eta_seconds / 60), + 'eta_formatted': format_seconds(eta_seconds), + 'model_key': model_key, + 'model_type': 'ewma', + 'alpha': metadata.get('alpha'), + 'has_ewma_value': has_ewma, + 'n_observations': n_observations, + 'using_global_mean': not has_ewma or n_observations < model.min_observations + } + + +def predict_and_update(model_key: str, + route_id: str, + stop_sequence: int, + observed_eta: float, + hour: Optional[int] = None, + save_updated: bool = False) -> Dict: + """ + Predict ETA and update model with observed value (online learning). + + Args: + model_key: Model identifier + route_id: Route ID + stop_sequence: Stop sequence + observed_eta: Actual observed ETA in seconds + hour: Hour of day + save_updated: Whether to save updated model back to registry + + Returns: + Dictionary with prediction, error, and updated EWMA + """ + # Get prediction first + result = predict_eta(model_key, route_id, stop_sequence, hour) + prediction = result['eta_seconds'] + + # Load model for update + registry = get_registry() + model = registry.load_model(model_key) + + # Prepare input for update + input_data = { + 'route_id': [route_id], + 'stop_sequence': [stop_sequence] + } + if hour is not None and 'hour' in model.group_by: + input_data['hour'] = [hour] + + input_df = pd.DataFrame(input_data) + + # Update model + model.update(input_df, np.array([observed_eta])) + + # Get new EWMA value + key = tuple(input_df.iloc[0][col] for col in model.group_by) + new_ewma = model.ewma_values.get(key) + + # Save if requested + if save_updated: + metadata = registry.load_metadata(model_key) + registry.save_model(model_key, model, metadata, overwrite=True) + + return { + **result, + 'observed_eta_seconds': observed_eta, + 'error_seconds': observed_eta - prediction, + 'updated_ewma_seconds': new_ewma, + 'model_updated': True + } + + +def batch_predict(model_key: str, input_df: pd.DataFrame) -> pd.DataFrame: + """Batch prediction.""" + registry = get_registry() + model = registry.load_model(model_key) + + result_df = input_df.copy() + result_df['predicted_eta_seconds'] = model.predict(input_df) + result_df['predicted_eta_minutes'] = result_df['predicted_eta_seconds'] / 60 + + return result_df + + +if __name__ == "__main__": + # Example: predict and update + result = predict_and_update( + model_key="ewma_sample_dataset_temporal-route_20250126_143022_alpha=0_3", + route_id="1", + stop_sequence=5, + observed_eta=180.0, # 3 minutes + hour=8 + ) + + print("Prediction and Update:") + print(f" Predicted: {result['eta_formatted']}") + print(f" Observed: {format_seconds(result['observed_eta_seconds'])}") + print(f" Error: {result['error_seconds']:.1f} seconds") + print(f" Updated EWMA: {result['updated_ewma_seconds']/60:.2f} minutes") \ No newline at end of file diff --git a/eta_prediction/models/ewma/train.py b/eta_prediction/models/ewma/train.py new file mode 100644 index 0000000..b205bf7 --- /dev/null +++ b/eta_prediction/models/ewma/train.py @@ -0,0 +1,324 @@ +""" +Exponentially Weighted Moving Average (EWMA) Model +Adapts predictions based on recent observations with exponential decay. +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional, Tuple +from collections import defaultdict +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset +from common.metrics import compute_all_metrics +from common.keys import ModelKey +from common.registry import get_registry +from common.utils import print_metrics_table, train_test_summary, clip_predictions + + +class EWMAModel: + """ + EWMA-based ETA prediction. + + Maintains exponentially weighted moving averages for each (route, stop) pair. + Updates incrementally as new observations arrive. + + ETA_new = alpha * observed + (1 - alpha) * ETA_old + """ + + def __init__(self, + alpha: float = 0.3, + group_by: list = ['route_id', 'stop_sequence'], + min_observations: int = 3): + """ + Initialize EWMA model. + + Args: + alpha: Smoothing parameter (0-1, higher = more weight on recent) + group_by: Features to group by + min_observations: Min observations before using EWMA + """ + self.alpha = alpha + self.group_by = group_by + self.min_observations = min_observations + + self.ewma_values = {} # (route, stop, ...) -> current EWMA + self.observation_counts = {} # (route, stop, ...) -> count + self.global_mean = None + + def _make_key(self, row: pd.Series) -> tuple: + """Create lookup key from row.""" + return tuple(row[col] for col in self.group_by) + + def fit(self, train_df: pd.DataFrame, target_col: str = 'time_to_arrival_seconds'): + """ + Train EWMA model by processing observations in time order. + + Args: + train_df: Training dataframe (should be time-sorted) + target_col: Target column name + """ + # Sort by timestamp + df_sorted = train_df.sort_values('vp_ts').reset_index(drop=True) + + self.global_mean = df_sorted[target_col].mean() + + # Process observations sequentially + for _, row in df_sorted.iterrows(): + key = self._make_key(row) + observed = row[target_col] + + if key not in self.ewma_values: + # Initialize with first observation + self.ewma_values[key] = observed + self.observation_counts[key] = 1 + else: + # Update EWMA + old_ewma = self.ewma_values[key] + new_ewma = self.alpha * observed + (1 - self.alpha) * old_ewma + self.ewma_values[key] = new_ewma + self.observation_counts[key] += 1 + + print(f"Trained EWMA model (alpha={self.alpha})") + print(f" Unique groups: {len(self.ewma_values)}") + print(f" Total observations: {len(df_sorted)}") + print(f" Global mean: {self.global_mean/60:.2f} minutes") + + def predict(self, X: pd.DataFrame) -> np.ndarray: + """ + Predict ETAs using current EWMA values. + + Args: + X: DataFrame with group_by columns + + Returns: + Array of predicted ETAs + """ + predictions = [] + + for _, row in X.iterrows(): + key = self._make_key(row) + + if key in self.ewma_values: + count = self.observation_counts[key] + if count >= self.min_observations: + predictions.append(self.ewma_values[key]) + else: + # Not enough observations, use global mean + predictions.append(self.global_mean) + else: + # New group, use global mean + predictions.append(self.global_mean) + + return clip_predictions(np.array(predictions)) + + def update(self, X: pd.DataFrame, y: np.ndarray): + """ + Update EWMA values with new observations (online learning). + + Args: + X: Features + y: Observed values + """ + for (_, row), observed in zip(X.iterrows(), y): + key = self._make_key(row) + + if key not in self.ewma_values: + self.ewma_values[key] = observed + self.observation_counts[key] = 1 + else: + old_ewma = self.ewma_values[key] + new_ewma = self.alpha * observed + (1 - self.alpha) * old_ewma + self.ewma_values[key] = new_ewma + self.observation_counts[key] += 1 + + def get_coverage(self, X: pd.DataFrame) -> float: + """Get fraction of predictions with EWMA values.""" + covered = 0 + for _, row in X.iterrows(): + key = self._make_key(row) + if key in self.ewma_values and self.observation_counts[key] >= self.min_observations: + covered += 1 + return covered / len(X) + + def get_group_stats(self) -> pd.DataFrame: + """Get statistics about learned groups.""" + stats = [] + for key, ewma in self.ewma_values.items(): + count = self.observation_counts[key] + stats.append({ + **{col: val for col, val in zip(self.group_by, key)}, + 'ewma_eta_minutes': ewma / 60, + 'n_observations': count + }) + + return pd.DataFrame(stats).sort_values('n_observations', ascending=False) + + +def train_ewma(dataset_name: str = "sample_dataset", + route_id: Optional[str] = None, + alpha: float = 0.3, + group_by: list = ['route_id', 'stop_sequence'], + min_observations: int = 3, + test_size: float = 0.2, + save_model: bool = True, + pre_split: Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]] = None) -> Dict: + """ + Train and evaluate EWMA model. + + Args: + dataset_name: Name of dataset + route_id: Optional route ID for route-specific training + alpha: EWMA smoothing parameter + group_by: Grouping columns + min_observations: Minimum observations threshold + test_size: Test fraction + save_model: Whether to save + pre_split: Optional (train, val, test) DataFrames to reuse + + Returns: + Dictionary with model, metrics, metadata + """ + print(f"\n{'='*60}") + print(f"Training EWMA Model".center(60)) + print(f"{'='*60}\n") + + route_info = f" (route: {route_id})" if route_id else " (global)" + print(f"Scope{route_info}") + print(f"Config: alpha={alpha}, group_by={group_by}, min_obs={min_observations}") + + # Load dataset + print(f"\nLoading dataset: {dataset_name}") + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Filter by route if specified + if route_id is not None: + df = dataset.df + df_filtered = df[df['route_id'] == route_id].copy() + print(f"Filtered to route {route_id}: {len(df_filtered):,} samples") + + if len(df_filtered) == 0: + raise ValueError(f"No data found for route {route_id}") + + dataset.df = df_filtered + + if pre_split is not None: + train_df, val_df, test_df = (df.copy() for df in pre_split) + else: + # Split data temporally (important for time series) + train_df, val_df, test_df = dataset.temporal_split( + train_frac=1-test_size-0.1, + val_frac=0.1 + ) + + train_test_summary(train_df, test_df, val_df) + + # Train model + print("Training model...") + model = EWMAModel( + alpha=alpha, + group_by=group_by, + min_observations=min_observations + ) + model.fit(train_df) + + # Evaluate on validation (with optional online updates) + print("\nValidation Performance:") + y_val = val_df['time_to_arrival_seconds'].values + val_preds = model.predict(val_df) + val_metrics = compute_all_metrics(y_val, val_preds, prefix="val_") + print_metrics_table(val_metrics, "Validation Metrics") + + val_coverage = model.get_coverage(val_df) + print(f"Validation coverage: {val_coverage*100:.1f}%") + + # Optionally update model with validation data + print("\nUpdating model with validation data...") + model.update(val_df, y_val) + + # Evaluate on test set + print("\nTest Performance:") + y_test = test_df['time_to_arrival_seconds'].values + test_preds = model.predict(test_df) + test_metrics = compute_all_metrics(y_test, test_preds, prefix="test_") + print_metrics_table(test_metrics, "Test Metrics") + + test_coverage = model.get_coverage(test_df) + print(f"Test coverage: {test_coverage*100:.1f}%") + + # Show some group stats + group_stats = model.get_group_stats() + print(f"\nTop 10 groups by observation count:") + print(group_stats.head(10)) + + # Prepare metadata + metadata = { + 'model_type': 'ewma', + 'dataset': dataset_name, + 'route_id': route_id, + 'alpha': alpha, + 'group_by': group_by, + 'min_observations': min_observations, + 'n_groups': len(model.ewma_values), + 'n_samples': len(train_df) + len(val_df) + len(test_df), + 'n_trips': dataset.df['trip_id'].nunique() if route_id else None, + 'train_samples': len(train_df), + 'test_samples': len(test_df), + 'test_coverage': float(test_coverage), + 'global_mean_eta': float(model.global_mean), + 'metrics': {**val_metrics, **test_metrics} + } + + # Save model + if save_model: + model_key = ModelKey.generate( + model_type='ewma', + dataset_name=dataset_name, + feature_groups=['temporal', 'route'], + route_id=route_id, + alpha=str(alpha).replace('.', '_') + ) + + registry = get_registry() + registry.save_model(model_key, model, metadata) + metadata['model_key'] = model_key + + return { + 'model': model, + 'metrics': metadata['metrics'], + 'metadata': metadata + } + + +if __name__ == "__main__": + # Train with different alpha values + + # Conservative (slow adaptation) + result1 = train_ewma(alpha=0.1) + + # Balanced + result2 = train_ewma(alpha=0.3) + + # Aggressive (fast adaptation) + result3 = train_ewma(alpha=0.5) + + # With hourly grouping + result4 = train_ewma( + alpha=0.3, + group_by=['route_id', 'stop_sequence', 'hour'] + ) + + # Compare + print("\n" + "="*60) + print("Model Comparison (Test MAE)") + print("="*60) + results = [result1, result2, result3, result4] + labels = ["alpha=0.1", "alpha=0.3", "alpha=0.5", "alpha=0.3+hour"] + + for label, result in zip(labels, results): + mae_min = result['metrics']['test_mae_minutes'] + print(f"{label:20s}: {mae_min:.3f} minutes") diff --git a/eta_prediction/models/historical_mean/predict.py b/eta_prediction/models/historical_mean/predict.py new file mode 100644 index 0000000..ff5ce67 --- /dev/null +++ b/eta_prediction/models/historical_mean/predict.py @@ -0,0 +1,136 @@ +""" +Prediction interface for Historical Mean model. +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.utils import format_seconds + + +def predict_eta(model_key: str, + route_id: str, + stop_sequence: int, + hour: int, + day_of_week: Optional[int] = None, + is_peak_hour: Optional[bool] = None) -> Dict: + """ + Predict ETA using historical mean model. + + Args: + model_key: Model identifier in registry + route_id: Route ID + stop_sequence: Stop sequence number + hour: Hour of day (0-23) + day_of_week: Day of week (0=Monday, optional) + is_peak_hour: Peak hour flag (optional) + + Returns: + Dictionary with prediction and metadata + """ + # Load model + registry = get_registry() + model = registry.load_model(model_key) + metadata = registry.load_metadata(model_key) + + # Prepare input dataframe + input_data = { + 'route_id': [route_id], + 'stop_sequence': [stop_sequence], + 'hour': [hour] + } + + if day_of_week is not None and 'day_of_week' in model.group_by: + input_data['day_of_week'] = [day_of_week] + + if is_peak_hour is not None and 'is_peak_hour' in model.group_by: + input_data['is_peak_hour'] = [is_peak_hour] + + input_df = pd.DataFrame(input_data) + + # Predict + eta_seconds = model.predict(input_df)[0] + + # Check if prediction came from historical data or fallback + coverage = model.get_coverage(input_df) + has_historical_data = coverage > 0 + + return { + 'eta_seconds': float(eta_seconds), + 'eta_minutes': float(eta_seconds / 60), + 'eta_formatted': format_seconds(eta_seconds), + 'model_key': model_key, + 'model_type': 'historical_mean', + 'has_historical_data': has_historical_data, + 'global_mean_eta': metadata.get('global_mean_eta'), + 'group_by': metadata.get('group_by') + } + + +def batch_predict(model_key: str, input_df: pd.DataFrame) -> pd.DataFrame: + """ + Batch prediction for multiple inputs. + + Args: + model_key: Model identifier in registry + input_df: DataFrame with features + + Returns: + DataFrame with predictions added + """ + registry = get_registry() + model = registry.load_model(model_key) + + result_df = input_df.copy() + result_df['predicted_eta_seconds'] = model.predict(input_df) + result_df['predicted_eta_minutes'] = result_df['predicted_eta_seconds'] / 60 + + # Add coverage information + result_df['has_historical_data'] = False + merged = input_df[model.group_by].merge( + model.lookup_table, + on=model.group_by, + how='left', + indicator=True + ) + result_df.loc[merged['_merge'] == 'both', 'has_historical_data'] = True + + return result_df + + +if __name__ == "__main__": + # Example usage + + # Single prediction + result = predict_eta( + model_key="historical_mean_sample_dataset_temporal-route_20250126_143022", + route_id="1", + stop_sequence=5, + hour=8, + day_of_week=0 # Monday + ) + + print("Single Prediction:") + print(f" ETA: {result['eta_formatted']}") + print(f" Has historical data: {result['has_historical_data']}") + + # Batch prediction example + test_data = pd.DataFrame({ + 'route_id': ['1', '1', '2'], + 'stop_sequence': [5, 10, 3], + 'hour': [8, 17, 12] + }) + + predictions = batch_predict( + model_key="historical_mean_sample_dataset_temporal-route_20250126_143022", + input_df=test_data + ) + + print("\nBatch Predictions:") + print(predictions[['route_id', 'stop_sequence', 'predicted_eta_minutes', 'has_historical_data']]) \ No newline at end of file diff --git a/eta_prediction/models/historical_mean/train.py b/eta_prediction/models/historical_mean/train.py new file mode 100644 index 0000000..d327b34 --- /dev/null +++ b/eta_prediction/models/historical_mean/train.py @@ -0,0 +1,243 @@ +""" +Historical Mean Baseline Model +Predicts ETA based on historical average travel times grouped by route, stop, and time features. +""" + +import pandas as pd +import numpy as np +from typing import Dict, List, Optional, Tuple +import sys +from pathlib import Path + +# Add parent directory to path for imports +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset, prepare_features_target +from common.metrics import compute_all_metrics +from common.keys import ModelKey +from common.registry import get_registry +from common.utils import print_metrics_table, train_test_summary + + +class HistoricalMeanModel: + """ + Baseline model using historical mean ETAs. + + Groups data by route, stop, and temporal features, then computes + mean ETA for each group. At prediction time, looks up the appropriate group mean. + """ + + def __init__(self, group_by: List[str] = ['route_id', 'stop_sequence', 'hour']): + """ + Initialize model. + + Args: + group_by: List of columns to group by for computing means + """ + self.group_by = group_by + self.lookup_table = None + self.global_mean = None + self.feature_cols = None + + def fit(self, train_df: pd.DataFrame, target_col: str = 'time_to_arrival_seconds'): + """ + Train model by computing historical means. + + Args: + train_df: Training dataframe with features and target + target_col: Name of target column + """ + # Store feature columns for later + self.feature_cols = self.group_by + + # Compute global mean as fallback + self.global_mean = train_df[target_col].mean() + + # Compute group means + self.lookup_table = train_df.groupby(self.group_by)[target_col].agg([ + ('mean', 'mean'), + ('std', 'std'), + ('count', 'count') + ]).reset_index() + + print(f"Trained on {len(train_df)} samples") + print(f"Created {len(self.lookup_table)} unique groups") + print(f"Global mean ETA: {self.global_mean/60:.2f} minutes") + + def predict(self, X: pd.DataFrame) -> np.ndarray: + """ + Predict ETAs for input data. + + Args: + X: DataFrame with features matching group_by columns + + Returns: + Array of predicted ETAs in seconds + """ + if self.lookup_table is None: + raise ValueError("Model not trained. Call fit() first.") + + # Merge with lookup table + merged = X[self.group_by].merge( + self.lookup_table, + on=self.group_by, + how='left' + ) + + # Use group mean, fallback to global mean + predictions = merged['mean'].fillna(self.global_mean).values + + return predictions + + def get_coverage(self, X: pd.DataFrame) -> float: + """ + Get fraction of predictions with matching historical data. + + Args: + X: DataFrame with features + + Returns: + Coverage ratio (0-1) + """ + merged = X[self.group_by].merge( + self.lookup_table, + on=self.group_by, + how='left', + indicator=True + ) + + return (merged['_merge'] == 'both').mean() + + +def train_historical_mean(dataset_name: str = "sample_dataset", + route_id: Optional[str] = None, + group_by: List[str] = ['route_id', 'stop_sequence', 'hour'], + test_size: float = 0.2, + save_model: bool = True, + pre_split: Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]] = None) -> Dict: + """ + Train and evaluate historical mean model. + + Args: + dataset_name: Name of dataset in datasets/ directory + route_id: Optional route ID for route-specific training + group_by: List of columns to group by + test_size: Fraction of data for testing + save_model: Whether to save to registry + pre_split: Optional (train, val, test) DataFrames to reuse + + Returns: + Dictionary with model, metrics, and metadata + """ + print(f"\n{'='*60}") + print(f"Training Historical Mean Model".center(60)) + print(f"{'='*60}\n") + + route_info = f" (route: {route_id})" if route_id else " (global)" + print(f"Scope{route_info}") + print(f"Group by: {group_by}") + + # Load dataset + print(f"\nLoading dataset: {dataset_name}") + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Filter by route if specified + if route_id is not None: + df = dataset.df + df_filtered = df[df['route_id'] == route_id].copy() + print(f"Filtered to route {route_id}: {len(df_filtered):,} samples") + + if len(df_filtered) == 0: + raise ValueError(f"No data found for route {route_id}") + + dataset.df = df_filtered + + if pre_split is not None: + train_df, val_df, test_df = (df.copy() for df in pre_split) + else: + train_df, val_df, test_df = dataset.temporal_split( + train_frac=1-test_size-0.1, + val_frac=0.1 + ) + + train_test_summary(train_df, test_df, val_df) + + # Train model + print("Training model...") + model = HistoricalMeanModel(group_by=group_by) + model.fit(train_df) + + # Evaluate on validation set + print("\nValidation Performance:") + y_val = val_df['time_to_arrival_seconds'].values + val_preds = model.predict(val_df) + val_metrics = compute_all_metrics(y_val, val_preds, prefix="val_") + print_metrics_table(val_metrics, "Validation Metrics") + + val_coverage = model.get_coverage(val_df) + print(f"Validation coverage: {val_coverage*100:.1f}%") + + # Evaluate on test set + print("\nTest Performance:") + y_test = test_df['time_to_arrival_seconds'].values + test_preds = model.predict(test_df) + test_metrics = compute_all_metrics(y_test, test_preds, prefix="test_") + print_metrics_table(test_metrics, "Test Metrics") + + test_coverage = model.get_coverage(test_df) + print(f"Test coverage: {test_coverage*100:.1f}%") + + # Prepare metadata + metadata = { + 'model_type': 'historical_mean', + 'dataset': dataset_name, + 'route_id': route_id, + 'group_by': group_by, + 'n_samples': len(train_df) + len(val_df) + len(test_df), + 'n_trips': dataset.df['trip_id'].nunique() if route_id else None, + 'train_samples': len(train_df), + 'test_samples': len(test_df), + 'unique_groups': len(model.lookup_table), + 'global_mean_eta': float(model.global_mean), + 'test_coverage': float(test_coverage), + 'metrics': {**val_metrics, **test_metrics} + } + + # Save model + if save_model: + model_key = ModelKey.generate( + model_type='historical_mean', + dataset_name=dataset_name, + feature_groups=['temporal', 'route'], + route_id=route_id + ) + + registry = get_registry() + registry.save_model(model_key, model, metadata) + metadata['model_key'] = model_key + + return { + 'model': model, + 'metrics': metadata['metrics'], + 'metadata': metadata + } + + +if __name__ == "__main__": + # Train with different grouping strategies + + # Basic: route + stop + hour + result1 = train_historical_mean( + group_by=['route_id', 'stop_sequence', 'hour'] + ) + + # With day of week + result2 = train_historical_mean( + group_by=['route_id', 'stop_sequence', 'hour', 'day_of_week'] + ) + + # With peak hour indicator + result3 = train_historical_mean( + group_by=['route_id', 'stop_sequence', 'is_peak_hour'] + ) diff --git a/eta_prediction/models/polyreg_distance/predict.py b/eta_prediction/models/polyreg_distance/predict.py new file mode 100644 index 0000000..2fa1ddd --- /dev/null +++ b/eta_prediction/models/polyreg_distance/predict.py @@ -0,0 +1,112 @@ +""" +Prediction interface for Polynomial Regression Distance model. +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.utils import format_seconds + + +def predict_eta(model_key: str, + distance_to_stop: float, + route_id: Optional[str] = None) -> Dict: + """ + Predict ETA using polynomial regression distance model. + + Args: + model_key: Model identifier in registry + distance_to_stop: Distance to stop in meters + route_id: Route ID (required for route-specific models) + + Returns: + Dictionary with prediction and metadata + """ + # Load model + registry = get_registry() + model = registry.load_model(model_key) + metadata = registry.load_metadata(model_key) + + # Prepare input + input_data = {'distance_to_stop': [distance_to_stop]} + if model.route_specific: + if route_id is None: + raise ValueError("route_id required for route-specific model") + input_data['route_id'] = [route_id] + + input_df = pd.DataFrame(input_data) + + # Predict + eta_seconds = model.predict(input_df)[0] + + # Get coefficients for this route + coefs = model.get_coefficients(route_id if model.route_specific else None) + + return { + 'eta_seconds': float(eta_seconds), + 'eta_minutes': float(eta_seconds / 60), + 'eta_formatted': format_seconds(eta_seconds), + 'model_key': model_key, + 'model_type': 'polyreg_distance', + 'distance_to_stop_m': distance_to_stop, + 'route_specific': metadata.get('route_specific', False), + 'degree': metadata.get('degree'), + 'coefficients': coefs + } + + +def batch_predict(model_key: str, input_df: pd.DataFrame) -> pd.DataFrame: + """ + Batch prediction for multiple inputs. + + Args: + model_key: Model identifier in registry + input_df: DataFrame with distance_to_stop (and route_id if needed) + + Returns: + DataFrame with predictions added + """ + registry = get_registry() + model = registry.load_model(model_key) + + result_df = input_df.copy() + result_df['predicted_eta_seconds'] = model.predict(input_df) + result_df['predicted_eta_minutes'] = result_df['predicted_eta_seconds'] / 60 + + return result_df + + +if __name__ == "__main__": + # Example usage + + # Single prediction + result = predict_eta( + model_key="polyreg_distance_sample_dataset_distance_20250126_143022_degree=2", + distance_to_stop=1500.0, # 1.5 km + route_id="1" + ) + + print("Single Prediction:") + print(f" Distance: {result['distance_to_stop_m']} meters") + print(f" ETA: {result['eta_formatted']}") + print(f" Degree: {result['degree']}") + + # Batch prediction + test_data = pd.DataFrame({ + 'route_id': ['1', '1', '2'], + 'distance_to_stop': [500.0, 1500.0, 3000.0] + }) + + predictions = batch_predict( + model_key="polyreg_distance_sample_dataset_distance_20250126_143022_degree=2", + input_df=test_data + ) + + print("\nBatch Predictions:") + print(predictions[['route_id', 'distance_to_stop', 'predicted_eta_minutes']]) \ No newline at end of file diff --git a/eta_prediction/models/polyreg_distance/train.py b/eta_prediction/models/polyreg_distance/train.py new file mode 100644 index 0000000..16a88e8 --- /dev/null +++ b/eta_prediction/models/polyreg_distance/train.py @@ -0,0 +1,318 @@ +""" +Polynomial Regression Model - Distance-based +Fits polynomial features on distance_to_stop with optional route-specific models. +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional, Tuple +from sklearn.preprocessing import PolynomialFeatures +from sklearn.linear_model import Ridge +from sklearn.pipeline import Pipeline +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset, prepare_features_target +from common.metrics import compute_all_metrics +from common.keys import ModelKey +from common.registry import get_registry +from common.utils import print_metrics_table, train_test_summary, clip_predictions + + +class PolyRegDistanceModel: + """ + Polynomial regression on distance with optional route-specific models. + + Features: distance_to_stop, (distance)^2, (distance)^3, ... + Can fit separate models per route for better performance. + """ + + def __init__(self, + degree: int = 2, + alpha: float = 1.0, + route_specific: bool = False): + """ + Initialize model. + + Args: + degree: Polynomial degree (2 or 3 recommended) + alpha: Ridge regression alpha (regularization strength) + route_specific: Whether to fit separate model per route + """ + self.degree = degree + self.alpha = alpha + self.route_specific = route_specific + self.models = {} # route_id -> model mapping + self.global_model = None + self.feature_cols = ['distance_to_stop'] + + def _create_pipeline(self) -> Pipeline: + """Create sklearn pipeline with polynomial features and ridge regression.""" + return Pipeline([ + ('poly', PolynomialFeatures(degree=self.degree, include_bias=True)), + ('ridge', Ridge(alpha=self.alpha)) + ]) + + def fit(self, train_df: pd.DataFrame, target_col: str = 'time_to_arrival_seconds'): + """ + Train model(s). + + Args: + train_df: Training dataframe with distance_to_stop and target + target_col: Name of target column + """ + if 'distance_to_stop' not in train_df.columns: + raise ValueError("distance_to_stop column required") + + if self.route_specific: + # Fit separate model per route + for route_id, route_df in train_df.groupby('route_id'): + X = route_df[['distance_to_stop']].values + y = route_df[target_col].values + + model = self._create_pipeline() + model.fit(X, y) + self.models[route_id] = model + + print(f"Trained {len(self.models)} route-specific models (degree={self.degree})") + else: + # Fit single global model + X = train_df[['distance_to_stop']].values + y = train_df[target_col].values + + self.global_model = self._create_pipeline() + self.global_model.fit(X, y) + + print(f"Trained global model (degree={self.degree}, alpha={self.alpha})") + + def predict(self, X: pd.DataFrame) -> np.ndarray: + """ + Predict ETAs. + + Args: + X: DataFrame with distance_to_stop (and route_id if route_specific) + + Returns: + Array of predicted ETAs in seconds + """ + if self.route_specific: + if 'route_id' not in X.columns: + raise ValueError("route_id required for route-specific model") + + # Create predictions array matching input length + predictions = np.zeros(len(X)) + + # Reset index to get positional indices + X_reset = X.reset_index(drop=True) + + for route_id, route_df in X_reset.groupby('route_id'): + # Get positional indices (0-based from reset_index) + pos_indices = route_df.index.values + X_route = route_df[['distance_to_stop']].values + + if route_id in self.models: + predictions[pos_indices] = self.models[route_id].predict(X_route) + elif self.global_model is not None: + # Fallback to global model + predictions[pos_indices] = self.global_model.predict(X_route) + else: + # No model available - use simple linear estimate (30 km/h avg) + predictions[pos_indices] = X_route.flatten() / 30000 * 3600 + else: + if self.global_model is None: + raise ValueError("Model not trained") + + X_dist = X[['distance_to_stop']].values + predictions = self.global_model.predict(X_dist) + + # Clip to reasonable range + return clip_predictions(predictions) + + def get_coefficients(self, route_id: Optional[str] = None) -> Dict: + """ + Get model coefficients. + + Args: + route_id: Route ID for route-specific model, None for global + + Returns: + Dictionary with coefficients + """ + if route_id and route_id in self.models: + model = self.models[route_id] + elif self.global_model: + model = self.global_model + else: + return {} + + coefs = model.named_steps['ridge'].coef_ + intercept = model.named_steps['ridge'].intercept_ + + return { + 'intercept': float(intercept), + 'coefficients': coefs.tolist(), + 'degree': self.degree + } + + +def train_polyreg_distance(dataset_name: str = "sample_dataset", + route_id: Optional[str] = None, + degree: int = 2, + alpha: float = 1.0, + route_specific: bool = False, + test_size: float = 0.2, + save_model: bool = True, + pre_split: Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]] = None) -> Dict: + """ + Train and evaluate polynomial regression distance model. + + Args: + dataset_name: Name of dataset in datasets/ directory + route_id: Optional route ID for route-specific training (overrides route_specific) + degree: Polynomial degree + alpha: Ridge regularization strength + route_specific: Whether to fit per-route models (ignored if route_id specified) + test_size: Fraction of data for testing + save_model: Whether to save to registry + pre_split: Optional (train, val, test) DataFrames to reuse + + Returns: + Dictionary with model, metrics, and metadata + """ + print(f"\n{'='*60}") + print(f"Training Polynomial Regression Distance Model".center(60)) + print(f"{'='*60}\n") + + route_info = f" (route: {route_id})" if route_id else " (global)" + print(f"Scope{route_info}") + print(f"Config: degree={degree}, alpha={alpha}, route_specific={route_specific}") + + # Load dataset + print(f"\nLoading dataset: {dataset_name}") + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Filter by route if specified (single-route model) + if route_id is not None: + df = dataset.df + df_filtered = df[df['route_id'] == route_id].copy() + print(f"Filtered to route {route_id}: {len(df_filtered):,} samples") + + if len(df_filtered) == 0: + raise ValueError(f"No data found for route {route_id}") + + dataset.df = df_filtered + # When training on single route, don't need route_specific flag + route_specific = False + + if pre_split is not None: + train_df, val_df, test_df = (df.copy() for df in pre_split) + else: + train_df, val_df, test_df = dataset.temporal_split( + train_frac=1-test_size-0.1, + val_frac=0.1 + ) + + train_test_summary(train_df, test_df, val_df) + + # Train model + print("Training model...") + model = PolyRegDistanceModel( + degree=degree, + alpha=alpha, + route_specific=route_specific + ) + model.fit(train_df) + + # Evaluate on validation set + print("\nValidation Performance:") + y_val = val_df['time_to_arrival_seconds'].values + val_preds = model.predict(val_df) + val_metrics = compute_all_metrics(y_val, val_preds, prefix="val_") + print_metrics_table(val_metrics, "Validation Metrics") + + # Evaluate on test set + print("\nTest Performance:") + y_test = test_df['time_to_arrival_seconds'].values + test_preds = model.predict(test_df) + test_metrics = compute_all_metrics(y_test, test_preds, prefix="test_") + print_metrics_table(test_metrics, "Test Metrics") + + # Get sample coefficients + sample_coefs = model.get_coefficients(route_id=route_id) + if sample_coefs: + print(f"\nModel coefficients:") + print(f" Intercept: {sample_coefs['intercept']:.2f}") + print(f" Coefficients: {[f'{c:.6f}' for c in sample_coefs['coefficients'][:5]]}") + + # Prepare metadata + metadata = { + 'model_type': 'polyreg_distance', + 'dataset': dataset_name, + 'route_id': route_id, + 'degree': degree, + 'alpha': alpha, + 'route_specific': route_specific, + 'n_models': len(model.models) if route_specific else 1, + 'n_samples': len(train_df) + len(val_df) + len(test_df), + 'n_trips': dataset.df['trip_id'].nunique() if route_id else None, + 'train_samples': len(train_df), + 'test_samples': len(test_df), + 'metrics': {**val_metrics, **test_metrics} + } + + # Save model + if save_model: + model_key = ModelKey.generate( + model_type='polyreg_distance', + dataset_name=dataset_name, + feature_groups=['distance'], + route_id=route_id, + degree=degree, + route_specific='yes' if route_specific else 'no' + ) + + registry = get_registry() + registry.save_model(model_key, model, metadata) + metadata['model_key'] = model_key + + return { + 'model': model, + 'metrics': metadata['metrics'], + 'metadata': metadata + } + + +if __name__ == "__main__": + # Train different configurations + + # Degree 2, global model + result1 = train_polyreg_distance( + degree=2, + alpha=1.0, + route_specific=False + ) + + # Degree 3, global model + result2 = train_polyreg_distance( + degree=3, + alpha=1.0, + route_specific=False + ) + + # Degree 2, route-specific + result3 = train_polyreg_distance( + degree=2, + alpha=1.0, + route_specific=True + ) + + # Compare results + print("\n" + "="*60) + print("Model Comparison (Test MAE)") + print("="*60) + for i, result in enumerate([result1, result2, result3], 1): + mae_min = result['metrics']['test_mae_minutes'] + print(f"Model {i}: {mae_min:.3f} minutes") diff --git a/eta_prediction/models/polyreg_time/predict.py b/eta_prediction/models/polyreg_time/predict.py new file mode 100644 index 0000000..107e996 --- /dev/null +++ b/eta_prediction/models/polyreg_time/predict.py @@ -0,0 +1,99 @@ +""" +Prediction interface for Polynomial Regression Time model. +""" + +import pandas as pd +from typing import Dict, Optional +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.utils import format_seconds + + +def predict_eta(model_key: str, + distance_to_stop: float, + progress_on_segment: Optional[float] = None, + progress_ratio: Optional[float] = None, + hour: Optional[int] = None, + day_of_week: Optional[int] = None, + is_peak_hour: Optional[bool] = None, + is_weekend: Optional[bool] = None, + is_holiday: Optional[bool] = None, + temperature_c: Optional[float] = None, + precipitation_mm: Optional[float] = None, + wind_speed_kmh: Optional[float] = None) -> Dict: + """ + Predict ETA using polynomial regression time model. + + Args: + model_key: Model identifier + distance_to_stop: Distance in meters + progress_on_segment: 0-1 fraction along the current stop-to-stop segment + progress_ratio: 0-1 fraction along the overall route + hour: Hour of day (0-23) + day_of_week: Day of week (0=Monday) + is_peak_hour: Peak hour flag + is_weekend: Weekend flag + is_holiday: Holiday flag + temperature_c: Temperature + precipitation_mm: Precipitation + wind_speed_kmh: Wind speed + + Returns: + Dictionary with prediction and metadata + """ + # Load model + registry = get_registry() + model = registry.load_model(model_key) + metadata = registry.load_metadata(model_key) + + # Prepare input + input_data = {'distance_to_stop': [distance_to_stop]} + + # Add optional features + optional_features = { + 'progress_on_segment': progress_on_segment, + 'progress_ratio': progress_ratio, + 'hour': hour, + 'day_of_week': day_of_week, + 'is_peak_hour': is_peak_hour, + 'is_weekend': is_weekend, + 'is_holiday': is_holiday, + 'temperature_c': temperature_c, + 'precipitation_mm': precipitation_mm, + 'wind_speed_kmh': wind_speed_kmh + } + + for key, value in optional_features.items(): + if value is not None: + input_data[key] = [value] + + input_df = pd.DataFrame(input_data) + + # Predict + eta_seconds = model.predict(input_df)[0] + + return { + 'eta_seconds': float(eta_seconds), + 'eta_minutes': float(eta_seconds / 60), + 'eta_formatted': format_seconds(eta_seconds), + 'model_key': model_key, + 'model_type': 'polyreg_time', + 'distance_to_stop_m': distance_to_stop, + 'features_used': list(input_data.keys()) + } + + +def batch_predict(model_key: str, input_df: pd.DataFrame) -> pd.DataFrame: + """Batch prediction.""" + registry = get_registry() + model = registry.load_model(model_key) + + result_df = input_df.copy() + result_df['predicted_eta_seconds'] = model.predict(input_df) + result_df['predicted_eta_minutes'] = result_df['predicted_eta_seconds'] / 60 + + return result_df diff --git a/eta_prediction/models/polyreg_time/train.py b/eta_prediction/models/polyreg_time/train.py new file mode 100644 index 0000000..b9ab4d1 --- /dev/null +++ b/eta_prediction/models/polyreg_time/train.py @@ -0,0 +1,539 @@ +""" +Polynomial Regression Model - Time Enhanced +Fits polynomial features with temporal, spatial, and optional weather features. +""" + +import pandas as pd +import numpy as np +from typing import Dict, List, Optional, Tuple +from sklearn.preprocessing import PolynomialFeatures, StandardScaler, OneHotEncoder +from sklearn.linear_model import Ridge +from sklearn.pipeline import Pipeline +from sklearn.compose import ColumnTransformer +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset +from common.metrics import compute_all_metrics +from common.keys import ModelKey +from common.registry import get_registry +from common.utils import print_metrics_table, train_test_summary, clip_predictions + + +def _make_one_hot_encoder() -> OneHotEncoder: + """Instantiate OneHotEncoder compatible with sklearn>=1.2 (sparse_output) and older.""" + try: + return OneHotEncoder(handle_unknown='ignore', sparse_output=False) + except TypeError: + return OneHotEncoder(handle_unknown='ignore', sparse=False) + + +class PolyRegTimeModel: + """ + Enhanced polynomial regression with temporal/spatial features. + + Features: + - Core: distance_to_stop (polynomialized) + - Temporal: hour, day_of_week, is_weekend, is_peak_hour + - Spatial: progress_on_segment, progress_ratio + - Weather: temperature_c, precipitation_mm, wind_speed_kmh + """ + + def __init__(self, + poly_degree: int = 2, + alpha: float = 1.0, + include_temporal: bool = True, + include_spatial: bool = True, + include_weather: bool = False, + handle_nan: str = 'drop'): # 'drop', 'impute', or 'error' + """ + Initialize model. + + Args: + poly_degree: Polynomial degree for distance + alpha: Ridge regularization strength + include_temporal: Include time-of-day features + include_spatial: Include spatial progress/segment features + include_weather: Include weather features + handle_nan: How to handle NaN - 'drop', 'impute', or 'error' + """ + self.poly_degree = poly_degree + self.alpha = alpha + self.include_temporal = include_temporal + self.include_spatial = include_spatial + self.include_weather = include_weather + self.handle_nan = handle_nan + self.model = None + self.feature_cols = None + self.available_features = None + self.categorical_features: List[str] = [] + self.numeric_features: List[str] = [] + + def _get_feature_groups(self) -> Dict[str, List[str]]: + """Define feature groups.""" + return { + 'core': ['distance_to_stop'], + 'spatial_numeric': ['progress_on_segment', 'progress_ratio'] + if self.include_spatial else [], + 'temporal': ['hour', 'day_of_week', 'is_weekend', 'is_peak_hour'] + if self.include_temporal else [], + 'weather': ['temperature_c', 'precipitation_mm', 'wind_speed_kmh'] + if self.include_weather else [] + } + + def _clean_data(self, df: pd.DataFrame, + target_col: str = 'time_to_arrival_seconds') -> pd.DataFrame: + """ + Clean data and handle NaN values. + + Args: + df: Input dataframe + target_col: Target column name + + Returns: + Cleaned dataframe + """ + print(f"\n{'='*60}") + print("Data Cleaning") + print(f"{'='*60}") + print(f"Initial rows: {len(df):,}") + + # Get all potential features + feature_groups = self._get_feature_groups() + all_features = [] + for group_features in feature_groups.values(): + all_features.extend(group_features) + + # Check which features are available and have acceptable NaN levels + available = [] + missing = [] + high_nan = [] + + for feat in all_features: + if feat not in df.columns: + missing.append(feat) + continue + + nan_ratio = df[feat].isna().sum() / len(df) + + if nan_ratio > 0.3: # More than 30% NaN + high_nan.append((feat, f"{nan_ratio*100:.1f}%")) + else: + available.append(feat) + + # Print feature availability + if missing: + print(f"\n⚠️ Missing features: {', '.join(missing)}") + if high_nan: + print(f"⚠️ High NaN features (>30%):") + for feat, ratio in high_nan: + print(f" - {feat}: {ratio} NaN") + + print(f"\nβœ“ Available features ({len(available)}):") + for feat in available: + nan_count = df[feat].isna().sum() + if nan_count > 0: + print(f" - {feat} ({nan_count:,} NaN)") + else: + print(f" - {feat}") + + # Store available features + self.available_features = available + + # Handle NaN based on strategy + if self.handle_nan == 'error': + # Check for any NaN in available features + target + check_cols = available + [target_col] + nan_counts = df[check_cols].isna().sum() + if nan_counts.sum() > 0: + print("\nNaN values found:") + for col, count in nan_counts[nan_counts > 0].items(): + print(f" {col}: {count}") + raise ValueError("NaN values found and handle_nan='error'") + df_clean = df + + elif self.handle_nan == 'drop': + # Drop rows with NaN in available features or target + check_cols = available + [target_col] + initial_len = len(df) + df_clean = df.dropna(subset=check_cols) + dropped = initial_len - len(df_clean) + + if dropped > 0: + pct = (dropped / initial_len) * 100 + print(f"\nβœ“ Dropped {dropped:,} rows ({pct:.2f}%) with NaN values") + + elif self.handle_nan == 'impute': + # Impute NaN values + df_clean = df.copy() + imputed = [] + + for feat in available: + nan_count = df_clean[feat].isna().sum() + if nan_count > 0: + if pd.api.types.is_numeric_dtype(df_clean[feat]): + fill_val = df_clean[feat].median() + df_clean[feat] = df_clean[feat].fillna(fill_val) + imputed.append(f"{feat} (median={fill_val:.2f})") + else: + mode_val = df_clean[feat].mode()[0] + df_clean[feat] = df_clean[feat].fillna(mode_val) + imputed.append(f"{feat} (mode={mode_val})") + + if imputed: + print(f"\nβœ“ Imputed features:") + for imp in imputed: + print(f" - {imp}") + + # Still drop rows with target NaN + target_nan = df_clean[target_col].isna().sum() + if target_nan > 0: + df_clean = df_clean.dropna(subset=[target_col]) + print(f"\nβœ“ Dropped {target_nan:,} rows with target NaN") + + else: + raise ValueError(f"Invalid handle_nan: {self.handle_nan}") + + print(f"\nFinal rows: {len(df_clean):,}") + + # Validate no NaN remains + remaining_nan = df_clean[available + [target_col]].isna().sum().sum() + if remaining_nan > 0: + raise ValueError(f"ERROR: {remaining_nan} NaN values remain after cleaning!") + + print("βœ“ No NaN values in features or target") + + return df_clean + + def _create_pipeline(self) -> Pipeline: + """Create sklearn pipeline.""" + if not self.feature_cols: + raise ValueError("Feature columns must be set before building pipeline") + + transformers = [] + + if 'distance_to_stop' in self.feature_cols: + transformers.append(( + 'poly_distance', + Pipeline([ + ('poly', PolynomialFeatures( + degree=self.poly_degree, + include_bias=False + )), + ('scale', StandardScaler()) + ]), + ['distance_to_stop'] + )) + + numeric_others = [ + col for col in self.numeric_features + if col != 'distance_to_stop' + ] + if numeric_others: + transformers.append(( + 'scale_numeric', + StandardScaler(), + numeric_others + )) + + if self.categorical_features: + transformers.append(( + 'encode_categorical', + _make_one_hot_encoder(), + self.categorical_features + )) + + return Pipeline([ + ('features', ColumnTransformer( + transformers, + remainder='drop', + sparse_threshold=0.0 + )), + ('ridge', Ridge(alpha=self.alpha)) + ]) + + def fit(self, train_df: pd.DataFrame, + target_col: str = 'time_to_arrival_seconds'): + """ + Train model. + + Args: + train_df: Training dataframe + target_col: Target column name + """ + # Clean data + train_clean = self._clean_data(train_df, target_col) + + # Build feature list from available features + feature_groups = self._get_feature_groups() + self.feature_cols = [] + + group_order = [ + 'core', + 'spatial_numeric', + 'temporal', + 'weather', + ] + for group in group_order: + for feat in feature_groups.get(group, []): + if feat in self.available_features and feat not in self.feature_cols: + self.feature_cols.append(feat) + + if not self.feature_cols: + raise ValueError("No features available after cleaning!") + + self.categorical_features = [] + self.numeric_features = list(self.feature_cols) + + print(f"\n{'='*60}") + print("Model Training") + print(f"{'='*60}") + print(f"Features ({len(self.feature_cols)}): {', '.join(self.feature_cols)}") + + # Prepare data + X = train_clean[self.feature_cols] + y = train_clean[target_col].values + + # Create and fit model + self.model = self._create_pipeline() + self.model.fit(X, y) + + print(f"βœ“ Model trained (poly_degree={self.poly_degree}, alpha={self.alpha})") + + def predict(self, X: pd.DataFrame) -> np.ndarray: + """ + Predict ETAs. + + Args: + X: DataFrame with features + + Returns: + Array of predicted ETAs in seconds + """ + if self.model is None: + raise ValueError("Model not trained") + + if self.feature_cols is None: + raise ValueError("Feature columns not set") + + # Check for missing features + missing = [f for f in self.feature_cols if f not in X.columns] + if missing: + raise ValueError(f"Missing features in input: {missing}") + + # Handle NaN in prediction data + X_pred = X[self.feature_cols].copy() + + if self.handle_nan == 'impute': + for col in self.numeric_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna(X_pred[col].median()) + for col in self.categorical_features: + if X_pred[col].isna().any(): + mode_series = X_pred[col].mode() + filler = mode_series.iloc[0] if not mode_series.empty else "missing" + X_pred[col] = X_pred[col].fillna(filler) + elif self.handle_nan == 'drop': + for col in self.numeric_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna(0.0) + for col in self.categorical_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna("missing") + + predictions = self.model.predict(X_pred[self.feature_cols]) + + return clip_predictions(predictions) + + def get_feature_importance(self) -> Dict[str, float]: + """Get feature coefficients (approximate importance).""" + if self.model is None: + return {} + + coefs = self.model.named_steps['ridge'].coef_ + transformer = self.model.named_steps['features'] + feature_names = transformer.get_feature_names_out() + + importance = { + name: abs(coef) + for name, coef in zip(feature_names, coefs) + } + + return dict(sorted(importance.items(), key=lambda x: x[1], reverse=True)) + + +def train_polyreg_time(dataset_name: str = "sample_dataset", + route_id: Optional[str] = None, + poly_degree: int = 2, + alpha: float = 1.0, + include_temporal: bool = True, + include_spatial: bool = True, + include_weather: bool = False, + handle_nan: str = 'drop', + test_size: float = 0.2, + save_model: bool = True, + pre_split: Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]] = None) -> Dict: + """ + Train and evaluate polynomial regression time model. + + Args: + dataset_name: Dataset name + route_id: Optional route ID for route-specific training + poly_degree: Polynomial degree for distance + alpha: Ridge regularization + include_temporal: Include temporal features + include_weather: Include weather features + handle_nan: 'drop', 'impute', or 'error' + test_size: Test set fraction + save_model: Save to registry + pre_split: Optional (train, val, test) DataFrames to reuse + + Returns: + Dictionary with model, metrics, metadata + """ + print(f"\n{'='*60}") + print(f"Polynomial Regression Time Model".center(60)) + print(f"{'='*60}\n") + + route_info = f" (route: {route_id})" if route_id else " (global)" + print(f"Scope{route_info}") + print(f"Config:") + print(f" poly_degree={poly_degree}, alpha={alpha}") + print(f" temporal={include_temporal}, spatial={include_spatial}") + print(f" weather={include_weather}, handle_nan='{handle_nan}'") + + # Load dataset + print(f"\nLoading dataset: {dataset_name}") + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Filter by route if specified + if route_id is not None: + df = dataset.df + df_filtered = df[df['route_id'] == route_id].copy() + print(f"Filtered to route {route_id}: {len(df_filtered):,} samples") + + if len(df_filtered) == 0: + raise ValueError(f"No data found for route {route_id}") + + dataset.df = df_filtered + + if pre_split is not None: + train_df, val_df, test_df = (df.copy() for df in pre_split) + else: + train_df, val_df, test_df = dataset.temporal_split( + train_frac=1-test_size-0.1, + val_frac=0.1 + ) + + train_test_summary(train_df, test_df, val_df) + + # Train model + print("\n" + "="*60) + print("Training") + print("="*60) + model = PolyRegTimeModel( + poly_degree=poly_degree, + alpha=alpha, + include_temporal=include_temporal, + include_spatial=include_spatial, + include_weather=include_weather, + handle_nan=handle_nan + ) + model.fit(train_df) + + # Validation + print(f"\n{'='*60}") + print("Validation Performance") + print("="*60) + y_val = val_df['time_to_arrival_seconds'].values + val_preds = model.predict(val_df) + val_metrics = compute_all_metrics(y_val, val_preds, prefix="val_") + print_metrics_table(val_metrics, "Validation") + + # Test + print(f"\n{'='*60}") + print("Test Performance") + print("="*60) + y_test = test_df['time_to_arrival_seconds'].values + test_preds = model.predict(test_df) + test_metrics = compute_all_metrics(y_test, test_preds, prefix="test_") + print_metrics_table(test_metrics, "Test") + + # Feature importance + importance = model.get_feature_importance() + if importance: + print(f"\nTop 5 Features by Coefficient:") + for i, (feat, coef) in enumerate(list(importance.items())[:5], 1): + print(f" {i}. {feat}: {coef:.6f}") + + # Metadata + metadata = { + 'model_type': 'polyreg_time', + 'dataset': dataset_name, + 'route_id': route_id, + 'poly_degree': poly_degree, + 'alpha': alpha, + 'include_temporal': include_temporal, + 'include_spatial': include_spatial, + 'include_weather': include_weather, + 'handle_nan': handle_nan, + 'n_features': len(model.feature_cols) if model.feature_cols else 0, + 'features': model.feature_cols, + 'n_samples': len(train_df) + len(val_df) + len(test_df), + 'n_trips': dataset.df['trip_id'].nunique() if route_id else None, + 'train_samples': len(train_df), + 'test_samples': len(test_df), + 'metrics': {**val_metrics, **test_metrics} + } + + # Save + if save_model: + feature_groups = [] + if include_temporal: + feature_groups.append('temporal') + if include_spatial: + feature_groups.append('spatial') + if include_weather: + feature_groups.append('weather') + model_key = ModelKey.generate( + model_type='polyreg_time', + dataset_name=dataset_name, + feature_groups=feature_groups, + route_id=route_id, + degree=poly_degree, + handle_nan=handle_nan + ) + + registry = get_registry() + registry.save_model(model_key, model, metadata) + metadata['model_key'] = model_key + print(f"\nβœ“ Model saved: {model_key}") + + return { + 'model': model, + 'metrics': metadata['metrics'], + 'metadata': metadata + } + + +if __name__ == "__main__": + # Example: Train with different configurations + + # Basic model - drop NaN + result1 = train_polyreg_time( + poly_degree=2, + include_temporal=True, + include_weather=False, + handle_nan='drop' + ) + + # With imputation + result2 = train_polyreg_time( + poly_degree=2, + include_temporal=True, + include_weather=False, + handle_nan='impute' + ) diff --git a/eta_prediction/models/train_all_models.py b/eta_prediction/models/train_all_models.py new file mode 100644 index 0000000..5412996 --- /dev/null +++ b/eta_prediction/models/train_all_models.py @@ -0,0 +1,393 @@ +""" +Train all models either globally or per-route for comparative analysis. +""" + +import argparse +import sys +from pathlib import Path +import pandas as pd +from typing import Tuple + +sys.path.append(str(Path(__file__).parent)) + +from common.data import load_dataset +from historical_mean.train import train_historical_mean +from polyreg_distance.train import train_polyreg_distance +from polyreg_time.train import train_polyreg_time +from ewma.train import train_ewma +from xgb.train import train_xgboost + +DEFAULT_TEST_SIZE = 0.2 +DEFAULT_VAL_FRAC = 0.1 +DEFAULT_TRAIN_FRAC = 1.0 - DEFAULT_TEST_SIZE - DEFAULT_VAL_FRAC + + +def _temporal_split_df(df: pd.DataFrame, + train_frac: float, + val_frac: float) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: + """Deterministically split a dataframe by vp_ts.""" + df_sorted = df.sort_values('vp_ts').reset_index(drop=True) + n = len(df_sorted) + train_end = int(n * train_frac) + val_end = int(n * (train_frac + val_frac)) + train_df = df_sorted.iloc[:train_end].copy() + val_df = df_sorted.iloc[train_end:val_end].copy() + test_df = df_sorted.iloc[val_end:].copy() + return train_df, val_df, test_df + + +def _clone_splits(splits: Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: + """Return deep copies of precomputed splits so callers can mutate freely.""" + return tuple(split.copy() for split in splits) + + +def train_all_models(dataset_name: str, + by_route: bool = False, + model_types: list = None, + save: bool = True): + """ + Train all baseline models, optionally per-route. + + Args: + dataset_name: Dataset to train on + by_route: If True, train separate model for each route + model_types: List of model types to train (None = all) + save: Whether to save models to registry + """ + + if model_types is None: + model_types = [ + 'historical_mean', + 'polyreg_distance', + 'polyreg_time', + 'ewma', + 'xgboost', # NEW + ] + + print(f"\n{'='*80}") + print(f"TRAINING ALL MODELS".center(80)) + print(f"{'='*80}") + print(f"Dataset: {dataset_name}") + print(f"Mode: {'Route-Specific' if by_route else 'Global'}") + print(f"Models: {', '.join(model_types)}") + print(f"Save: {save}") + print(f"{'='*80}\n") + + # Load dataset to get routes / global data + dataset = load_dataset(dataset_name) + dataset.clean_data() + + if by_route: + routes = sorted(dataset.df['route_id'].unique()) + print(f"Found {len(routes)} routes: {routes}\n") + + # Get trip counts per route for summary + route_stats = dataset.df.groupby('route_id').agg({ + 'trip_id': 'nunique', + 'time_to_arrival_seconds': 'count' + }).rename(columns={ + 'trip_id': 'n_trips', + 'time_to_arrival_seconds': 'n_samples' + }) + + print("Route Statistics:") + print(route_stats.to_string()) + print() + + # Train models for each route + results = {} + + for route_id in routes: + n_trips = route_stats.loc[route_id, 'n_trips'] + n_samples = route_stats.loc[route_id, 'n_samples'] + route_df = dataset.df[dataset.df['route_id'] == route_id].copy() + if route_df.empty: + print(f"\nSkipping route {route_id}: no samples after cleaning") + continue + route_splits = _temporal_split_df( + route_df, + DEFAULT_TRAIN_FRAC, + DEFAULT_VAL_FRAC, + ) + + print(f"\n{'#'*80}") + print(f"ROUTE {route_id} ({n_trips} trips, {n_samples} samples)".center(80)) + print(f"{'#'*80}\n") + + route_results = {} + + try: + if 'historical_mean' in model_types: + print(f"\n>>> Training Historical Mean for route {route_id}...") + result = train_historical_mean( + dataset_name=dataset_name, + route_id=route_id, + save_model=save, + pre_split=_clone_splits(route_splits), + ) + route_results['historical_mean'] = result + + if 'polyreg_distance' in model_types: + print(f"\n>>> Training PolyReg Distance for route {route_id}...") + result = train_polyreg_distance( + dataset_name=dataset_name, + route_id=route_id, + degree=2, + save_model=save, + pre_split=_clone_splits(route_splits), + ) + route_results['polyreg_distance'] = result + + if 'polyreg_time' in model_types: + print(f"\n>>> Training PolyReg Time for route {route_id}...") + result = train_polyreg_time( + dataset_name=dataset_name, + route_id=route_id, + poly_degree=2, + include_temporal=True, + save_model=save, + pre_split=_clone_splits(route_splits), + ) + route_results['polyreg_time'] = result + + if 'ewma' in model_types: + print(f"\n>>> Training EWMA for route {route_id}...") + result = train_ewma( + dataset_name=dataset_name, + route_id=route_id, + alpha=0.3, + save_model=save, + pre_split=_clone_splits(route_splits), + ) + route_results['ewma'] = result + + # NEW: XGBoost route-specific + if 'xgboost' in model_types: + print(f"\n>>> Training XGBoost for route {route_id}...") + result = train_xgboost( + dataset_name=dataset_name, + route_id=route_id, + save_model=save, + pre_split=_clone_splits(route_splits), + ) + route_results['xgboost'] = result + + results[route_id] = route_results + + except Exception as e: + print(f"\n❌ ERROR training models for route {route_id}: {e}") + import traceback + traceback.print_exc() + continue + + # Print summary + print(f"\n{'='*80}") + print("TRAINING SUMMARY".center(80)) + print(f"{'='*80}\n") + + summary_data = [] + for route_id in routes: + if route_id not in results: + continue + + n_trips = route_stats.loc[route_id, 'n_trips'] + n_samples = route_stats.loc[route_id, 'n_samples'] + + for model_type in model_types: + if model_type in results[route_id]: + metrics = results[route_id][model_type]['metrics'] + summary_data.append({ + 'route_id': route_id, + 'n_trips': n_trips, + 'n_samples': n_samples, # NEW: number of observations + 'model_type': model_type, + 'test_mae_min': metrics.get('test_mae_minutes', None), + 'test_rmse_sec': metrics.get('test_rmse_seconds', None), + 'test_r2': metrics.get('test_r2', None) + }) + + if summary_data: + summary_df = pd.DataFrame(summary_data) + summary_df = summary_df.sort_values( + ['n_trips', 'model_type'], + ascending=[False, True] + ) + + print("Performance by Route and Model:") + print(summary_df.to_string(index=False)) + + # Show correlation between training data size (trips) and performance + if len(summary_df) > 2: + print(f"\n{'='*80}") + print("Data Size vs Performance Analysis".center(80)) + print(f"{'='*80}\n") + + for model_type in model_types: + model_data = summary_df[summary_df['model_type'] == model_type] + if len(model_data) > 1: + corr = model_data['n_trips'].corr(model_data['test_mae_min']) + print(f"{model_type:20s}: trips vs MAE correlation = {corr:+.3f}") + + # NEW: Correlation between number of observations and performance + print(f"\n{'='*80}") + print("Observations vs Performance Analysis".center(80)) + print(f"{'='*80}\n") + + for model_type in model_types: + model_data = summary_df[summary_df['model_type'] == model_type] + if len(model_data) > 1: + corr_mae = model_data['n_samples'].corr(model_data['test_mae_min']) + corr_rmse = model_data['n_samples'].corr(model_data['test_rmse_sec']) + print( + f"{model_type:20s}: " + f"obs vs MAE = {corr_mae:+.3f}, " + f"obs vs RMSE = {corr_rmse:+.3f}" + ) + + return results + + else: + # Train global models + print("Training GLOBAL models across all routes...\n") + + results = {} + global_splits = dataset.temporal_split( + train_frac=DEFAULT_TRAIN_FRAC, + val_frac=DEFAULT_VAL_FRAC, + ) + + try: + if 'historical_mean' in model_types: + print("\n>>> Training Historical Mean (global)...") + results['historical_mean'] = train_historical_mean( + dataset_name=dataset_name, + save_model=save, + pre_split=_clone_splits(global_splits), + ) + + if 'polyreg_distance' in model_types: + print("\n>>> Training PolyReg Distance (global)...") + results['polyreg_distance'] = train_polyreg_distance( + dataset_name=dataset_name, + degree=2, + save_model=save, + pre_split=_clone_splits(global_splits), + ) + + if 'polyreg_time' in model_types: + print("\n>>> Training PolyReg Time (global)...") + results['polyreg_time'] = train_polyreg_time( + dataset_name=dataset_name, + poly_degree=2, + include_temporal=True, + save_model=save, + pre_split=_clone_splits(global_splits), + ) + + if 'ewma' in model_types: + print("\n>>> Training EWMA (global)...") + results['ewma'] = train_ewma( + dataset_name=dataset_name, + alpha=0.3, + save_model=save, + pre_split=_clone_splits(global_splits), + ) + + # NEW: XGBoost global + if 'xgboost' in model_types: + print("\n>>> Training XGBoost (global)...") + results['xgboost'] = train_xgboost( + dataset_name=dataset_name, + route_id=None, # global model + save_model=save, + pre_split=_clone_splits(global_splits), + ) + + except Exception as e: + print(f"\n❌ ERROR training global models: {e}") + import traceback + traceback.print_exc() + + # Print summary + print(f"\n{'='*80}") + print("TRAINING SUMMARY".center(80)) + print(f"{'='*80}\n") + + for model_type, result in results.items(): + metrics = result['metrics'] + print(f"{model_type:20s}: MAE = {metrics['test_mae_minutes']:.3f} min, " + f"RMSE = {metrics['test_rmse_seconds']:.1f} sec, " + f"RΒ² = {metrics.get('test_r2', 0):.3f}") + + return results + + +def main(): + parser = argparse.ArgumentParser( + description='Train ETA prediction models' + ) + + parser.add_argument( + '--dataset', + type=str, + default='sample_dataset', + help='Dataset name (default: sample_dataset)' + ) + + parser.add_argument( + '--by-route', + action='store_true', + help='Train separate models for each route' + ) + + parser.add_argument( + '--models', + type=str, + nargs='+', + choices=[ + 'historical_mean', + 'polyreg_distance', + 'polyreg_time', + 'ewma', + 'xgboost', # NEW + 'all' + ], + default=['all'], + help='Models to train (default: all)' + ) + + parser.add_argument( + '--no-save', + action='store_true', + help='Do not save models to registry (dry run)' + ) + + args = parser.parse_args() + + # Parse model types + if 'all' in args.models: + model_types = [ + 'historical_mean', + 'polyreg_distance', + 'polyreg_time', + 'ewma', + 'xgboost', # NEW + ] + else: + model_types = args.models + + # Train models + results = train_all_models( + dataset_name=args.dataset, + by_route=args.by_route, + model_types=model_types, + save=not args.no_save + ) + + print(f"\n{'='*80}") + print("βœ“ TRAINING COMPLETE".center(80)) + print(f"{'='*80}\n") + + +if __name__ == "__main__": + main() diff --git a/eta_prediction/models/xgb/predict.py b/eta_prediction/models/xgb/predict.py new file mode 100644 index 0000000..6695d47 --- /dev/null +++ b/eta_prediction/models/xgb/predict.py @@ -0,0 +1,113 @@ +""" +Prediction interface for XGBoost Time model. +""" + +import sys +from pathlib import Path +from typing import Dict, Optional + +import pandas as pd + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.registry import get_registry +from common.utils import format_seconds + + +def predict_eta( + model_key: str, + distance_to_stop: float, + progress_on_segment: Optional[float] = None, + progress_ratio: Optional[float] = None, + hour: Optional[int] = None, + day_of_week: Optional[int] = None, + is_peak_hour: Optional[bool] = None, + is_weekend: Optional[bool] = None, + is_holiday: Optional[bool] = None, + temperature_c: Optional[float] = None, + precipitation_mm: Optional[float] = None, + wind_speed_kmh: Optional[float] = None, +) -> Dict: + """ + Predict ETA using XGBoost time model. + + Args: + model_key: Model identifier + distance_to_stop: Distance in meters + progress_on_segment: 0-1 fraction of the current stop segment + progress_ratio: 0-1 fraction along the entire route + hour: Hour of day (0–23) + day_of_week: Day of week (0=Monday) + is_peak_hour: Peak hour flag + is_weekend: Weekend flag + is_holiday: Holiday flag + temperature_c: Temperature (Β°C) + precipitation_mm: Precipitation (mm) + wind_speed_kmh: Wind speed (km/h) + + Returns: + Dictionary with prediction and metadata. + """ + # Load model & metadata + registry = get_registry() + model = registry.load_model(model_key) + metadata = registry.load_metadata(model_key) + + # Prepare input + input_data = {"distance_to_stop": [distance_to_stop]} + + # Add optional features (only if provided) + optional_features = { + "progress_on_segment": progress_on_segment, + "progress_ratio": progress_ratio, + "hour": hour, + "day_of_week": day_of_week, + "is_peak_hour": is_peak_hour, + "is_weekend": is_weekend, + "is_holiday": is_holiday, + "temperature_c": temperature_c, + "precipitation_mm": precipitation_mm, + "wind_speed_kmh": wind_speed_kmh, + } + + for key, value in optional_features.items(): + if value is not None: + input_data[key] = [value] + + input_df = pd.DataFrame(input_data) + + # Predict + eta_seconds = float(model.predict(input_df)[0]) + + return { + "eta_seconds": eta_seconds, + "eta_minutes": eta_seconds / 60.0, + "eta_formatted": format_seconds(eta_seconds), + "model_key": model_key, + "model_type": metadata.get("model_type", "xgboost"), + "distance_to_stop_m": distance_to_stop, + "features_used": list(input_data.keys()), + } + + +def batch_predict(model_key: str, input_df: pd.DataFrame) -> pd.DataFrame: + """ + Batch prediction for XGBoost time model. + + Args: + model_key: Model identifier + input_df: DataFrame with required feature columns + + Returns: + DataFrame with additional prediction columns: + - predicted_eta_seconds + - predicted_eta_minutes + """ + registry = get_registry() + model = registry.load_model(model_key) + + result_df = input_df.copy() + result_df["predicted_eta_seconds"] = model.predict(input_df) + result_df["predicted_eta_minutes"] = result_df["predicted_eta_seconds"] / 60.0 + + return result_df diff --git a/eta_prediction/models/xgb/train.py b/eta_prediction/models/xgb/train.py new file mode 100644 index 0000000..5de5b27 --- /dev/null +++ b/eta_prediction/models/xgb/train.py @@ -0,0 +1,592 @@ +""" +XGBoost Regression Model - Time & Spatial Features +Fits a gradient boosted tree model with temporal, spatial, +and optional weather features for ETA prediction. +""" + +import sys +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +import numpy as np +import pandas as pd +from sklearn.compose import ColumnTransformer +from sklearn.preprocessing import OneHotEncoder +from xgboost import XGBRegressor + +sys.path.append(str(Path(__file__).parent.parent)) + +from common.data import load_dataset +from common.metrics import compute_all_metrics +from common.keys import ModelKey +from common.registry import get_registry +from common.utils import print_metrics_table, train_test_summary, clip_predictions + + +def _make_one_hot_encoder() -> OneHotEncoder: + """Return a OneHotEncoder that works across sklearn versions.""" + try: + return OneHotEncoder(handle_unknown="ignore", sparse_output=False) + except TypeError: + return OneHotEncoder(handle_unknown="ignore", sparse=False) + + +class XGBTimeModel: + """ + Gradient boosted tree model (XGBoost) with temporal/spatial features. + + Features: + - Core: distance_to_stop + - Temporal: hour, day_of_week, is_weekend, is_peak_hour + - Spatial: progress_on_segment, progress_ratio + - Weather: temperature_c, precipitation_mm, wind_speed_kmh + """ + + def __init__( + self, + max_depth: int = 5, + n_estimators: int = 200, + learning_rate: float = 0.05, + subsample: float = 0.8, + colsample_bytree: float = 0.8, + include_temporal: bool = True, + include_spatial: bool = True, + include_weather: bool = False, + handle_nan: str = "drop", # 'drop', 'impute', or 'error' + random_state: int = 42, + n_jobs: int = 4, + ) -> None: + """ + Initialize model. + + Args: + max_depth: Maximum tree depth + n_estimators: Number of boosting stages + learning_rate: Learning rate (eta) + subsample: Row subsample ratio per tree + colsample_bytree: Column subsample ratio per tree + include_temporal: Include time-of-day features + include_spatial: Include spatial progress/segment features + include_weather: Include weather features + handle_nan: How to handle NaN - 'drop', 'impute', or 'error' + random_state: Random seed + n_jobs: Number of parallel threads + """ + self.max_depth = max_depth + self.n_estimators = n_estimators + self.learning_rate = learning_rate + self.subsample = subsample + self.colsample_bytree = colsample_bytree + self.include_temporal = include_temporal + self.include_spatial = include_spatial + self.include_weather = include_weather + self.handle_nan = handle_nan + self.random_state = random_state + self.n_jobs = n_jobs + + self.model: Optional[XGBRegressor] = None + self.feature_cols: Optional[List[str]] = None + self.available_features: Optional[List[str]] = None + self.preprocessor: Optional[ColumnTransformer] = None + self.categorical_features: List[str] = [] + self.numeric_features: List[str] = [] + + def _get_feature_groups(self) -> Dict[str, List[str]]: + """Define feature groups.""" + return { + "core": ["distance_to_stop"], + "spatial_numeric": ( + ["progress_on_segment", "progress_ratio"] + if self.include_spatial + else [] + ), + "temporal": ( + ["hour", "day_of_week", "is_weekend", "is_peak_hour"] + if self.include_temporal + else [] + ), + "weather": ( + ["temperature_c", "precipitation_mm", "wind_speed_kmh"] + if self.include_weather + else [] + ), + } + + def _build_preprocessor(self) -> None: + """Create a ColumnTransformer to handle numeric + categorical features.""" + if not self.feature_cols: + raise ValueError("Feature columns must be set before building the preprocessor") + + transformers = [] + numeric_features = [ + col for col in self.feature_cols if col not in self.categorical_features + ] + self.numeric_features = numeric_features + + if numeric_features: + transformers.append(( + "numeric", + "passthrough", + numeric_features, + )) + + if self.categorical_features: + transformers.append(( + "categorical", + _make_one_hot_encoder(), + self.categorical_features, + )) + + self.preprocessor = ColumnTransformer( + transformers, + remainder="drop", + sparse_threshold=0.0, + ) + + def _clean_data( + self, + df: pd.DataFrame, + target_col: str = "time_to_arrival_seconds", + ) -> pd.DataFrame: + """ + Clean data and handle NaN values. + + Args: + df: Input dataframe + target_col: Target column name + + Returns: + Cleaned dataframe + """ + print(f"\n{'=' * 60}") + print("Data Cleaning (XGBoost)".center(60)) + print(f"{'=' * 60}") + print(f"Initial rows: {len(df):,}") + + # Get all potential features + feature_groups = self._get_feature_groups() + all_features: List[str] = [] + for group_features in feature_groups.values(): + all_features.extend(group_features) + + # Check which features are available and have acceptable NaN levels + available: List[str] = [] + missing: List[str] = [] + high_nan: List[tuple[str, str]] = [] + + for feat in all_features: + if feat not in df.columns: + missing.append(feat) + continue + + nan_ratio = df[feat].isna().sum() / len(df) + + if nan_ratio > 0.3: # More than 30% NaN + high_nan.append((feat, f"{nan_ratio * 100:.1f}%")) + else: + available.append(feat) + + # Print feature availability + if missing: + print(f"\n⚠️ Missing features: {', '.join(missing)}") + if high_nan: + print("⚠️ High NaN features (>30%):") + for feat, ratio in high_nan: + print(f" - {feat}: {ratio} NaN") + + print(f"\nβœ“ Available features ({len(available)}):") + for feat in available: + nan_count = df[feat].isna().sum() + if nan_count > 0: + print(f" - {feat} ({nan_count:,} NaN)") + else: + print(f" - {feat}") + + # Store available features + self.available_features = available + + # Handle NaN based on strategy + if self.handle_nan == "error": + # Check for any NaN in available features + target + check_cols = available + [target_col] + nan_counts = df[check_cols].isna().sum() + if nan_counts.sum() > 0: + print("\nNaN values found:") + for col, count in nan_counts[nan_counts > 0].items(): + print(f" {col}: {count}") + raise ValueError("NaN values found and handle_nan='error'") + df_clean = df + + elif self.handle_nan == "drop": + # Drop rows with NaN in available features or target + check_cols = available + [target_col] + initial_len = len(df) + df_clean = df.dropna(subset=check_cols) + dropped = initial_len - len(df_clean) + + if dropped > 0: + pct = (dropped / initial_len) * 100 + print(f"\nβœ“ Dropped {dropped:,} rows ({pct:.2f}%) with NaN values") + + elif self.handle_nan == "impute": + # Impute NaN values + df_clean = df.copy() + imputed: List[str] = [] + + for feat in available: + nan_count = df_clean[feat].isna().sum() + if nan_count > 0: + if pd.api.types.is_numeric_dtype(df_clean[feat]): + fill_val = df_clean[feat].median() + df_clean[feat] = df_clean[feat].fillna(fill_val) + imputed.append(f"{feat} (median={fill_val:.2f})") + else: + mode_val = df_clean[feat].mode()[0] + df_clean[feat] = df_clean[feat].fillna(mode_val) + imputed.append(f"{feat} (mode={mode_val})") + + if imputed: + print(f"\nβœ“ Imputed features:") + for imp in imputed: + print(f" - {imp}") + + # Still drop rows with target NaN + target_nan = df_clean[target_col].isna().sum() + if target_nan > 0: + df_clean = df_clean.dropna(subset=[target_col]) + print(f"\nβœ“ Dropped {target_nan:,} rows with target NaN") + + else: + raise ValueError(f"Invalid handle_nan: {self.handle_nan}") + + print(f"\nFinal rows: {len(df_clean):,}") + + # Validate no NaN remains + remaining_nan = df_clean[available + [target_col]].isna().sum().sum() + if remaining_nan > 0: + raise ValueError( + f"ERROR: {remaining_nan} NaN values remain after cleaning!" + ) + + print("βœ“ No NaN values in features or target") + + return df_clean + + def fit( + self, + train_df: pd.DataFrame, + target_col: str = "time_to_arrival_seconds", + ) -> None: + """ + Train model. + + Args: + train_df: Training dataframe + target_col: Target column name + """ + # Clean data + train_clean = self._clean_data(train_df, target_col) + + # Build feature list from available features + feature_groups = self._get_feature_groups() + self.feature_cols = [] + + group_order = [ + "core", + "spatial_numeric", + "spatial_categorical", + "temporal", + "weather", + ] + for group in group_order: + for feat in feature_groups.get(group, []): + if feat in self.available_features and feat not in self.feature_cols: + self.feature_cols.append(feat) + + if not self.feature_cols: + raise ValueError("No features available after cleaning!") + + self.categorical_features = [] + self._build_preprocessor() + + print(f"\n{'=' * 60}") + print("Model Training (XGBoost)".center(60)) + print(f"{'=' * 60}") + print(f"Features ({len(self.feature_cols)}): {', '.join(self.feature_cols)}") + + # Prepare data + X = train_clean[self.feature_cols] + y = train_clean[target_col].values + + X_transformed = self.preprocessor.fit_transform(X) + + # Create and fit model + self.model = XGBRegressor( + max_depth=self.max_depth, + n_estimators=self.n_estimators, + learning_rate=self.learning_rate, + subsample=self.subsample, + colsample_bytree=self.colsample_bytree, + objective="reg:squarederror", + random_state=self.random_state, + n_jobs=self.n_jobs, + ) + self.model.fit(X_transformed, y) + + print( + f"βœ“ XGBoost model trained " + f"(max_depth={self.max_depth}, n_estimators={self.n_estimators}, " + f"learning_rate={self.learning_rate})" + ) + + def predict(self, X: pd.DataFrame) -> np.ndarray: + """ + Predict ETAs. + + Args: + X: DataFrame with features + + Returns: + Array of predicted ETAs in seconds + """ + if self.model is None: + raise ValueError("Model not trained") + + if self.feature_cols is None or self.preprocessor is None: + raise ValueError("Feature/preprocessor not set") + + # Check for missing features + missing = [f for f in self.feature_cols if f not in X.columns] + if missing: + raise ValueError(f"Missing features in input: {missing}") + + # Handle NaN in prediction data + X_pred = X[self.feature_cols].copy() + + if self.handle_nan == "impute": + for col in self.numeric_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna(X_pred[col].median()) + for col in self.categorical_features: + if X_pred[col].isna().any(): + mode_series = X_pred[col].mode() + filler = mode_series.iloc[0] if not mode_series.empty else "missing" + X_pred[col] = X_pred[col].fillna(filler) + elif self.handle_nan == "drop": + for col in self.numeric_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna(0.0) + for col in self.categorical_features: + if X_pred[col].isna().any(): + X_pred[col] = X_pred[col].fillna("missing") + + X_transformed = self.preprocessor.transform(X_pred[self.feature_cols]) + predictions = self.model.predict(X_transformed) + + return clip_predictions(predictions) + + def get_feature_importance(self) -> Dict[str, float]: + """Get feature importances as reported by XGBoost.""" + if self.model is None or self.preprocessor is None: + return {} + + if not hasattr(self.model, "feature_importances_"): + return {} + + importances = self.model.feature_importances_ + feature_names = self.preprocessor.get_feature_names_out() + + # Map back to feature names + importance = { + feat: float(imp) + for feat, imp in zip(feature_names, importances) + } + + # Sort by importance + return dict(sorted(importance.items(), key=lambda x: x[1], reverse=True)) + + +def train_xgboost( + dataset_name: str = "sample_dataset", + route_id: Optional[str] = None, + max_depth: int = 5, + n_estimators: int = 200, + learning_rate: float = 0.05, + subsample: float = 0.8, + colsample_bytree: float = 0.8, + include_temporal: bool = True, + include_spatial: bool = True, + include_weather: bool = False, + handle_nan: str = "drop", + test_size: float = 0.2, + save_model: bool = True, + pre_split: Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]] = None, +) -> Dict: + """ + Train and evaluate XGBoost time model. + + Args: + dataset_name: Dataset name + route_id: Optional route ID for route-specific training + max_depth: Maximum tree depth + n_estimators: Number of boosting rounds + learning_rate: Learning rate (eta) + subsample: Row subsample ratio per tree + colsample_bytree: Column subsample ratio per tree + include_temporal: Include temporal features + include_spatial: Include spatial features + include_weather: Include weather features + handle_nan: 'drop', 'impute', or 'error' + test_size: Test set fraction (test); val is fixed at 0.1 + save_model: Save to registry + pre_split: Optional (train, val, test) DataFrames to reuse + + Returns: + Dictionary with model, metrics, metadata + """ + print(f"\n{'=' * 60}") + print("XGBoost Time Model".center(60)) + print(f"{'=' * 60}\n") + + route_info = f" (route: {route_id})" if route_id else " (global)" + print(f"Scope{route_info}") + print("Config:") + print(f" max_depth={max_depth}, n_estimators={n_estimators}, learning_rate={learning_rate}") + print(f" subsample={subsample}, colsample_bytree={colsample_bytree}") + print(f" temporal={include_temporal}, spatial={include_spatial}") + print(f" weather={include_weather}, handle_nan='{handle_nan}'") + + # Load dataset + print(f"\nLoading dataset: {dataset_name}") + dataset = load_dataset(dataset_name) + dataset.clean_data() + + # Filter by route if specified + if route_id is not None: + df = dataset.df + df_filtered = df[df["route_id"] == route_id].copy() + print(f"Filtered to route {route_id}: {len(df_filtered):,} samples") + + if len(df_filtered) == 0: + raise ValueError(f"No data found for route {route_id}") + + dataset.df = df_filtered + + # Split data (train / val / test) + if pre_split is not None: + train_df, val_df, test_df = (df.copy() for df in pre_split) + else: + train_df, val_df, test_df = dataset.temporal_split( + train_frac=1 - test_size - 0.1, + val_frac=0.1, + ) + + train_test_summary(train_df, test_df, val_df) + + # Train model + print("\n" + "=" * 60) + print("Training".center(60)) + print("=" * 60) + + model = XGBTimeModel( + max_depth=max_depth, + n_estimators=n_estimators, + learning_rate=learning_rate, + subsample=subsample, + colsample_bytree=colsample_bytree, + include_temporal=include_temporal, + include_spatial=include_spatial, + include_weather=include_weather, + handle_nan=handle_nan, + ) + model.fit(train_df) + + # Validation + print(f"\n{'=' * 60}") + print("Validation Performance".center(60)) + print(f"{'=' * 60}") + y_val = val_df["time_to_arrival_seconds"].values + val_preds = model.predict(val_df) + val_metrics = compute_all_metrics(y_val, val_preds, prefix="val_") + print_metrics_table(val_metrics, "Validation") + + # Test + print(f"\n{'=' * 60}") + print("Test Performance".center(60)) + print(f"{'=' * 60}") + y_test = test_df["time_to_arrival_seconds"].values + test_preds = model.predict(test_df) + test_metrics = compute_all_metrics(y_test, test_preds, prefix="test_") + print_metrics_table(test_metrics, "Test") + + # Feature importance + importance = model.get_feature_importance() + if importance: + print(f"\nTop 5 Features by Importance:") + for i, (feat, imp) in enumerate(list(importance.items())[:5], 1): + print(f" {i}. {feat}: {imp:.6f}") + + # Metadata + metadata = { + "model_type": "xgboost", + "dataset": dataset_name, + "route_id": route_id, + "max_depth": max_depth, + "n_estimators": n_estimators, + "learning_rate": learning_rate, + "subsample": subsample, + "colsample_bytree": colsample_bytree, + "include_temporal": include_temporal, + "include_spatial": include_spatial, + "include_weather": include_weather, + "handle_nan": handle_nan, + "n_features": len(model.feature_cols) if model.feature_cols else 0, + "features": model.feature_cols, + "n_samples": len(train_df) + len(val_df) + len(test_df), + "n_trips": dataset.df["trip_id"].nunique() if route_id else None, + "train_samples": len(train_df), + "test_samples": len(test_df), + "metrics": {**val_metrics, **test_metrics}, + } + + # Save + if save_model: + feature_groups = [] + if include_temporal: + feature_groups.append("temporal") + if include_spatial: + feature_groups.append("spatial") + if include_weather: + feature_groups.append("weather") + + model_key = ModelKey.generate( + model_type="xgboost", + dataset_name=dataset_name, + feature_groups=feature_groups, + route_id=route_id, + max_depth=max_depth, + n_estimators=n_estimators, + learning_rate=learning_rate, + handle_nan=handle_nan, + ) + + registry = get_registry() + registry.save_model(model_key, model, metadata) + metadata["model_key"] = model_key + print(f"\nβœ“ Model saved: {model_key}") + + return { + "model": model, + "metrics": metadata["metrics"], + "metadata": metadata, + } + + +if __name__ == "__main__": + # Example: basic global model + _ = train_xgboost( + dataset_name="sample_dataset", + include_temporal=True, + include_weather=False, + handle_nan="drop", + ) From 0b6f921313442e947c14c1a211f0c7ecf4157cfb Mon Sep 17 00:00:00 2001 From: Jae Date: Thu, 27 Nov 2025 22:12:45 -0600 Subject: [PATCH 4/5] feat(service): PR4 - Add ETA Runtime Service (Depends on PR3) --- eta_prediction/eta_service/README.md | 231 ++++++++ eta_prediction/eta_service/__init__.py | 11 + eta_prediction/eta_service/estimator.py | 352 ++++++++++++ eta_prediction/eta_service/test_estimator.py | 560 +++++++++++++++++++ 4 files changed, 1154 insertions(+) create mode 100644 eta_prediction/eta_service/README.md create mode 100644 eta_prediction/eta_service/__init__.py create mode 100644 eta_prediction/eta_service/estimator.py create mode 100644 eta_prediction/eta_service/test_estimator.py diff --git a/eta_prediction/eta_service/README.md b/eta_prediction/eta_service/README.md new file mode 100644 index 0000000..a229f18 --- /dev/null +++ b/eta_prediction/eta_service/README.md @@ -0,0 +1,231 @@ +# ETA Service + +Real-time arrival time estimation service for transit vehicles. + +## Overview + +The `eta_service` provides a production-ready interface for generating ETA predictions from live vehicle positions. It bridges the real-time data stream (MQTT/Redis) with trained ML models. + +## Current Status: MVP (Phase 1) + +**What's Working:** +- βœ… Basic `estimate_stop_times()` function +- βœ… Temporal + spatial feature extraction +- βœ… Model registry integration +- βœ… Predictions for next N stops +- βœ… Error handling for missing models/data + +**Not Yet Implemented:** +- ⏳ Redis caching (upcoming stops, predictions) +- ⏳ Persistent route-specific model overrides +- ⏳ Shape-aware progress (currently geometric fallback only) +- ⏳ Weather integrations (currently constants) +- ⏳ Confidence intervals +- ⏳ Database integration for stop sequences + +--- + +## Quick Start + +```python +from eta_service import estimate_stop_times +from datetime import datetime, timezone + +# Vehicle position from MQTT/Redis (matches your MQTT format) +vehicle_position = { + 'vehicle_id': 'vehicle_42', + 'route': '1', + 'lat': 9.9281, + 'lon': -84.0907, + 'speed': 10.5, # m/s (will be converted to km/h internally) + 'heading': 90, + 'timestamp': datetime.now(timezone.utc).isoformat() +} + +# Upcoming stops (from cached route data) +upcoming_stops = [ + {'stop_id': 'stop_001', 'stop_sequence': 5, 'total_stop_sequence': 20, 'lat': 9.9291, 'lon': -84.0897}, + {'stop_id': 'stop_002', 'stop_sequence': 6, 'total_stop_sequence': 20, 'lat': 9.9301, 'lon': -84.0887}, + {'stop_id': 'stop_003', 'stop_sequence': 7, 'total_stop_sequence': 20, 'lat': 9.9311, 'lon': -84.0877}, +] + +# Get predictions +result = estimate_stop_times( + vehicle_position=vehicle_position, + upcoming_stops=upcoming_stops, + route_id='1', + trip_id='trip_001', + max_stops=3 +) + +# Access predictions +print(f"Vehicle: {result['vehicle_id']}") +print(f"Model: {result['model_type']}") +for pred in result['predictions']: + if not pred.get('error'): + print(f"Stop {pred['stop_id']}: {pred['eta_formatted']} ({pred['eta_minutes']:.1f} min)") +``` + +--- + +## Function Reference + +### `estimate_stop_times()` + +```python +estimate_stop_times( + vehicle_position: dict, + upcoming_stops: list[dict], + route_id: str = None, + trip_id: str = None, + model_key: str = None, + max_stops: int = 3, +) -> dict +``` + +**Parameters:** +- `vehicle_position`: Vehicle data with: + - `vehicle_id` (str) + - `lat`, `lon` (float) + - `speed` (float, in m/s) - converted to km/h internally + - `heading` (int, degrees) - optional + - `timestamp` (str, ISO format) +- `upcoming_stops`: List of stops with `stop_id`, `stop_sequence`, `lat`, `lon` +- `route_id`: Route identifier (optional, passed to models) +- `trip_id`: Trip identifier (optional, for context) +- `model_key`: Specific model to use (if `None`, uses best from registry) +- `max_stops`: Maximum stops to predict (default: 3) + +**Returns:** +```python +{ + 'vehicle_id': str, + 'route_id': str, + 'trip_id': str, + 'computed_at': str, # ISO timestamp + 'model_key': str, + 'model_type': str, # 'historical_mean', 'ewma', 'polyreg_distance', 'polyreg_time' + 'predictions': [ + { + 'stop_id': str, + 'stop_sequence': int, + 'distance_to_stop_m': float, + 'eta_seconds': float, + 'eta_minutes': float, + 'eta_formatted': str, # e.g., "5m 23s" + 'eta_timestamp': str, # ISO timestamp + }, + ... + ] +} +``` + +**Important Notes:** +- Speed must be in **m/s** (matches your MQTT format) +- Each model type has different feature requirements, handled internally +- Models use appropriate features based on their training configuration +- Missing features are filled with sensible defaults + +--- + +## Testing + +Run the test suite: + +```bash +cd eta_prediction/ +python eta_service/test_estimator.py +``` + +Test individual scenarios: + +```python +from eta_service.test_estimator import test_basic_prediction +test_basic_prediction() +``` + +--- + +## Integration with Data Pipeline + +### Expected Flow (When Complete): + +``` +MQTT Broker (raw vehicle data) + ↓ +Redis Subscriber + ↓ +Bytewax Stream Processor + ↓ +estimate_stop_times() ← YOU ARE HERE + ↓ +Redis (predictions cache) + ↓ +API / Dashboard +``` + +### Current MVP Flow: + +``` +Mock/Real Vehicle Position + ↓ +estimate_stop_times() + ↓ +Return predictions dict +``` + +--- + +## Directory Structure + +``` +eta_service/ +β”œβ”€β”€ __init__.py # Module exports +β”œβ”€β”€ estimator.py # Main estimation logic (MVP) +β”œβ”€β”€ test_estimator.py # Test suite +β”œβ”€β”€ cache.py # (TODO: Redis operations) +β”œβ”€β”€ config.py # (TODO: Configuration) +└── README.md # This file +``` + +--- + +## Next Steps (Phase 2) + +1. **Redis Integration** + - Cache upcoming stops by route + - Store predictions with TTL + - Implement prediction retrieval API + +2. **Enhanced Model Selection** + - Route-specific model lookup + - Fallback hierarchy + - Model performance monitoring + +3. **Additional Features** + - Shape-derived progress metrics + - Weather integration + - Confidence intervals + +4. **Performance Optimization** + - Batch predictions for multiple vehicles + - Model warm-up/preloading + - Feature extraction caching + +--- + +## Dependencies + +- `feature_engineering` module (temporal, spatial) +- `models` module (registry, trained models) +- Python 3.10+ +- Standard library: `math`, `datetime`, `pathlib` + +--- + +## Notes + +- All timestamps are UTC +- Distances in meters, speeds in km/h +- Models must be trained and registered before use +- Function is designed to be called from stream processors (Bytewax, Flink, etc.) diff --git a/eta_prediction/eta_service/__init__.py b/eta_prediction/eta_service/__init__.py new file mode 100644 index 0000000..5122ad2 --- /dev/null +++ b/eta_prediction/eta_service/__init__.py @@ -0,0 +1,11 @@ + +""" +ETA Service Module + +Main interface for real-time ETA predictions. +""" + +from .estimator import estimate_stop_times + +__version__ = "0.1.0" +__all__ = ["estimate_stop_times"] \ No newline at end of file diff --git a/eta_prediction/eta_service/estimator.py b/eta_prediction/eta_service/estimator.py new file mode 100644 index 0000000..1e2a04b --- /dev/null +++ b/eta_prediction/eta_service/estimator.py @@ -0,0 +1,352 @@ +""" +ETA Service - Enhanced Implementation +Estimates stop arrival times from vehicle positions with route-specific model support. +""" + +import sys +from pathlib import Path +from datetime import datetime, timezone +import math + +# Add parent directories to path for imports +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / "feature_engineering")) +sys.path.insert(0, str(project_root / "models")) + +from feature_engineering.temporal import extract_temporal_features +from models.common.registry import get_registry + + +def haversine_distance(lat1, lon1, lat2, lon2): + """Calculate distance between two points in meters.""" + R = 6371000 # Earth radius in meters + phi1, phi2 = math.radians(lat1), math.radians(lat2) + dphi = math.radians(lat2 - lat1) + dlambda = math.radians(lon2 - lon1) + + a = math.sin(dphi/2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda/2)**2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + + return R * c + + +def _progress_features(vehicle_position, stop, next_stop, total_segments_hint): + """Approximate distance/progress metrics without requiring shapes.""" + vp_lat = vehicle_position["lat"] + vp_lon = vehicle_position["lon"] + stop_lat = stop["lat"] + stop_lon = stop["lon"] + + distance_to_stop = haversine_distance(vp_lat, vp_lon, stop_lat, stop_lon) + + progress_on_segment = 0.0 + if next_stop: + next_lat = next_stop["lat"] + next_lon = next_stop["lon"] + segment_length = haversine_distance(stop_lat, stop_lon, next_lat, next_lon) + if segment_length == 0.0: + distance_to_next = 0.0 + else: + distance_to_next = haversine_distance(vp_lat, vp_lon, next_lat, next_lon) + progress_on_segment = max(0.0, min(1.0, 1.0 - (distance_to_next / segment_length))) + else: + distance_to_next = None + + stop_seq = ( + stop.get("stop_sequence") + or stop.get("sequence") + or stop.get("stop_order") + or 1 + ) + total_segments = ( + stop.get("total_stop_sequence") + or total_segments_hint + or stop_seq + ) + completed = max(float(stop_seq) - 1.0, 0.0) + denom = max(float(total_segments), 1.0) + progress_ratio = max(0.0, min(1.0, (completed + progress_on_segment) / denom)) + + return distance_to_stop, progress_on_segment, progress_ratio + + +def _predict_with_model(model_key, model_type, features, distance_m): + """ + Call the appropriate predict_eta function based on model type. + """ + + if model_type == 'historical_mean': + from models.historical_mean.predict import predict_eta + return predict_eta( + model_key=model_key, + route_id=features.get('route_id', 'unknown'), + stop_sequence=features.get('stop_sequence', 0), + hour=features.get('hour', 0), + day_of_week=features.get('day_of_week', 0), + is_peak_hour=features.get('is_peak_hour', False) + ) + + elif model_type == 'ewma': + from models.ewma.predict import predict_eta + return predict_eta( + model_key=model_key, + route_id=features.get('route_id', 'unknown'), + stop_sequence=features.get('stop_sequence', 0), + hour=features.get('hour', 0) + ) + + elif model_type == 'polyreg_distance': + from models.polyreg_distance.predict import predict_eta + return predict_eta( + model_key=model_key, + distance_to_stop=distance_m + ) + + elif model_type == 'polyreg_time': + from models.polyreg_time.predict import predict_eta + return predict_eta( + model_key=model_key, + distance_to_stop=distance_m, + progress_on_segment=features.get('progress_on_segment'), + progress_ratio=features.get('progress_ratio'), + hour=features.get('hour', 0), + day_of_week=features.get('day_of_week', 0), + is_peak_hour=features.get('is_peak_hour', False), + is_weekend=features.get('is_weekend', False), + is_holiday=features.get('is_holiday', False), + temperature_c=features.get('temperature_c', 25.0), + precipitation_mm=features.get('precipitation_mm', 0.0), + wind_speed_kmh=features.get('wind_speed_kmh') + ) + + # βœ… NEW XGBOOST BRANCH (consistent with xgb/predict.py) + elif model_type == 'xgboost': + from models.xgb.predict import predict_eta + + return predict_eta( + model_key=model_key, + distance_to_stop=distance_m, + progress_on_segment=features.get('progress_on_segment'), + progress_ratio=features.get('progress_ratio'), + hour=features.get('hour', 0), + day_of_week=features.get('day_of_week', 0), + is_peak_hour=features.get('is_peak_hour', False), + is_weekend=features.get('is_weekend', False), + is_holiday=features.get('is_holiday', False), + temperature_c=features.get('temperature_c', 25.0), + precipitation_mm=features.get('precipitation_mm', 0.0), + wind_speed_kmh=features.get('wind_speed_kmh', None) + ) + + else: + raise ValueError(f"Unknown model type: {model_type}") + + +def estimate_stop_times( + vehicle_position: dict, + upcoming_stops: list[dict], + route_id: str = None, + trip_id: str = None, + model_key: str = None, + model_type: str = None, + prefer_route_model: bool = True, + max_stops: int = 3, +) -> dict: + + # Validate inputs + if not vehicle_position or not upcoming_stops: + return { + 'vehicle_id': vehicle_position.get('vehicle_id', 'unknown') if vehicle_position else 'unknown', + 'route_id': route_id, + 'trip_id': trip_id, + 'computed_at': datetime.now(timezone.utc).isoformat(), + 'model_key': None, + 'predictions': [], + 'error': 'Missing vehicle position or stops' + } + + stops_to_predict = upcoming_stops[:max_stops] + + # Parse timestamp + vp_timestamp_str = vehicle_position['timestamp'] + if vp_timestamp_str.endswith('Z'): + vp_timestamp_str = vp_timestamp_str.replace('Z', '+00:00') + vp_timestamp = datetime.fromisoformat(vp_timestamp_str) + + # Extract temporal features + temporal_features = extract_temporal_features( + vp_timestamp, + tz='America/Costa_Rica', + region='CR' + ) + + # Determine route + if route_id is None: + route_id = vehicle_position.get('route', 'unknown') + + # Load registry + registry = get_registry() + model_scope = 'unknown' + + # ============================================================ + # SMART MODEL SELECTION + # ============================================================ + if model_key is None: + if prefer_route_model and route_id and route_id != 'unknown': + model_key = registry.get_best_model( + model_type=model_type, + route_id=route_id, + metric='test_mae_seconds' + ) + + if model_key: + model_scope = 'route' + print(f"[ETA Service] Using route-specific model for route {route_id}") + else: + print(f"[ETA Service] No route model for {route_id}. Trying global.") + model_key = registry.get_best_model( + model_type=model_type, + route_id='global', + metric='test_mae_seconds' + ) + model_scope = 'global' + else: + model_key = registry.get_best_model( + model_type=model_type, + route_id='global', + metric='test_mae_seconds' + ) + model_scope = 'global' + + # Last fallback + if model_key is None: + model_key = registry.get_best_model(model_type=model_type) + + if model_key is None: + return { + 'vehicle_id': vehicle_position['vehicle_id'], + 'route_id': route_id, + 'trip_id': trip_id, + 'computed_at': datetime.now(timezone.utc).isoformat(), + 'model_key': None, + 'predictions': [], + 'error': 'No trained models found for model_type' + } + + # Load metadata + try: + model_metadata = registry.load_metadata(model_key) + actual_model_type = model_metadata.get('model_type', 'unknown') + model_route_id = model_metadata.get('route_id') + + if model_route_id not in (None, 'global'): + model_scope = 'route' + elif model_scope == 'unknown': + model_scope = 'global' + + except Exception as e: + return { + 'vehicle_id': vehicle_position['vehicle_id'], + 'route_id': route_id, + 'trip_id': trip_id, + 'computed_at': datetime.now(timezone.utc).isoformat(), + 'model_key': model_key, + 'predictions': [], + 'error': f'Failed to load model metadata: {str(e)}' + } + + # ============================================================ + # PREDICT STOP ETAs + # ============================================================ + predictions = [] + approx_total_segments = max( + ( + stop.get('total_stop_sequence') + or stop.get('stop_sequence') + or stop.get('sequence') + or 0 + ) + for stop in stops_to_predict + ) if stops_to_predict else 0 + if approx_total_segments <= 0: + approx_total_segments = max(len(stops_to_predict), 1) + + for idx, stop in enumerate(stops_to_predict): + next_stop = stops_to_predict[idx + 1] if idx + 1 < len(stops_to_predict) else None + + distance_m, progress_on_segment, progress_ratio = _progress_features( + vehicle_position, + stop, + next_stop, + approx_total_segments, + ) + + stop_sequence_value = ( + stop.get('stop_sequence') + or stop.get('sequence') + or stop.get('stop_order') + or idx + 1 + ) + + # Build features + features = { + 'route_id': route_id, + 'stop_sequence': stop_sequence_value, + 'distance_to_stop': distance_m, + 'progress_on_segment': progress_on_segment, + 'progress_ratio': progress_ratio, + 'hour': temporal_features['hour'], + 'day_of_week': temporal_features['day_of_week'], + 'is_weekend': temporal_features['is_weekend'], + 'is_holiday': temporal_features['is_holiday'], + 'is_peak_hour': temporal_features['is_peak_hour'], + 'temperature_c': 25.0, + 'precipitation_mm': 0.0, + 'wind_speed_kmh': None, + } + + try: + result = _predict_with_model(model_key, actual_model_type, features, distance_m) + + eta_seconds = result.get('eta_seconds', 0.0) + eta_minutes = eta_seconds / 60.0 + eta_formatted = result.get( + 'eta_formatted', + f"{int(eta_minutes)}m {int(eta_seconds % 60)}s" + ) + + eta_ts = datetime.fromtimestamp(vp_timestamp.timestamp() + eta_seconds, tz=timezone.utc) + + predictions.append({ + 'stop_id': stop['stop_id'], + 'stop_sequence': stop_sequence_value, + 'distance_to_stop_m': round(distance_m, 1), + 'eta_seconds': round(eta_seconds, 1), + 'eta_minutes': round(eta_minutes, 2), + 'eta_formatted': eta_formatted, + 'eta_timestamp': eta_ts.isoformat(), + }) + + except Exception as e: + predictions.append({ + 'stop_id': stop['stop_id'], + 'stop_sequence': stop_sequence_value, + 'distance_to_stop_m': round(distance_m, 1), + 'eta_seconds': None, + 'eta_minutes': None, + 'eta_formatted': None, + 'eta_timestamp': None, + 'error': str(e), + }) + + return { + 'vehicle_id': vehicle_position['vehicle_id'], + 'route_id': route_id, + 'trip_id': trip_id, + 'computed_at': datetime.now(timezone.utc).isoformat(), + 'model_key': model_key, + 'model_type': actual_model_type, + 'model_scope': model_scope, + 'predictions': predictions + } diff --git a/eta_prediction/eta_service/test_estimator.py b/eta_prediction/eta_service/test_estimator.py new file mode 100644 index 0000000..b36b656 --- /dev/null +++ b/eta_prediction/eta_service/test_estimator.py @@ -0,0 +1,560 @@ +""" +Enhanced test script for estimate_stop_times() with route-specific model support +Run from project root: python eta_service/test_estimator.py +""" + +import sys +from pathlib import Path +from datetime import datetime, timezone +import json + +# Setup paths +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from eta_service.estimator import estimate_stop_times +from models.common.registry import get_registry + + +def get_sample_vp_data(route='1'): + """Mock vehicle position in MQTT/Redis format.""" + return { + 'vehicle_id': f'vehicle_{route}_42', + 'route': route, + 'lat': 9.9281, + 'lon': -84.0907, + 'speed': 10.5, # m/s + 'heading': 90, + 'timestamp': datetime.now(timezone.utc).isoformat() + } + + +def get_sample_stops(): + """Mock upcoming stops.""" + return [ + {'stop_id': 'stop_001', 'stop_sequence': 5, 'total_stop_sequence': 20, 'lat': 9.9281, 'lon': -84.0907}, + {'stop_id': 'stop_002', 'stop_sequence': 6, 'total_stop_sequence': 20, 'lat': 9.9306, 'lon': -84.0882}, + {'stop_id': 'stop_003', 'stop_sequence': 7, 'total_stop_sequence': 20, 'lat': 9.9331, 'lon': -84.0857}, + {'stop_id': 'stop_004', 'stop_sequence': 8, 'total_stop_sequence': 20, 'lat': 9.9356, 'lon': -84.0832}, + ] + + +def test_basic_prediction(): + """Test with mock data using best model.""" + + print("=" * 70) + print("TEST 1: Basic Prediction with Auto-Selected Model") + print("=" * 70) + + vp_data = get_sample_vp_data() + stops = get_sample_stops() + + print(f"\n[VEHICLE POSITION]") + print(f" Vehicle: {vp_data['vehicle_id']}") + print(f" Route: {vp_data['route']}") + print(f" Location: ({vp_data['lat']}, {vp_data['lon']})") + print(f" Speed: {vp_data['speed']} m/s ({vp_data['speed'] * 3.6:.1f} km/h)") + + # Get predictions + result = estimate_stop_times( + vehicle_position=vp_data, + upcoming_stops=stops, + route_id='1', + trip_id='trip_001', + prefer_route_model=True, + max_stops=3 + ) + + # Display results + print(f"\n[PREDICTIONS]") + print(f" Model Type: {result.get('model_type', 'N/A')}") + print(f" Model Scope: {result.get('model_scope', 'N/A')}") + print(f" Model Key: {result['model_key']}") + print(f" Computed at: {result['computed_at']}") + + if result.get('error'): + print(f"\n ERROR: {result['error']}") + return False + + print(f"\n Upcoming Stops ({len(result['predictions'])}):") + print(" " + "-" * 66) + + for i, pred in enumerate(result['predictions'], 1): + if pred.get('error'): + print(f"\n {i}. Stop {pred['stop_id']}: ERROR - {pred['error']}") + else: + print(f"\n {i}. Stop {pred['stop_id']} (sequence {pred['stop_sequence']})") + print(f" Distance: {pred['distance_to_stop_m']:,.1f} m") + print(f" ETA: {pred['eta_formatted']} ({pred['eta_seconds']:.0f}s)") + print(f" Arrival: {pred['eta_timestamp']}") + + print("\n" + "=" * 70) + return True + + +def test_route_specific_models(): + """Test route-specific vs global model selection.""" + + print("\n\nTEST 2: Route-Specific vs Global Model Selection") + print("=" * 70) + + registry = get_registry() + + # Get available routes with trained models + routes = registry.get_routes() + print(f"\nFound {len(routes)} routes with trained models: {routes}") + + # Also check for global models + global_models = [k for k, info in registry.registry.items() + if info.get('route_id') is None] + print(f"Found {len(global_models)} global models") + + vp_data = get_sample_vp_data() + stops = get_sample_stops()[:2] # Just 2 stops for speed + + test_routes = routes[:3] if len(routes) >= 3 else routes # Test up to 3 routes + + print(f"\n{'-' * 70}") + print("Testing route-specific model preference:") + print(f"{'-' * 70}") + + for route_id in test_routes: + print(f"\nRoute {route_id}:") + + # Test 1: With prefer_route_model=True + result_route = estimate_stop_times( + vehicle_position=get_sample_vp_data(route_id), + upcoming_stops=stops, + route_id=route_id, + prefer_route_model=True, + max_stops=2 + ) + + # Test 2: With prefer_route_model=False (force global) + result_global = estimate_stop_times( + vehicle_position=get_sample_vp_data(route_id), + upcoming_stops=stops, + route_id=route_id, + prefer_route_model=False, + max_stops=2 + ) + + print(f" Route-specific: {result_route.get('model_scope', 'N/A')}") + if result_route.get('predictions') and not result_route['predictions'][0].get('error'): + print(f" ETA: {result_route['predictions'][0]['eta_formatted']}") + + print(f" Global forced: {result_global.get('model_scope', 'N/A')}") + if result_global.get('predictions') and not result_global['predictions'][0].get('error'): + print(f" ETA: {result_global['predictions'][0]['eta_formatted']}") + + # Compare if both succeeded + if (result_route.get('predictions') and result_global.get('predictions') and + not result_route['predictions'][0].get('error') and + not result_global['predictions'][0].get('error')): + + eta_route = result_route['predictions'][0]['eta_seconds'] + eta_global = result_global['predictions'][0]['eta_seconds'] + diff = eta_route - eta_global + diff_pct = (diff / eta_global * 100) if eta_global > 0 else 0 + + print(f" Difference: {diff:+.1f}s ({diff_pct:+.1f}%)") + + print("\n" + "=" * 70) + return True + + +def test_all_model_types(): + """Test with each model type explicitly.""" + + print("\n\nTEST 3: Testing All Model Types") + print("=" * 70) + + registry = get_registry() + df = registry.list_models() + + if df.empty: + print("No models found in registry!") + return False + + # Group by model type + model_types = df['model_type'].unique() + + print(f"\nFound {len(model_types)} model types:") + for mtype in model_types: + count = len(df[df['model_type'] == mtype]) + route_count = len(df[(df['model_type'] == mtype) & (df['route_id'] != 'global')]) + global_count = len(df[(df['model_type'] == mtype) & (df['route_id'] == 'global')]) + print(f" - {mtype}: {count} models ({route_count} route-specific, {global_count} global)") + + vp_data = get_sample_vp_data() + stops = get_sample_stops()[:2] # Just 2 stops for speed + + results_by_type = {} + + print(f"\n{'-' * 70}") + print("Testing each model type:") + print(f"{'-' * 70}") + + for model_type in model_types: + print(f"\n{model_type.upper()}:") + + # Test with auto-selection for this type + result = estimate_stop_times( + vehicle_position=vp_data, + upcoming_stops=stops, + route_id='1', + trip_id='trip_001', + model_type=model_type, + prefer_route_model=True, + max_stops=2 + ) + + if result.get('error'): + print(f" βœ— Failed: {result['error']}") + else: + first_pred = result['predictions'][0] if result['predictions'] else None + if first_pred and not first_pred.get('error'): + print(f" βœ“ Success!") + print(f" Scope: {result.get('model_scope', 'unknown')}") + print(f" Model: {result['model_key'][:60]}...") + print(f" First stop ETA: {first_pred['eta_formatted']}") + results_by_type[model_type] = first_pred['eta_seconds'] + else: + print(f" βœ— Prediction failed") + + # Summary comparison + if results_by_type: + print(f"\n{'-' * 70}") + print("COMPARISON (First Stop ETA):") + print(f"{'-' * 70}") + for mtype, eta_sec in sorted(results_by_type.items(), key=lambda x: x[1]): + print(f" {mtype:20s}: {eta_sec:6.0f}s ({eta_sec/60:5.1f} min)") + + print("\n" + "=" * 70) + return True + + +def test_xgboost_models(): + """Dedicated test to validate XGBoost ETA estimations.""" + + print("\n\nTEST 4: XGBoost Models (Route-Specific & Global)") + print("=" * 70) + + registry = get_registry() + + # List all xgboost models + df = registry.list_models(model_type='xgboost') + if df.empty: + print("\nNo xgboost models found in registry. Skipping test.") + print("=" * 70) + return True + + print(f"\nFound {len(df)} xgboost models in registry:") + routes = registry.get_routes(model_type='xgboost') + print(f" Routes with xgboost models: {routes}") + + # Separate route-specific and global models + route_df = df[df['route_id'] != 'global'] + global_df = df[df['route_id'] == 'global'] + + vp_data = get_sample_vp_data() + stops = get_sample_stops()[:2] + + # Test route-specific xgboost if available + if not route_df.empty: + print(f"\n--- Route-specific XGBoost ---") + test_routes = routes[:3] if len(routes) >= 3 else routes + for route_id in test_routes: + print(f"\nRoute {route_id}:") + result = estimate_stop_times( + vehicle_position=get_sample_vp_data(route_id), + upcoming_stops=stops, + route_id=route_id, + trip_id=f"trip_xgb_{route_id}", + model_type='xgboost', + prefer_route_model=True, + max_stops=2, + ) + if result.get('error'): + print(f" βœ— Error: {result['error']}") + else: + print(f" βœ“ Scope: {result.get('model_scope', 'unknown')}") + print(f" Model: {result.get('model_key', '')[:60]}...") + if result['predictions'] and not result['predictions'][0].get('error'): + first = result['predictions'][0] + print(f" First stop ETA: {first['eta_formatted']} ({first['eta_seconds']:.0f}s)") + else: + print(" βœ— Prediction failed for first stop.") + else: + print("\nNo route-specific xgboost models found.") + + # Test global xgboost if available + if not global_df.empty: + print(f"\n--- Global XGBoost ---") + result = estimate_stop_times( + vehicle_position=vp_data, + upcoming_stops=stops, + route_id='1', + trip_id='trip_xgb_global', + model_type='xgboost', + prefer_route_model=False, # force global + max_stops=2, + ) + if result.get('error'): + print(f" βœ— Error: {result['error']}") + else: + print(f" βœ“ Scope: {result.get('model_scope', 'unknown')}") + print(f" Model: {result.get('model_key', '')[:60]}...") + if result['predictions'] and not result['predictions'][0].get('error'): + first = result['predictions'][0] + print(f" First stop ETA: {first['eta_formatted']} ({first['eta_seconds']:.0f}s)") + else: + print(" βœ— Prediction failed for first stop.") + else: + print("\nNo global xgboost models found.") + + print("\n" + "=" * 70) + return True + + +def test_route_performance_comparison(): + """Compare predictions across different routes with varying training data.""" + + print("\n\nTEST 5: Route Performance Comparison") + print("=" * 70) + + registry = get_registry() + + # Get routes with their metadata + routes = registry.get_routes(model_type='polyreg_time') + + print("ROUTES: ", routes) + + if not routes: + print("No route-specific polyreg_time models found.") + return True + + print(f"\nComparing polyreg_time models across {len(routes)} routes:\n") + + stops = get_sample_stops()[:1] # Single stop for comparison + results = [] + + for route_id in routes: + # Get best model for this route + model_key = registry.get_best_model( + model_type='polyreg_time', + route_id=route_id, + metric='test_mae_seconds' + ) + + if not model_key: + continue + + # Get metadata + try: + metadata = registry.load_metadata(model_key) + n_trips = metadata.get('n_trips', 0) + test_mae_min = metadata.get('metrics', {}).get('test_mae_minutes', None) + except: + continue + + # Make prediction + result = estimate_stop_times( + vehicle_position=get_sample_vp_data(route_id), + upcoming_stops=stops, + route_id=route_id, + model_key=model_key, + max_stops=1 + ) + + if result.get('predictions') and not result['predictions'][0].get('error'): + eta = result['predictions'][0]['eta_seconds'] + results.append({ + 'route_id': route_id, + 'n_trips': n_trips, + 'test_mae_min': test_mae_min, + 'prediction_eta': eta + }) + + if results: + # Sort by number of trips + results.sort(key=lambda x: x['n_trips'], reverse=True) + + print(f"{'Route':8s} {'Trips':>6s} {'Test MAE':>10s} {'Prediction':>12s}") + print("-" * 45) + + for r in results: + mae_str = f"{r['test_mae_min']:.2f} min" if r['test_mae_min'] else "N/A" + eta_str = f"{r['prediction_eta']:.0f}s ({r['prediction_eta']/60:.1f}m)" + print(f"{r['route_id']:8s} {r['n_trips']:6d} {mae_str:>10s} {eta_str:>12s}") + + print(f"\n{'='*70}") + print("Insight: Routes with more training trips should have lower MAE") + print("and potentially more accurate predictions for similar conditions.") + print(f"{'='*70}") + + print("\n" + "=" * 70) + return True + + +def test_different_distances(): + """Test predictions at various distances.""" + + print("\n\nTEST 6: Predictions at Different Distances") + print("=" * 70) + + vp_data = get_sample_vp_data() + + # Create stops at various distances + test_distances = [100, 500, 1000, 2000, 5000] # meters + + print("\nTesting stop distances:") + + registry = get_registry() + # Try to find a polyreg_distance model for this test + df = registry.list_models(route_id='Green-E', model_type='xgboost') + # df = registry.list_models(model_type='polyreg_distance') + + if df.empty: + print(" No polyreg_distance models found. Using best model.") + model_key = None + else: + model_key = df.iloc[0]['model_key'] + print(f" Using model: {model_key[:60]}...") + + model_key = None + for dist_m in test_distances: + # Calculate approximate lat/lon offset + lat_offset = dist_m / 111320.0 # Rough: 1 degree lat β‰ˆ 111.32 km + + stops = [{ + 'stop_id': f'stop_at_{dist_m}m', + 'stop_sequence': 1, + 'lat': vp_data['lat'] + lat_offset, + 'lon': vp_data['lon'] + }] + + result = estimate_stop_times( + vehicle_position=vp_data, + upcoming_stops=stops, + route_id='1', + model_key=model_key, + max_stops=1 + ) + + if result.get('error') or not result['predictions']: + print(f" {dist_m:5d}m: ERROR") + else: + pred = result['predictions'][0] + if pred.get('error'): + print(f" {dist_m:5d}m: {pred['error']}") + else: + print(f" {dist_m:5d}m β†’ {pred['eta_formatted']:8s} ({pred['eta_seconds']:6.0f}s)") + + print("\n" + "=" * 70) + return True + + +def test_edge_cases(): + """Test error handling.""" + + print("\n\nTEST 7: Edge Cases & Error Handling") + print("=" * 70) + + # Test 1: No stops + print("\n7a. Empty stops list:") + result = estimate_stop_times( + vehicle_position=get_sample_vp_data(), + upcoming_stops=[], + route_id='1' + ) + print(f" Expected error: {result.get('error', 'N/A')}") + print(f" βœ“ Handled gracefully" if result.get('error') else " βœ— Should have errored") + + # Test 2: Invalid model key + print("\n7b. Invalid model key:") + result = estimate_stop_times( + vehicle_position=get_sample_vp_data(), + upcoming_stops=get_sample_stops()[:1], + route_id='1', + model_key='nonexistent_model_12345' + ) + print(f" Expected error: {result.get('error', 'N/A')}") + print(f" βœ“ Handled gracefully" if result.get('error') else " βœ— Should have errored") + + # Test 3: Missing optional fields + print("\n7c. Minimal vehicle position:") + minimal_vp = { + 'vehicle_id': 'minimal_bus', + 'lat': 9.9281, + 'lon': -84.0907, + 'timestamp': datetime.now(timezone.utc).isoformat() + # Missing: speed, heading, route + } + result = estimate_stop_times( + vehicle_position=minimal_vp, + upcoming_stops=get_sample_stops()[:1], + route_id='1' + ) + if result.get('error'): + print(f" Error: {result['error']}") + else: + print(f" βœ“ Handled missing fields, got ETA: {result['predictions'][0].get('eta_formatted', 'N/A')}") + + # Test 4: Route with no trained model + print("\n7d. Route without trained model:") + result = estimate_stop_times( + vehicle_position=get_sample_vp_data('999'), + upcoming_stops=get_sample_stops()[:1], + route_id='999', + prefer_route_model=True, + max_stops=1 + ) + if result.get('error'): + print(f" Error: {result['error']}") + else: + print(f" βœ“ Fell back to: {result.get('model_scope', 'N/A')} model") + if result['predictions'] and not result['predictions'][0].get('error'): + print(f" ETA: {result['predictions'][0]['eta_formatted']}") + + print("\n" + "=" * 70) + return True + + +if __name__ == '__main__': + print("\n" + "=" * 70) + print("ETA ESTIMATOR TEST SUITE - Route-Specific Edition") + print("=" * 70 + "\n") + + tests = [ + ("Basic Prediction", test_basic_prediction), + ("Route-Specific Models", test_route_specific_models), + ("All Model Types", test_all_model_types), + ("XGBoost Models", test_xgboost_models), # NEW + ("Route Performance Comparison", test_route_performance_comparison), + ("Different Distances", test_different_distances), + ("Edge Cases", test_edge_cases), + ] + + passed = 0 + failed = 0 + + for test_name, test_func in tests: + try: + if test_func(): + passed += 1 + else: + failed += 1 + except Exception as e: + print(f"\nβœ— {test_name} CRASHED: {str(e)}") + import traceback + traceback.print_exc() + failed += 1 + + print("\n" + "=" * 70) + print(f"FINAL RESULTS: {passed}/{len(tests)} tests passed") + if failed == 0: + print("πŸŽ‰ All tests passed!") + else: + print(f"⚠️ {failed} test(s) failed or crashed") + print("=" * 70 + "\n") From 1223dec551f9a497e5b00761e161fa28040b9567 Mon Sep 17 00:00:00 2001 From: Jae Date: Fri, 28 Nov 2025 20:07:32 -0600 Subject: [PATCH 5/5] feat(bytewax): PR5 - Redis2Redis Bytewax Prediction Flow --- eta_prediction/bytewax/README.md | 60 ++ .../bytewax/mock_stops_and_shapes.py | 340 +++++++++ eta_prediction/bytewax/mqtt2redis.py | 373 ++++++++++ eta_prediction/bytewax/pred2redis.py | 657 +++++++++++++++++ eta_prediction/bytewax/pyproject.toml | 30 + .../bytewax/subscriber/mqtt2redis.py | 373 ++++++++++ .../bytewax/subscriber/pyproject.toml | 18 + eta_prediction/bytewax/subscriber/uv.lock | 71 ++ .../bytewax/test_redis_predictions.py | 202 ++++++ eta_prediction/bytewax/uv.lock | 665 ++++++++++++++++++ eta_prediction/eta_service/estimator.py | 172 ++++- eta_prediction/models/common/registry.py | 63 +- 12 files changed, 2993 insertions(+), 31 deletions(-) create mode 100644 eta_prediction/bytewax/README.md create mode 100644 eta_prediction/bytewax/mock_stops_and_shapes.py create mode 100644 eta_prediction/bytewax/mqtt2redis.py create mode 100644 eta_prediction/bytewax/pred2redis.py create mode 100644 eta_prediction/bytewax/pyproject.toml create mode 100644 eta_prediction/bytewax/subscriber/mqtt2redis.py create mode 100644 eta_prediction/bytewax/subscriber/pyproject.toml create mode 100644 eta_prediction/bytewax/subscriber/uv.lock create mode 100644 eta_prediction/bytewax/test_redis_predictions.py create mode 100644 eta_prediction/bytewax/uv.lock diff --git a/eta_prediction/bytewax/README.md b/eta_prediction/bytewax/README.md new file mode 100644 index 0000000..8cd9e7e --- /dev/null +++ b/eta_prediction/bytewax/README.md @@ -0,0 +1,60 @@ +# Bytewax ETA Prediction Flow + +Low-latency ETA predictions are generated by streaming vehicle telemetry from MQTT into Redis, enriching the data with cached stops/shapes, and running the Bytewax flow that calls `eta_service`. This README explains how to bring all moving pieces online locally. + +## Prerequisites +- This repository cloned locally (paths below assume the root is `eta_prediction/`). +- [`uv`](https://github.com/astral-sh/uv) or another tool capable of running the provided `pyproject.toml` environments. +- Docker (or Podman) for running the MQTT + Redis stack provided by [`simovilab/databus-mqtt`](https://github.com/simovilab/databus-mqtt). + +## End-to-End Workflow + +### 1. Start the MQTT/Redis stack +```bash +git clone https://github.com/simovilab/databus-mqtt.git +cd databus-mqtt +docker compose up +``` +> Leave this terminal running; it launches the Mosquitto broker, Redis, and any helper services defined in that repo. + +### 2. Publish sample vehicle data +In another terminal inside the `databus-mqtt` repository: +```bash +uv run python publisher/publisher_example +``` +Wait for the publisher to log that it is connectedβ€”this continuously streams mock vehicle events into the MQTT broker. + +### 3. Bridge MQTT β†’ Redis +Back in this repository: +```bash +cd bytewax/subscriber +uv run python mqtt2redis.py +``` +This subscriber listens to `transit/vehicles/bus/#`, validates each payload, and caches the latest vehicle state under `vehicle:*` keys in Redis. Keep it running. + +### 4. Seed mock stops and shapes +From `eta_prediction/bytewax/`: +```bash +uv run python mock_stops_and_shapes.py +``` +This loads placeholder stop sequences (`route_stops:*`) and encoded GTFS shapes (`route_shape:*`) into Redis. The Bytewax flow expects this cache to exist; in production it will be populated by another service. + +### 5. Run the Bytewax prediction flow +Still inside `bytewax/`: +```bash +uv run python -m bytewax.run pred2redis +``` +The flow polls Redis for new vehicle entries, enriches them with the cached stops/shapes, calls `estimate_stop_times`, and stores the resulting ETAs under `predictions:`. + +### 6. Monitor predictions +Optionally observe the cached ETAs from another terminal: +```bash +uv run python test_redis_predictions.py --continuous +# or monitor a single vehicle: +uv run python test_redis_predictions.py --vehicle BUS-004 --continuous +``` +The monitor subscribes to Redis and pretty-prints each `predictions:*` payload so you can confirm the pipeline is healthy. + +## Notes +- All scripts default to `localhost` for MQTT and Redis; adjust the constants in `mqtt2redis.py` or provide environment overrides if your services run elsewhere. +- If you restart Redis you will need to re-run `mock_stops_and_shapes.py` to repopulate the cache before restarting the Bytewax flow. diff --git a/eta_prediction/bytewax/mock_stops_and_shapes.py b/eta_prediction/bytewax/mock_stops_and_shapes.py new file mode 100644 index 0000000..c17e1e8 --- /dev/null +++ b/eta_prediction/bytewax/mock_stops_and_shapes.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +""" +Mock route stops and GTFS shapes data for testing ETA prediction flow. +This provides hardcoded stop sequences and shape polylines for routes when Redis/PostgreSQL cache is not available. +Replace with actual GTFS data in production. + +Stops are positioned around NYC area (40.7128, -74.0060) with ~150-250m spacing. +Shapes follow realistic paths with intermediate points between stops. +""" + +# Mock stop data for testing +# Format: route_id -> list of stops with stop_id, stop_sequence, lat, lon +# Stops are clustered around NYC coordinates with realistic 150-250m spacing +MOCK_ROUTE_STOPS = { + "Route-1": [ + {"stop_id": "stop_1_001", "stop_sequence": 1, "lat": 40.7128, "lon": -74.0060}, + {"stop_id": "stop_1_002", "stop_sequence": 2, "lat": 40.7143, "lon": -74.0058}, + {"stop_id": "stop_1_003", "stop_sequence": 3, "lat": 40.7158, "lon": -74.0056}, + {"stop_id": "stop_1_004", "stop_sequence": 4, "lat": 40.7173, "lon": -74.0054}, + {"stop_id": "stop_1_005", "stop_sequence": 5, "lat": 40.7188, "lon": -74.0052}, + {"stop_id": "stop_1_006", "stop_sequence": 6, "lat": 40.7203, "lon": -74.0050}, + {"stop_id": "stop_1_007", "stop_sequence": 7, "lat": 40.7218, "lon": -74.0048}, + {"stop_id": "stop_1_008", "stop_sequence": 8, "lat": 40.7233, "lon": -74.0046}, + ], + "Route-2": [ + {"stop_id": "stop_2_001", "stop_sequence": 1, "lat": 40.7100, "lon": -74.0065}, + {"stop_id": "stop_2_002", "stop_sequence": 2, "lat": 40.7115, "lon": -74.0063}, + {"stop_id": "stop_2_003", "stop_sequence": 3, "lat": 40.7130, "lon": -74.0061}, + {"stop_id": "stop_2_004", "stop_sequence": 4, "lat": 40.7145, "lon": -74.0059}, + {"stop_id": "stop_2_005", "stop_sequence": 5, "lat": 40.7160, "lon": -74.0057}, + {"stop_id": "stop_2_006", "stop_sequence": 6, "lat": 40.7175, "lon": -74.0055}, + {"stop_id": "stop_2_007", "stop_sequence": 7, "lat": 40.7190, "lon": -74.0053}, + ], + "Route-3": [ + {"stop_id": "stop_3_001", "stop_sequence": 1, "lat": 40.7080, "lon": -74.0075}, + {"stop_id": "stop_3_002", "stop_sequence": 2, "lat": 40.7098, "lon": -74.0071}, + {"stop_id": "stop_3_003", "stop_sequence": 3, "lat": 40.7116, "lon": -74.0067}, + {"stop_id": "stop_3_004", "stop_sequence": 4, "lat": 40.7134, "lon": -74.0063}, + {"stop_id": "stop_3_005", "stop_sequence": 5, "lat": 40.7152, "lon": -74.0059}, + ], + "Route-4": [ + {"stop_id": "stop_4_001", "stop_sequence": 1, "lat": 40.7150, "lon": -74.0070}, + {"stop_id": "stop_4_002", "stop_sequence": 2, "lat": 40.7165, "lon": -74.0067}, + {"stop_id": "stop_4_003", "stop_sequence": 3, "lat": 40.7180, "lon": -74.0064}, + {"stop_id": "stop_4_004", "stop_sequence": 4, "lat": 40.7195, "lon": -74.0061}, + {"stop_id": "stop_4_005", "stop_sequence": 5, "lat": 40.7210, "lon": -74.0058}, + {"stop_id": "stop_4_006", "stop_sequence": 6, "lat": 40.7225, "lon": -74.0055}, + ], + "Route-5": [ + {"stop_id": "stop_5_001", "stop_sequence": 1, "lat": 40.7050, "lon": -74.0040}, + {"stop_id": "stop_5_002", "stop_sequence": 2, "lat": 40.7067, "lon": -74.0044}, + {"stop_id": "stop_5_003", "stop_sequence": 3, "lat": 40.7084, "lon": -74.0048}, + {"stop_id": "stop_5_004", "stop_sequence": 4, "lat": 40.7101, "lon": -74.0052}, + {"stop_id": "stop_5_005", "stop_sequence": 5, "lat": 40.7118, "lon": -74.0056}, + {"stop_id": "stop_5_006", "stop_sequence": 6, "lat": 40.7135, "lon": -74.0060}, + {"stop_id": "stop_5_007", "stop_sequence": 7, "lat": 40.7152, "lon": -74.0064}, + {"stop_id": "stop_5_008", "stop_sequence": 8, "lat": 40.7169, "lon": -74.0068}, + {"stop_id": "stop_5_009", "stop_sequence": 9, "lat": 40.7186, "lon": -74.0072}, + ], + "Route-6": [ + {"stop_id": "stop_6_001", "stop_sequence": 1, "lat": 40.7128, "lon": -73.9980}, + {"stop_id": "stop_6_002", "stop_sequence": 2, "lat": 40.7143, "lon": -73.9985}, + {"stop_id": "stop_6_003", "stop_sequence": 3, "lat": 40.7158, "lon": -73.9990}, + {"stop_id": "stop_6_004", "stop_sequence": 4, "lat": 40.7173, "lon": -73.9995}, + {"stop_id": "stop_6_005", "stop_sequence": 5, "lat": 40.7188, "lon": -74.0000}, + {"stop_id": "stop_6_006", "stop_sequence": 6, "lat": 40.7203, "lon": -74.0005}, + ], + "Route-7": [ + {"stop_id": "stop_7_001", "stop_sequence": 1, "lat": 40.7070, "lon": -74.0095}, + {"stop_id": "stop_7_002", "stop_sequence": 2, "lat": 40.7087, "lon": -74.0090}, + {"stop_id": "stop_7_003", "stop_sequence": 3, "lat": 40.7104, "lon": -74.0085}, + {"stop_id": "stop_7_004", "stop_sequence": 4, "lat": 40.7121, "lon": -74.0080}, + {"stop_id": "stop_7_005", "stop_sequence": 5, "lat": 40.7138, "lon": -74.0075}, + {"stop_id": "stop_7_006", "stop_sequence": 6, "lat": 40.7155, "lon": -74.0070}, + {"stop_id": "stop_7_007", "stop_sequence": 7, "lat": 40.7172, "lon": -74.0065}, + ], + "Route-8": [ + {"stop_id": "stop_8_001", "stop_sequence": 1, "lat": 40.7180, "lon": -74.0100}, + {"stop_id": "stop_8_002", "stop_sequence": 2, "lat": 40.7195, "lon": -74.0095}, + {"stop_id": "stop_8_003", "stop_sequence": 3, "lat": 40.7210, "lon": -74.0090}, + {"stop_id": "stop_8_004", "stop_sequence": 4, "lat": 40.7225, "lon": -74.0085}, + {"stop_id": "stop_8_005", "stop_sequence": 5, "lat": 40.7240, "lon": -74.0080}, + ], + "Route-9": [ + {"stop_id": "stop_9_001", "stop_sequence": 1, "lat": 40.7040, "lon": -74.0020}, + {"stop_id": "stop_9_002", "stop_sequence": 2, "lat": 40.7057, "lon": -74.0026}, + {"stop_id": "stop_9_003", "stop_sequence": 3, "lat": 40.7074, "lon": -74.0032}, + {"stop_id": "stop_9_004", "stop_sequence": 4, "lat": 40.7091, "lon": -74.0038}, + {"stop_id": "stop_9_005", "stop_sequence": 5, "lat": 40.7108, "lon": -74.0044}, + {"stop_id": "stop_9_006", "stop_sequence": 6, "lat": 40.7125, "lon": -74.0050}, + {"stop_id": "stop_9_007", "stop_sequence": 7, "lat": 40.7142, "lon": -74.0056}, + {"stop_id": "stop_9_008", "stop_sequence": 8, "lat": 40.7159, "lon": -74.0062}, + ], + "Route-10": [ + {"stop_id": "stop_10_001", "stop_sequence": 1, "lat": 40.7160, "lon": -73.9960}, + {"stop_id": "stop_10_002", "stop_sequence": 2, "lat": 40.7175, "lon": -73.9966}, + {"stop_id": "stop_10_003", "stop_sequence": 3, "lat": 40.7190, "lon": -73.9972}, + {"stop_id": "stop_10_004", "stop_sequence": 4, "lat": 40.7205, "lon": -73.9978}, + {"stop_id": "stop_10_005", "stop_sequence": 5, "lat": 40.7220, "lon": -73.9984}, + {"stop_id": "stop_10_006", "stop_sequence": 6, "lat": 40.7235, "lon": -73.9990}, + ], +} + + +def _generate_shape_points(stops: list, points_per_segment: int = 3) -> list: + """ + Generate realistic shape points between stops with slight curves. + + Args: + stops: List of stop dictionaries with lat/lon + points_per_segment: Number of intermediate points between each stop pair + + Returns: + List of shape point dicts with shape_pt_lat, shape_pt_lon, shape_pt_sequence + """ + shape_points = [] + sequence = 1 + + for i in range(len(stops)): + # Add the stop location as a shape point + shape_points.append({ + "shape_pt_lat": stops[i]["lat"], + "shape_pt_lon": stops[i]["lon"], + "shape_pt_sequence": sequence, + "shape_dist_traveled": None # Will be calculated by ShapePolyline + }) + sequence += 1 + + # Add intermediate points between this stop and the next + if i < len(stops) - 1: + lat1, lon1 = stops[i]["lat"], stops[i]["lon"] + lat2, lon2 = stops[i + 1]["lat"], stops[i + 1]["lon"] + + for j in range(1, points_per_segment + 1): + # Linear interpolation with slight curve + t = j / (points_per_segment + 1) + + # Add small perpendicular offset for realistic road curves + # Alternating left/right curve + curve_factor = 0.00002 * (1 if i % 2 == 0 else -1) + perp_offset = curve_factor * (1 - 2 * abs(t - 0.5)) + + lat = lat1 + (lat2 - lat1) * t + lon = lon1 + (lon2 - lon1) * t + perp_offset + + shape_points.append({ + "shape_pt_lat": lat, + "shape_pt_lon": lon, + "shape_pt_sequence": sequence, + "shape_dist_traveled": None + }) + sequence += 1 + + return shape_points + + +# Generate mock shapes for each route +MOCK_ROUTE_SHAPES = {} +for route_id, stops in MOCK_ROUTE_STOPS.items(): + shape_id = f"shape_{route_id.lower().replace('-', '_')}" + MOCK_ROUTE_SHAPES[route_id] = { + "shape_id": shape_id, + "points": _generate_shape_points(stops, points_per_segment=3) + } + + +def get_route_stops(route_id: str) -> list: + """ + Get stops for a given route. + + Args: + route_id: Route identifier + + Returns: + List of stop dictionaries, or empty list if route not found + """ + return MOCK_ROUTE_STOPS.get(route_id, []) + + +def get_route_shape(route_id: str) -> dict: + """ + Get shape data for a given route. + + Args: + route_id: Route identifier + + Returns: + Dict with shape_id and points list, or None if route not found + """ + return MOCK_ROUTE_SHAPES.get(route_id) + + +def load_all_stops_to_redis(redis_client, key_prefix: str = "route_stops:"): + """ + Load all mock route stops into Redis for testing. + + Args: + redis_client: Redis client instance + key_prefix: Prefix for Redis keys (default: "route_stops:") + + Returns: + Number of routes loaded + """ + import json + + count = 0 + for route_id, stops in MOCK_ROUTE_STOPS.items(): + key = f"{key_prefix}{route_id}" + value = json.dumps(stops) + redis_client.set(key, value) + count += 1 + print(f"βœ“ Loaded {len(stops)} stops for {route_id} -> {key}") + + print(f"\nβœ“ Total: Loaded {count} routes to Redis") + return count + + +def load_all_shapes_to_redis(redis_client, key_prefix: str = "route_shape:"): + """ + Load all mock route shapes into Redis for testing. + + Args: + redis_client: Redis client instance + key_prefix: Prefix for Redis keys (default: "route_shape:") + + Returns: + Number of shapes loaded + """ + import json + + count = 0 + for route_id, shape_data in MOCK_ROUTE_SHAPES.items(): + key = f"{key_prefix}{route_id}" + value = json.dumps(shape_data) + redis_client.set(key, value) + count += 1 + print(f"βœ“ Loaded {len(shape_data['points'])} shape points for {route_id} -> {key}") + + print(f"\nβœ“ Total: Loaded {count} shapes to Redis") + return count + + +def load_all_to_redis(redis_client, + stops_key_prefix: str = "route_stops:", + shapes_key_prefix: str = "route_shape:"): + """ + Load both stops and shapes to Redis. + + Args: + redis_client: Redis client instance + stops_key_prefix: Prefix for stop keys + shapes_key_prefix: Prefix for shape keys + + Returns: + Tuple of (stops_count, shapes_count) + """ + print("Loading stops...") + stops_count = load_all_stops_to_redis(redis_client, stops_key_prefix) + + print("\nLoading shapes...") + shapes_count = load_all_shapes_to_redis(redis_client, shapes_key_prefix) + + return stops_count, shapes_count + + +def create_mock_shape_polyline(route_id: str): + """ + Create a ShapePolyline object from mock data for testing. + + Args: + route_id: Route identifier + + Returns: + ShapePolyline object or None if route not found or spatial module unavailable + """ + try: + from feature_engineering.spatial import ShapePolyline + except ImportError: + print("⚠️ ShapePolyline not available, install feature_engineering module") + return None + + shape_data = get_route_shape(route_id) + if not shape_data: + return None + + # Convert to format expected by ShapePolyline + points = [ + (pt["shape_pt_lat"], pt["shape_pt_lon"]) + for pt in shape_data["points"] + ] + + return ShapePolyline(points) + + +if __name__ == "__main__": + """ + Standalone script to populate Redis with mock route stops and shapes. + Usage: python mock_route_stops.py + """ + import redis + + print("="*70) + print("LOADING MOCK ROUTE DATA TO REDIS") + print("NYC AREA - 150-250m stop spacing with realistic shape polylines") + print("="*70 + "\n") + + try: + client = redis.Redis( + host="localhost", + port=6379, + db=0, + decode_responses=True + ) + client.ping() + print("βœ“ Connected to Redis at localhost:6379\n") + + stops_count, shapes_count = load_all_to_redis(client) + + print("\n" + "="*70) + print("SUMMARY") + print("="*70) + print(f"βœ“ Loaded {stops_count} route stop sequences") + print(f"βœ“ Loaded {shapes_count} route shape polylines") + print(f"βœ“ Total shape points: {sum(len(s['points']) for s in MOCK_ROUTE_SHAPES.values())}") + print("\nYour Bytewax flow can now fetch:") + print(" β€’ Stops from Redis: route_stops:{route_id}") + print(" β€’ Shapes from Redis: route_shape:{route_id}") + print("\nShape data enables:") + print(" β€’ Accurate distance along route") + print(" β€’ Cross-track error detection") + print(" β€’ Progress ratio calculation") + print("="*70) + + except redis.ConnectionError as e: + print(f"βœ— Failed to connect to Redis: {e}") + print("Make sure Redis is running: redis-server") + except Exception as e: + print(f"βœ— Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/eta_prediction/bytewax/mqtt2redis.py b/eta_prediction/bytewax/mqtt2redis.py new file mode 100644 index 0000000..388cc4f --- /dev/null +++ b/eta_prediction/bytewax/mqtt2redis.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +MQTT to Redis Subscriber Bridge for ETA Prediction Pipeline +Subscribes to MQTT vehicle topics and stores enriched data in Redis. + +Usage: + pip install paho-mqtt redis + python mqtt_to_redis_subscriber.py +""" + +import json +import time +import sys +from datetime import datetime, timezone +import paho.mqtt.client as mqtt +import redis + +# ============================================================================ +# Configuration +# ============================================================================ + +# MQTT Configuration +MQTT_HOST = "localhost" +MQTT_PORT = 1883 +MQTT_USER = "admin" +MQTT_PASS = "admin" +MQTT_TOPIC = "transit/vehicles/bus/#" # Subscribe to all bus topics + +# Redis Configuration +REDIS_HOST = "localhost" +REDIS_PORT = 6379 +REDIS_DB = 0 +REDIS_PASSWORD = None +REDIS_KEY_PREFIX = "vehicle:" # Keys will be like "vehicle:BUS-001" +REDIS_TTL = 300 # Time to live in seconds (5 minutes) + +# Optional: Publish to Redis channel for real-time subscribers +REDIS_PUBSUB_ENABLED = False +REDIS_PUBSUB_CHANNEL = "vehicle_updates" + +# Data validation - ensure all required fields are present +REQUIRED_FIELDS = [ + 'vehicle_id', + 'lat', + 'lon', + 'speed', + 'timestamp' +] + +# Optional fields that enhance predictions (but won't reject messages if missing) +RECOMMENDED_FIELDS = [ + 'route_id', + 'trip_id', + 'stop_id', + 'stop_lat', + 'stop_lon', + 'stop_sequence', + 'heading', + 'bearing' +] + +# ============================================================================ +# Redis Client +# ============================================================================ + +redis_client = None + +def connect_redis(): + """Connect to Redis""" + global redis_client + + try: + redis_client = redis.Redis( + host=REDIS_HOST, + port=REDIS_PORT, + db=REDIS_DB, + password=REDIS_PASSWORD, + decode_responses=True + ) + + # Test connection + redis_client.ping() + print(f"βœ“ Connected to Redis at {REDIS_HOST}:{REDIS_PORT}") + return True + + except redis.ConnectionError as e: + print(f"βœ— Failed to connect to Redis: {e}") + print(f" Make sure Redis is running: docker-compose up -d redis") + return False + except Exception as e: + print(f"βœ— Redis connection error: {e}") + return False + +def validate_vehicle_data(data): + """ + Validate that vehicle data contains required fields for ETA prediction. + Returns (is_valid, missing_fields, missing_recommended) + """ + missing_required = [field for field in REQUIRED_FIELDS if field not in data] + missing_recommended = [field for field in RECOMMENDED_FIELDS if field not in data] + + is_valid = len(missing_required) == 0 + + return is_valid, missing_required, missing_recommended + +def enrich_vehicle_data(data): + """ + Enrich vehicle data with additional metadata and transformations. + """ + # Ensure consistent field naming + if 'route' in data and 'route_id' not in data: + data['route_id'] = data['route'] + + # Add UTC timestamp if not present or normalize it + if 'timestamp' in data: + try: + # Parse and normalize to ISO format with timezone + ts = datetime.fromisoformat(data['timestamp'].replace('Z', '+00:00')) + data['timestamp'] = ts.isoformat() + except: + # If parsing fails, use current time + data['timestamp'] = datetime.now(timezone.utc).isoformat() + else: + data['timestamp'] = datetime.now(timezone.utc).isoformat() + + # Ensure heading and bearing are both present (some systems use one or the other) + if 'heading' in data and 'bearing' not in data: + data['bearing'] = data['heading'] + elif 'bearing' in data and 'heading' not in data: + data['heading'] = data['bearing'] + + # Add processing metadata + data['_mqtt_received_at'] = datetime.now(timezone.utc).isoformat() + data['_data_quality'] = 'complete' if all(f in data for f in RECOMMENDED_FIELDS) else 'partial' + + return data + +def store_in_redis(vehicle_id, data): + """Store vehicle data in Redis with enrichment""" + try: + # Validate data + is_valid, missing_required, missing_recommended = validate_vehicle_data(data) + + if not is_valid: + print(f"⚠️ Invalid data for {vehicle_id}: missing required fields {missing_required}") + return False + + # Warn about missing recommended fields + if missing_recommended: + print(f"ℹ️ {vehicle_id} missing recommended fields: {missing_recommended}") + + # Enrich data + enriched_data = enrich_vehicle_data(data) + + # Create Redis key + key = f"{REDIS_KEY_PREFIX}{vehicle_id}" + + # Convert data to JSON string + json_data = json.dumps(enriched_data) + + # Store with TTL (expires after REDIS_TTL seconds) + redis_client.setex(key, REDIS_TTL, json_data) + + # Optional: Publish to Redis Pub/Sub channel for real-time subscribers + if REDIS_PUBSUB_ENABLED: + redis_client.publish(REDIS_PUBSUB_CHANNEL, json_data) + + return True + + except Exception as e: + print(f"βœ— Error storing data in Redis: {e}") + import traceback + traceback.print_exc() + return False + +# ============================================================================ +# MQTT Callbacks +# ============================================================================ + +message_count = 0 +error_count = 0 +warning_count = 0 +start_time = None + +def on_connect(client, userdata, flags, rc): + """Callback when connected to MQTT broker""" + global start_time + + if rc == 0: + print(f"βœ“ Connected to MQTT broker at {MQTT_HOST}:{MQTT_PORT}") + + # Subscribe to topic + client.subscribe(MQTT_TOPIC, qos=1) + print(f"βœ“ Subscribed to topic: {MQTT_TOPIC}") + print("\n" + "="*70) + print("MQTT β†’ Redis Bridge Active (ETA Prediction Pipeline)") + print("="*70) + print("Features:") + print(" β€’ Data validation (required & recommended fields)") + print(" β€’ Timestamp normalization") + print(" β€’ Field enrichment (heading/bearing consistency)") + print(" β€’ Quality scoring") + print("="*70) + print(f"Listening for messages... (press Ctrl+C to stop)\n") + + start_time = time.time() + + else: + print(f"βœ— Failed to connect to MQTT broker, return code: {rc}") + print(" Return codes: 0=Success, 1=Protocol version, 2=Invalid client ID") + print(" 3=Server unavailable, 4=Bad credentials, 5=Not authorized") + sys.exit(1) + +def on_message(client, userdata, msg): + """Callback when a message is received""" + global message_count, error_count, warning_count + + try: + # Parse the message + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + + # Get vehicle ID + vehicle_id = data.get('vehicle_id') + + if not vehicle_id: + print(f"⚠️ Message missing vehicle_id: {msg.topic}") + error_count += 1 + return + + # Add MQTT metadata + data['_mqtt_topic'] = msg.topic + + # Store in Redis (with validation and enrichment) + if store_in_redis(vehicle_id, data): + message_count += 1 + + # Detailed output for first few messages, then summary + if message_count <= 5: + print(f"βœ“ [{message_count}] Stored {vehicle_id}") + print(f" Route: {data.get('route_id', 'N/A')}") + print(f" Position: ({data.get('lat'):.4f}, {data.get('lon'):.4f})") + print(f" Speed: {data.get('speed')} km/h") + print(f" Next Stop: {data.get('stop_id', 'N/A')}") + print(f" Quality: {data.get('_data_quality', 'unknown')}") + elif message_count % 10 == 0: + elapsed = time.time() - start_time if start_time else 0 + rate = message_count / elapsed if elapsed > 0 else 0 + print(f"πŸ“Š [{message_count}] messages | " + f"{rate:.1f} msg/sec | " + f"{error_count} errors | " + f"{warning_count} warnings") + else: + # Compact output for routine messages + quality_icon = "βœ“" if data.get('_data_quality') == 'complete' else "⚠" + print(f"{quality_icon} [{message_count}] {vehicle_id} β†’ " + f"{data.get('route_id', 'N/A'):10s} | " + f"Speed: {data.get('speed', 0):5.1f} km/h | " + f"Stop: {data.get('stop_id', 'N/A')}") + else: + error_count += 1 + + except json.JSONDecodeError as e: + print(f"βœ— Failed to decode JSON from topic {msg.topic}: {e}") + error_count += 1 + except Exception as e: + print(f"βœ— Error processing message: {e}") + import traceback + traceback.print_exc() + error_count += 1 + +def on_disconnect(client, userdata, rc): + """Callback when disconnected from MQTT broker""" + if rc != 0: + print(f"\nβœ— Unexpected disconnection from MQTT broker (code: {rc})") + print(" Attempting to reconnect...") + +# ============================================================================ +# Main Function +# ============================================================================ + +def print_config(): + """Print current configuration""" + print("="*70) + print("MQTT to Redis Subscriber - ETA Prediction Pipeline") + print("="*70) + print(f"MQTT Broker: {MQTT_HOST}:{MQTT_PORT}") + print(f"MQTT Topic: {MQTT_TOPIC}") + print(f"Redis Server: {REDIS_HOST}:{REDIS_PORT}") + print(f"Redis Key: {REDIS_KEY_PREFIX}") + print(f"Redis TTL: {REDIS_TTL} seconds") + print(f"Pub/Sub: {'Enabled' if REDIS_PUBSUB_ENABLED else 'Disabled'}") + if REDIS_PUBSUB_ENABLED: + print(f"Pub/Sub Channel: {REDIS_PUBSUB_CHANNEL}") + print() + print("Required Fields: " + ", ".join(REQUIRED_FIELDS)) + print("Recommended: " + ", ".join(RECOMMENDED_FIELDS)) + print("="*70) + print() + +def print_stats(): + """Print final statistics""" + print("\n" + "="*70) + print("FINAL STATISTICS") + print("="*70) + print(f"Total messages processed: {message_count}") + print(f"Total errors: {error_count}") + print(f"Total warnings: {warning_count}") + + if message_count > 0: + success_rate = ((message_count / (message_count + error_count)) * 100) if (message_count + error_count) > 0 else 0 + print(f"Success rate: {success_rate:.1f}%") + + if start_time: + elapsed = time.time() - start_time + rate = message_count / elapsed if elapsed > 0 else 0 + print(f"Average rate: {rate:.2f} messages/second") + print(f"Running time: {elapsed:.1f} seconds") + print("="*70) + +def main(): + """Main function""" + print_config() + + # Connect to Redis first + print("Connecting to Redis...") + if not connect_redis(): + print("\nβœ— Cannot start without Redis connection") + sys.exit(1) + + print() + + # Create MQTT client + print("Setting up MQTT client...") + mqtt_client = mqtt.Client(client_id="mqtt-redis-bridge-eta") + mqtt_client.username_pw_set(MQTT_USER, MQTT_PASS) + + # Set up callbacks + mqtt_client.on_connect = on_connect + mqtt_client.on_message = on_message + mqtt_client.on_disconnect = on_disconnect + + try: + # Connect to MQTT broker + print(f"Connecting to MQTT broker at {MQTT_HOST}:{MQTT_PORT}...") + mqtt_client.connect(MQTT_HOST, MQTT_PORT, 60) + + # Start the loop (blocking) + mqtt_client.loop_forever() + + except KeyboardInterrupt: + print_stats() + + except ConnectionRefusedError: + print(f"\nβœ— Connection refused to MQTT broker at {MQTT_HOST}:{MQTT_PORT}") + print(" Make sure RabbitMQ is running: docker-compose up -d rabbitmq") + sys.exit(1) + + except Exception as e: + print(f"\nβœ— Error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + finally: + # Cleanup + mqtt_client.disconnect() + if redis_client: + redis_client.close() + print("\nβœ“ Subscriber stopped cleanly\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/eta_prediction/bytewax/pred2redis.py b/eta_prediction/bytewax/pred2redis.py new file mode 100644 index 0000000..e045e1a --- /dev/null +++ b/eta_prediction/bytewax/pred2redis.py @@ -0,0 +1,657 @@ +#!/usr/bin/env python3 +""" +Bytewax dataflow for low-latency ETA prediction processing. + +LOW-LATENCY DESIGN: +- All data loaded from Redis cache (no database calls) +- In-memory shape caching per worker +- Shape loading happens in enrichment step before inference +- Estimator receives pre-loaded shapes for zero I/O during prediction + +This flow: +1. Polls vehicle position data from Redis (where MQTT subscriber stores it) +2. Enriches with upcoming stops from Redis cache +3. Loads GTFS shapes from Redis cache with in-memory caching +4. Processes through estimate_stop_times() with pre-loaded shapes +5. Stores predictions back to Redis under "predictions:*" keys + +Usage: + pip install bytewax redis + python -m bytewax.run bytewax_eta_flow +""" + +import json +import time +from datetime import datetime, timedelta, timezone +from typing import Optional, Dict, Any, List, Set +from dataclasses import dataclass +from pathlib import Path + +import redis +import bytewax.operators as op +from bytewax.dataflow import Dataflow +from bytewax.inputs import FixedPartitionedSource, StatefulSourcePartition +from bytewax.outputs import DynamicSink, StatelessSinkPartition + +# Import the ETA estimator with proper path resolution +import sys +import os + +# Resolve paths relative to this file +current_dir = Path(__file__).resolve().parent +project_root = current_dir.parent + +# Add project directories to Python path +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(project_root / "eta_service")) +sys.path.insert(0, str(project_root / "feature_engineering")) +sys.path.insert(0, str(project_root / "models")) + +from eta_service.estimator import estimate_stop_times + +# Import ShapePolyline for shape processing +try: + from feature_engineering.spatial import ShapePolyline + SHAPE_SUPPORT = True +except ImportError: + SHAPE_SUPPORT = False + print("⚠️ ShapePolyline not available, shape-aware features disabled") + + +# ============================================================================ +# Configuration +# ============================================================================ + +@dataclass +class RedisConfig: + """Redis connection configuration""" + host: str = "localhost" + port: int = 6379 + db: int = 0 + password: Optional[str] = None + vehicle_key_pattern: str = "vehicle:*" # Pattern for vehicle position data + route_stops_key_prefix: str = "route_stops:" # Cache key for route stop sequences + route_shape_key_prefix: str = "route_shape:" # Cache key for route shapes + predictions_key_prefix: str = "predictions:" # Output key prefix for predictions + poll_interval_ms: int = 1000 # How often to poll Redis (milliseconds) + predictions_ttl: int = 300 # TTL for prediction cache (5 minutes) + + +# ============================================================================ +# Shape Loading & Caching (Zero Database Calls) +# ============================================================================ + +class ShapeCache: + """ + In-memory LRU-style cache for loaded shapes. + Persists across batches within a worker to avoid repeated Redis queries. + """ + + def __init__(self, max_size: int = 100): + self.cache: Dict[str, Any] = {} + self.access_order: List[str] = [] + self.max_size = max_size + self.hits = 0 + self.misses = 0 + + def get(self, route_id: str) -> Optional[Any]: + """Get cached shape for route""" + if route_id in self.cache: + # Move to end (most recently used) + self.access_order.remove(route_id) + self.access_order.append(route_id) + self.hits += 1 + return self.cache[route_id] + + self.misses += 1 + return None + + def set(self, route_id: str, shape: Any): + """Cache shape for route with LRU eviction""" + if route_id in self.cache: + # Update existing + self.access_order.remove(route_id) + self.access_order.append(route_id) + self.cache[route_id] = shape + else: + # Add new + if len(self.cache) >= self.max_size: + # Evict least recently used + oldest = self.access_order.pop(0) + del self.cache[oldest] + + self.cache[route_id] = shape + self.access_order.append(route_id) + + def stats(self) -> dict: + """Get cache statistics""" + total = self.hits + self.misses + hit_rate = (self.hits / total * 100) if total > 0 else 0 + return { + 'size': len(self.cache), + 'hits': self.hits, + 'misses': self.misses, + 'hit_rate_pct': round(hit_rate, 1) + } + + +def load_shape_from_redis(redis_client, route_id: str, shape_cache: ShapeCache, key_prefix: str = "route_shape:") -> Optional[Any]: + """ + Load shape data from Redis and convert to ShapePolyline. + Uses in-memory cache to avoid repeated Redis queries. + + Args: + redis_client: Redis client instance + route_id: Route identifier + shape_cache: ShapeCache instance for this worker + key_prefix: Redis key prefix + + Returns: + ShapePolyline object or None if not available + """ + if not SHAPE_SUPPORT: + return None + + # Check in-memory cache first (fastest path) + cached = shape_cache.get(route_id) + if cached is not None: + return cached + + # Load from Redis + try: + shape_key = f"{key_prefix}{route_id}" + shape_json = redis_client.get(shape_key) + + if shape_json: + shape_data = json.loads(shape_json) + points = [ + (pt["shape_pt_lat"], pt["shape_pt_lon"]) + for pt in shape_data["points"] + ] + + shape = ShapePolyline(points) + shape_cache.set(route_id, shape) + + return shape + except Exception as e: + print(f"⚠️ Error loading shape from Redis for route {route_id}: {e}") + + return None + + +# ============================================================================ +# Redis Input Source +# ============================================================================ + +class RedisVehiclePartition(StatefulSourcePartition): + """Partition that polls vehicle position data from Redis""" + + def __init__(self, redis_config: RedisConfig): + self.redis_config = redis_config + self.redis_client = None + self.shape_cache = ShapeCache(max_size=100) # One cache per worker + self._setup_client() + + def _setup_client(self): + """Initialize Redis client""" + try: + self.redis_client = redis.Redis( + host=self.redis_config.host, + port=self.redis_config.port, + db=self.redis_config.db, + password=self.redis_config.password, + decode_responses=True + ) + self.redis_client.ping() + print(f"βœ“ Connected to Redis at {self.redis_config.host}:{self.redis_config.port}") + print(f"βœ“ Polling pattern: {self.redis_config.vehicle_key_pattern}") + print(f"βœ“ Shape cache enabled with max_size=100\n") + except redis.ConnectionError as e: + print(f"βœ— Failed to connect to Redis: {e}") + raise + + def next_batch(self): + """Get next batch of vehicle updates from Redis""" + batch = [] + + try: + keys = self.redis_client.keys(self.redis_config.vehicle_key_pattern) + + for key in keys: + try: + value = self.redis_client.get(key) + + if value: + data = json.loads(value) + data['_redis_key'] = key + data['_fetched_at'] = datetime.now(timezone.utc).isoformat() + data['_redis_client'] = self.redis_client + data['_redis_config'] = self.redis_config + data['_shape_cache'] = self.shape_cache # Pass cache to downstream + + ttl = self.redis_client.ttl(key) + if ttl > 0: + data['_ttl_seconds'] = ttl + + batch.append(data) + + except json.JSONDecodeError as e: + print(f"⚠️ Failed to decode JSON from key {key}: {e}") + except Exception as e: + print(f"⚠️ Error processing key {key}: {e}") + + if batch: + cache_stats = self.shape_cache.stats() + print(f"πŸ“¦ Fetched {len(batch)} vehicle records | " + f"Shape cache: {cache_stats['hit_rate_pct']}% hit rate " + f"({cache_stats['hits']}/{cache_stats['hits'] + cache_stats['misses']})") + + except Exception as e: + print(f"βœ— Error in next_batch: {e}") + + return batch + + def next_awake(self): + """Return when to wake up next (must be timezone-aware)""" + return datetime.now(timezone.utc) + timedelta(milliseconds=self.redis_config.poll_interval_ms) + + def snapshot(self): + """Save state for recovery""" + return None + + def close(self): + """Cleanup when source is closed""" + if self.redis_client: + cache_stats = self.shape_cache.stats() + print(f"\nβœ“ Worker closing. Final shape cache stats: {cache_stats}") + self.redis_client.close() + print("βœ“ Disconnected from Redis") + + +class RedisVehicleSource(FixedPartitionedSource): + """Source that creates Redis polling partitions""" + + def __init__(self, redis_config: RedisConfig): + self.redis_config = redis_config + + def list_parts(self): + """List available partitions""" + return ["redis-vehicles-0"] + + def build_part(self, step_id, for_part, resume_state): + """Build partition for reading""" + return RedisVehiclePartition(self.redis_config) + + +# ============================================================================ +# Processing Functions +# ============================================================================ + +def validate_vehicle_data(data: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """ + Validate vehicle data has required fields for ETA estimation. + Returns None if data is invalid (will be filtered out). + """ + required_fields = ['vehicle_id', 'lat', 'lon', 'speed', 'timestamp', 'route'] + + if not all(field in data for field in required_fields): + missing = [f for f in required_fields if f not in data] + print(f"⚠️ Invalid vehicle data: missing fields {missing}") + return None + + # Validate data types and ranges + try: + float(data['lat']) + float(data['lon']) + float(data['speed']) + except (ValueError, TypeError): + print(f"⚠️ Invalid vehicle data: non-numeric lat/lon/speed") + return None + + return data + + +def enrich_with_stops_and_shape(data: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """ + Enrich vehicle data with upcoming stops and pre-loaded shape from Redis cache. + + ZERO DATABASE CALLS - All data from Redis cache or in-memory cache. + + Shape loading priority: + 1. In-memory cache (zero latency) + 2. Redis cache (low latency) + 3. Mock data fallback + + Expected Redis cache structure: + + Stops - Key: "route_stops:{route_id}" + Value: JSON array of stops with fields: stop_id, stop_sequence, lat, lon + + Shapes - Key: "route_shape:{route_id}" + Value: JSON object with shape_id and points array + """ + route_id = data.get('route') + + if not route_id: + print(f"⚠️ Vehicle {data.get('vehicle_id')} missing route_id") + return None + + redis_client = data.get('_redis_client') + redis_config = data.get('_redis_config') + shape_cache = data.get('_shape_cache') + + if not redis_client or not redis_config or not shape_cache: + print(f"⚠️ Missing Redis client/config/cache for vehicle {data.get('vehicle_id')}") + return None + + try: + # Load stops from Redis + stops_key = f"{redis_config.route_stops_key_prefix}{route_id}" + stops_json = redis_client.get(stops_key) + upcoming_stops = None + + if stops_json: + upcoming_stops = json.loads(stops_json) + + if not upcoming_stops or not isinstance(upcoming_stops, list): + print(f"⚠️ No stops available for route {route_id}") + return None + + data['upcoming_stops'] = upcoming_stops + + # Load shape from Redis/cache (best-effort, not required) + shape = load_shape_from_redis( + redis_client, + route_id, + shape_cache, + redis_config.route_shape_key_prefix + ) + + # Store shape for inference + data['_shape'] = shape + data['_shape_available'] = shape is not None + + return data + + except json.JSONDecodeError as e: + print(f"⚠️ Failed to decode JSON for route {route_id}: {e}") + return None + except Exception as e: + print(f"⚠️ Error enriching with stops/shape: {e}") + import traceback + traceback.print_exc() + return None + + +def process_eta(data: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """ + Process vehicle data through the ETA estimator with pre-loaded shape. + + ZERO I/O DURING INFERENCE - Shape already loaded in enrichment step. + + Input: Vehicle position data enriched with upcoming stops and optional shape + Output: ETA predictions for upcoming stops + """ + try: + # Prepare vehicle position dict for estimator + vehicle_position = { + 'vehicle_id': data['vehicle_id'], + 'route': data['route'], + 'lat': float(data['lat']), + 'lon': float(data['lon']), + 'speed': float(data['speed']), # Assumed to be in m/s + 'heading': data.get('heading'), + 'timestamp': data['timestamp'] + } + + upcoming_stops = data.get('upcoming_stops', []) + + if not upcoming_stops: + print(f"⚠️ No upcoming stops for vehicle {data['vehicle_id']}") + return None + + # Get pre-loaded shape (or None) + shape = data.get('_shape') + trip_id = data.get('trip_id') + + # Call estimator with pre-loaded shape (zero I/O!) + result = estimate_stop_times( + vehicle_position=vehicle_position, + upcoming_stops=upcoming_stops, + route_id=data['route'], + trip_id=trip_id, + max_stops=5, + shape=shape # Pre-loaded shape, no database calls + ) + + # Add original vehicle data for context + result['_original_vehicle_data'] = { + 'vehicle_id': data['vehicle_id'], + 'route': data['route'], + 'lat': data['lat'], + 'lon': data['lon'], + 'speed': data['speed'], + 'timestamp': data['timestamp'], + 'trip_id': trip_id + } + + shape_status = "with shape" if result.get('shape_used') else "without shape" + print(f"βœ“ Processed ETA for vehicle {data['vehicle_id']} on route {data['route']} {shape_status}: " + f"{len(result['predictions'])} stops predicted") + + return result + + except Exception as e: + print(f"βœ— Error processing ETA for vehicle {data.get('vehicle_id')}: {e}") + import traceback + traceback.print_exc() + return None + + +def format_for_redis(result: Dict[str, Any]) -> tuple[str, str]: + """ + Format ETA prediction result for Redis storage. + + Returns: (key, value) tuple + Key format: "predictions:{vehicle_id}" + Value: JSON string of prediction result + """ + vehicle_id = result.get('vehicle_id', 'unknown') + key = f"predictions:{vehicle_id}" + + # Add metadata + result['_stored_at'] = datetime.now(timezone.utc).isoformat() + + value = json.dumps(result, indent=2) + + return (key, value) + + +# ============================================================================ +# Redis Output Sink +# ============================================================================ + +class RedisETASinkPartition(StatelessSinkPartition): + """Partition that writes ETA predictions to Redis""" + + def __init__(self, config: RedisConfig, worker_index: int): + self.config = config + self.worker_index = worker_index + self.client = None + self.count = 0 + self.shape_count = 0 + self._setup_client() + + def _setup_client(self): + """Initialize Redis client""" + try: + self.client = redis.Redis( + host=self.config.host, + port=self.config.port, + db=self.config.db, + password=self.config.password, + decode_responses=True + ) + self.client.ping() + print(f"βœ“ Worker {self.worker_index}: Redis sink connected") + except redis.ConnectionError as e: + print(f"βœ— Worker {self.worker_index}: Failed to connect to Redis: {e}") + raise + + def write_batch(self, items: List[tuple[str, str]]) -> None: + """Write batch of predictions to Redis""" + for key, value in items: + try: + # Write to Redis with TTL + self.client.setex( + key, + self.config.predictions_ttl, + value + ) + self.count += 1 + + # Track shape usage + try: + result = json.loads(value) + if result.get('shape_used'): + self.shape_count += 1 + except: + pass + + if self.count % 10 == 0: + shape_pct = (self.shape_count / self.count * 100) if self.count > 0 else 0 + print(f"[Worker {self.worker_index}] Stored {self.count} predictions " + f"({self.shape_count} with shapes, {shape_pct:.1f}%)") + + except Exception as e: + print(f"βœ— Worker {self.worker_index}: Error writing to Redis: {e}") + + def close(self) -> None: + """Cleanup when sink is closed""" + if self.client: + self.client.close() + shape_pct = (self.shape_count / self.count * 100) if self.count > 0 else 0 + print(f"βœ“ Worker {self.worker_index}: Redis sink closed.") + print(f" Total predictions: {self.count}") + print(f" With shapes: {self.shape_count} ({shape_pct:.1f}%)") + + +class RedisETASink(DynamicSink): + """Dynamic sink that writes ETA predictions to Redis""" + + def __init__(self, config: RedisConfig): + self.config = config + + def build(self, step_id: str, worker_index: int, worker_count: int) -> StatelessSinkPartition: + """Build partition for writing""" + return RedisETASinkPartition(self.config, worker_index) + + +# ============================================================================ +# Dataflow Definition +# ============================================================================ + +def build_flow(): + """ + Build and return the Bytewax dataflow for low-latency ETA prediction. + + LOW-LATENCY DESIGN: + - All data from Redis cache (no database calls) + - In-memory shape caching per worker + - Pre-loaded shapes passed to estimator + - Zero I/O during inference + + Pipeline: + 1. Input: Poll vehicle positions from Redis + 2. Validate: Check required fields + 3. Enrich: Load stops and shapes from Redis/cache + 4. Process: Run ETA estimation with pre-loaded shapes + 5. Format: Prepare for Redis storage + 6. Output: Store predictions in Redis with TTL + """ + + # Create configuration + redis_config = RedisConfig( + host="localhost", + port=6379, + db=0, + password=None, + vehicle_key_pattern="vehicle:*", + route_stops_key_prefix="route_stops:", + route_shape_key_prefix="route_shape:", + predictions_key_prefix="predictions:", + poll_interval_ms=1000, + predictions_ttl=300 # 5 minutes + ) + + # Initialize dataflow + flow = Dataflow("eta-prediction-flow") + + # Step 1: Input from Redis (vehicle positions) + redis_source = RedisVehicleSource(redis_config) + stream = op.input("redis-vehicles", flow, redis_source) + + # Step 2: Validate vehicle data + stream = op.filter_map("validate", stream, validate_vehicle_data) + + # Step 3: Enrich with stops and pre-load shapes from Redis/cache + stream = op.filter_map("enrich-stops-shape", stream, enrich_with_stops_and_shape) + + # Step 4: Process through ETA estimator (zero I/O - shapes pre-loaded!) + stream = op.filter_map("estimate-eta", stream, process_eta) + + # Step 5: Format for Redis storage + stream = op.map("format-redis", stream, format_for_redis) + + # Step 6: Output to Redis + redis_sink = RedisETASink(redis_config) + op.output("redis-predictions", stream, redis_sink) + + return flow + + +# ============================================================================ +# Main Entry Point +# ============================================================================ + +# Bytewax looks for a variable called 'flow' at module level +flow = build_flow() + +if __name__ == "__main__": + print("\n" + "="*70) + print("LOW-LATENCY ETA PREDICTION DATAFLOW") + print("="*70) + print("Architecture:") + print(" β€’ Zero database calls during inference") + print(" β€’ All data from Redis cache (stops, shapes)") + print(" β€’ In-memory LRU shape caching per worker") + print(" β€’ Pre-loaded shapes passed to estimator") + print("\nThis flow:") + print(" 1. Reads vehicle positions from Redis (vehicle:*)") + print(" 2. Enriches with stops from Redis (route_stops:{route_id})") + print(" 3. Loads shapes from Redis with caching (route_shape:{route_id})") + print(" 4. Estimates ETAs with pre-loaded shapes (zero I/O)") + print(" 5. Stores predictions in Redis (predictions:*)") + print("\nShape Loading (Low-Latency Priority):") + print(" 1. In-memory worker cache (zero latency)") + print(" 2. Redis cache (low latency)") + print(" 3. Mock data fallback (testing)") + print("\nPerformance Features:") + print(" β€’ LRU cache with 100 shape limit per worker") + print(" β€’ Cache hit rate monitoring") + print(" β€’ Shape-aware spatial features when available") + print(" β€’ Graceful fallback to haversine distance") + print("\nPrerequisites:") + print(" - MQTT subscriber writing to Redis (vehicle:* keys)") + print(" - Route stops cached in Redis (route_stops:* keys)") + print(" - Route shapes cached in Redis (route_shape:* keys)") + print(" - Trained models in models/trained/ directory") + print("\nSetup:") + print(" 1. Load cache: python mock_route_stops.py") + print(" 2. Start flow: python -m bytewax.run bytewax_eta_flow") + print(" 3. Multiple workers: python -m bytewax.run bytewax_eta_flow -w 4") + print("\nMonitoring:") + print(" β€’ Watch cache hit rates in logs") + print(" β€’ Track shape usage percentage") + print(" β€’ Monitor prediction throughput") + print("="*70 + "\n") \ No newline at end of file diff --git a/eta_prediction/bytewax/pyproject.toml b/eta_prediction/bytewax/pyproject.toml new file mode 100644 index 0000000..53e1b52 --- /dev/null +++ b/eta_prediction/bytewax/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "gtfsrt-tools" +version = "0.1.0" +description = "Utilities for exploring GTFS-Realtime feeds (Vehicle Positions, Trip Updates, Alerts)." +authors = [ + { name = "JΓ¦" } +] +license = { text = "MIT" } +readme = "README.md" +requires-python = ">=3.12, <3.13" + +dependencies = [ + "bytewax>=0.21.1", + "celery>=5.5.3", + "django>=4.2.25", + "gtfs-realtime-bindings>=1.0.0", + "matplotlib>=3.9.4", + "pandas>=2.3.3", + "psycopg>=3.2.11", + "redis>=7.0.1", + "requests>=2.31", + "scikit-learn>=1.7.2", + "xgboost>=3.1.2", +] + +[tool.uv.workspace] +members = [ + "feature_engineering/proj", + "feature_engineering", +] diff --git a/eta_prediction/bytewax/subscriber/mqtt2redis.py b/eta_prediction/bytewax/subscriber/mqtt2redis.py new file mode 100644 index 0000000..388cc4f --- /dev/null +++ b/eta_prediction/bytewax/subscriber/mqtt2redis.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +MQTT to Redis Subscriber Bridge for ETA Prediction Pipeline +Subscribes to MQTT vehicle topics and stores enriched data in Redis. + +Usage: + pip install paho-mqtt redis + python mqtt_to_redis_subscriber.py +""" + +import json +import time +import sys +from datetime import datetime, timezone +import paho.mqtt.client as mqtt +import redis + +# ============================================================================ +# Configuration +# ============================================================================ + +# MQTT Configuration +MQTT_HOST = "localhost" +MQTT_PORT = 1883 +MQTT_USER = "admin" +MQTT_PASS = "admin" +MQTT_TOPIC = "transit/vehicles/bus/#" # Subscribe to all bus topics + +# Redis Configuration +REDIS_HOST = "localhost" +REDIS_PORT = 6379 +REDIS_DB = 0 +REDIS_PASSWORD = None +REDIS_KEY_PREFIX = "vehicle:" # Keys will be like "vehicle:BUS-001" +REDIS_TTL = 300 # Time to live in seconds (5 minutes) + +# Optional: Publish to Redis channel for real-time subscribers +REDIS_PUBSUB_ENABLED = False +REDIS_PUBSUB_CHANNEL = "vehicle_updates" + +# Data validation - ensure all required fields are present +REQUIRED_FIELDS = [ + 'vehicle_id', + 'lat', + 'lon', + 'speed', + 'timestamp' +] + +# Optional fields that enhance predictions (but won't reject messages if missing) +RECOMMENDED_FIELDS = [ + 'route_id', + 'trip_id', + 'stop_id', + 'stop_lat', + 'stop_lon', + 'stop_sequence', + 'heading', + 'bearing' +] + +# ============================================================================ +# Redis Client +# ============================================================================ + +redis_client = None + +def connect_redis(): + """Connect to Redis""" + global redis_client + + try: + redis_client = redis.Redis( + host=REDIS_HOST, + port=REDIS_PORT, + db=REDIS_DB, + password=REDIS_PASSWORD, + decode_responses=True + ) + + # Test connection + redis_client.ping() + print(f"βœ“ Connected to Redis at {REDIS_HOST}:{REDIS_PORT}") + return True + + except redis.ConnectionError as e: + print(f"βœ— Failed to connect to Redis: {e}") + print(f" Make sure Redis is running: docker-compose up -d redis") + return False + except Exception as e: + print(f"βœ— Redis connection error: {e}") + return False + +def validate_vehicle_data(data): + """ + Validate that vehicle data contains required fields for ETA prediction. + Returns (is_valid, missing_fields, missing_recommended) + """ + missing_required = [field for field in REQUIRED_FIELDS if field not in data] + missing_recommended = [field for field in RECOMMENDED_FIELDS if field not in data] + + is_valid = len(missing_required) == 0 + + return is_valid, missing_required, missing_recommended + +def enrich_vehicle_data(data): + """ + Enrich vehicle data with additional metadata and transformations. + """ + # Ensure consistent field naming + if 'route' in data and 'route_id' not in data: + data['route_id'] = data['route'] + + # Add UTC timestamp if not present or normalize it + if 'timestamp' in data: + try: + # Parse and normalize to ISO format with timezone + ts = datetime.fromisoformat(data['timestamp'].replace('Z', '+00:00')) + data['timestamp'] = ts.isoformat() + except: + # If parsing fails, use current time + data['timestamp'] = datetime.now(timezone.utc).isoformat() + else: + data['timestamp'] = datetime.now(timezone.utc).isoformat() + + # Ensure heading and bearing are both present (some systems use one or the other) + if 'heading' in data and 'bearing' not in data: + data['bearing'] = data['heading'] + elif 'bearing' in data and 'heading' not in data: + data['heading'] = data['bearing'] + + # Add processing metadata + data['_mqtt_received_at'] = datetime.now(timezone.utc).isoformat() + data['_data_quality'] = 'complete' if all(f in data for f in RECOMMENDED_FIELDS) else 'partial' + + return data + +def store_in_redis(vehicle_id, data): + """Store vehicle data in Redis with enrichment""" + try: + # Validate data + is_valid, missing_required, missing_recommended = validate_vehicle_data(data) + + if not is_valid: + print(f"⚠️ Invalid data for {vehicle_id}: missing required fields {missing_required}") + return False + + # Warn about missing recommended fields + if missing_recommended: + print(f"ℹ️ {vehicle_id} missing recommended fields: {missing_recommended}") + + # Enrich data + enriched_data = enrich_vehicle_data(data) + + # Create Redis key + key = f"{REDIS_KEY_PREFIX}{vehicle_id}" + + # Convert data to JSON string + json_data = json.dumps(enriched_data) + + # Store with TTL (expires after REDIS_TTL seconds) + redis_client.setex(key, REDIS_TTL, json_data) + + # Optional: Publish to Redis Pub/Sub channel for real-time subscribers + if REDIS_PUBSUB_ENABLED: + redis_client.publish(REDIS_PUBSUB_CHANNEL, json_data) + + return True + + except Exception as e: + print(f"βœ— Error storing data in Redis: {e}") + import traceback + traceback.print_exc() + return False + +# ============================================================================ +# MQTT Callbacks +# ============================================================================ + +message_count = 0 +error_count = 0 +warning_count = 0 +start_time = None + +def on_connect(client, userdata, flags, rc): + """Callback when connected to MQTT broker""" + global start_time + + if rc == 0: + print(f"βœ“ Connected to MQTT broker at {MQTT_HOST}:{MQTT_PORT}") + + # Subscribe to topic + client.subscribe(MQTT_TOPIC, qos=1) + print(f"βœ“ Subscribed to topic: {MQTT_TOPIC}") + print("\n" + "="*70) + print("MQTT β†’ Redis Bridge Active (ETA Prediction Pipeline)") + print("="*70) + print("Features:") + print(" β€’ Data validation (required & recommended fields)") + print(" β€’ Timestamp normalization") + print(" β€’ Field enrichment (heading/bearing consistency)") + print(" β€’ Quality scoring") + print("="*70) + print(f"Listening for messages... (press Ctrl+C to stop)\n") + + start_time = time.time() + + else: + print(f"βœ— Failed to connect to MQTT broker, return code: {rc}") + print(" Return codes: 0=Success, 1=Protocol version, 2=Invalid client ID") + print(" 3=Server unavailable, 4=Bad credentials, 5=Not authorized") + sys.exit(1) + +def on_message(client, userdata, msg): + """Callback when a message is received""" + global message_count, error_count, warning_count + + try: + # Parse the message + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + + # Get vehicle ID + vehicle_id = data.get('vehicle_id') + + if not vehicle_id: + print(f"⚠️ Message missing vehicle_id: {msg.topic}") + error_count += 1 + return + + # Add MQTT metadata + data['_mqtt_topic'] = msg.topic + + # Store in Redis (with validation and enrichment) + if store_in_redis(vehicle_id, data): + message_count += 1 + + # Detailed output for first few messages, then summary + if message_count <= 5: + print(f"βœ“ [{message_count}] Stored {vehicle_id}") + print(f" Route: {data.get('route_id', 'N/A')}") + print(f" Position: ({data.get('lat'):.4f}, {data.get('lon'):.4f})") + print(f" Speed: {data.get('speed')} km/h") + print(f" Next Stop: {data.get('stop_id', 'N/A')}") + print(f" Quality: {data.get('_data_quality', 'unknown')}") + elif message_count % 10 == 0: + elapsed = time.time() - start_time if start_time else 0 + rate = message_count / elapsed if elapsed > 0 else 0 + print(f"πŸ“Š [{message_count}] messages | " + f"{rate:.1f} msg/sec | " + f"{error_count} errors | " + f"{warning_count} warnings") + else: + # Compact output for routine messages + quality_icon = "βœ“" if data.get('_data_quality') == 'complete' else "⚠" + print(f"{quality_icon} [{message_count}] {vehicle_id} β†’ " + f"{data.get('route_id', 'N/A'):10s} | " + f"Speed: {data.get('speed', 0):5.1f} km/h | " + f"Stop: {data.get('stop_id', 'N/A')}") + else: + error_count += 1 + + except json.JSONDecodeError as e: + print(f"βœ— Failed to decode JSON from topic {msg.topic}: {e}") + error_count += 1 + except Exception as e: + print(f"βœ— Error processing message: {e}") + import traceback + traceback.print_exc() + error_count += 1 + +def on_disconnect(client, userdata, rc): + """Callback when disconnected from MQTT broker""" + if rc != 0: + print(f"\nβœ— Unexpected disconnection from MQTT broker (code: {rc})") + print(" Attempting to reconnect...") + +# ============================================================================ +# Main Function +# ============================================================================ + +def print_config(): + """Print current configuration""" + print("="*70) + print("MQTT to Redis Subscriber - ETA Prediction Pipeline") + print("="*70) + print(f"MQTT Broker: {MQTT_HOST}:{MQTT_PORT}") + print(f"MQTT Topic: {MQTT_TOPIC}") + print(f"Redis Server: {REDIS_HOST}:{REDIS_PORT}") + print(f"Redis Key: {REDIS_KEY_PREFIX}") + print(f"Redis TTL: {REDIS_TTL} seconds") + print(f"Pub/Sub: {'Enabled' if REDIS_PUBSUB_ENABLED else 'Disabled'}") + if REDIS_PUBSUB_ENABLED: + print(f"Pub/Sub Channel: {REDIS_PUBSUB_CHANNEL}") + print() + print("Required Fields: " + ", ".join(REQUIRED_FIELDS)) + print("Recommended: " + ", ".join(RECOMMENDED_FIELDS)) + print("="*70) + print() + +def print_stats(): + """Print final statistics""" + print("\n" + "="*70) + print("FINAL STATISTICS") + print("="*70) + print(f"Total messages processed: {message_count}") + print(f"Total errors: {error_count}") + print(f"Total warnings: {warning_count}") + + if message_count > 0: + success_rate = ((message_count / (message_count + error_count)) * 100) if (message_count + error_count) > 0 else 0 + print(f"Success rate: {success_rate:.1f}%") + + if start_time: + elapsed = time.time() - start_time + rate = message_count / elapsed if elapsed > 0 else 0 + print(f"Average rate: {rate:.2f} messages/second") + print(f"Running time: {elapsed:.1f} seconds") + print("="*70) + +def main(): + """Main function""" + print_config() + + # Connect to Redis first + print("Connecting to Redis...") + if not connect_redis(): + print("\nβœ— Cannot start without Redis connection") + sys.exit(1) + + print() + + # Create MQTT client + print("Setting up MQTT client...") + mqtt_client = mqtt.Client(client_id="mqtt-redis-bridge-eta") + mqtt_client.username_pw_set(MQTT_USER, MQTT_PASS) + + # Set up callbacks + mqtt_client.on_connect = on_connect + mqtt_client.on_message = on_message + mqtt_client.on_disconnect = on_disconnect + + try: + # Connect to MQTT broker + print(f"Connecting to MQTT broker at {MQTT_HOST}:{MQTT_PORT}...") + mqtt_client.connect(MQTT_HOST, MQTT_PORT, 60) + + # Start the loop (blocking) + mqtt_client.loop_forever() + + except KeyboardInterrupt: + print_stats() + + except ConnectionRefusedError: + print(f"\nβœ— Connection refused to MQTT broker at {MQTT_HOST}:{MQTT_PORT}") + print(" Make sure RabbitMQ is running: docker-compose up -d rabbitmq") + sys.exit(1) + + except Exception as e: + print(f"\nβœ— Error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + finally: + # Cleanup + mqtt_client.disconnect() + if redis_client: + redis_client.close() + print("\nβœ“ Subscriber stopped cleanly\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/eta_prediction/bytewax/subscriber/pyproject.toml b/eta_prediction/bytewax/subscriber/pyproject.toml new file mode 100644 index 0000000..83d3476 --- /dev/null +++ b/eta_prediction/bytewax/subscriber/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "databus-mqtt" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "paho-mqtt>=2.1.0", + "redis>=7.0.0", +] + +[project.optional-dependencies] +dev = [] + +[dependency-groups] +dev = [ + "ruff>=0.14.3", +] diff --git a/eta_prediction/bytewax/subscriber/uv.lock b/eta_prediction/bytewax/subscriber/uv.lock new file mode 100644 index 0000000..b50c5f1 --- /dev/null +++ b/eta_prediction/bytewax/subscriber/uv.lock @@ -0,0 +1,71 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "databus-mqtt" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "paho-mqtt" }, + { name = "redis" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "paho-mqtt", specifier = ">=2.1.0" }, + { name = "redis", specifier = ">=7.0.0" }, +] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [{ name = "ruff", specifier = ">=0.14.3" }] + +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, +] + +[[package]] +name = "redis" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/5b/dd7406afa6c95e3d8fa9d652b6d6dd17dd4a6bf63cb477014e8ccd3dcd46/ruff-0.14.7.tar.gz", hash = "sha256:3417deb75d23bd14a722b57b0a1435561db65f0ad97435b4cf9f85ffcef34ae5", size = 5727324, upload-time = "2025-11-28T20:55:10.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/b1/7ea5647aaf90106f6d102230e5df874613da43d1089864da1553b899ba5e/ruff-0.14.7-py3-none-linux_armv6l.whl", hash = "sha256:b9d5cb5a176c7236892ad7224bc1e63902e4842c460a0b5210701b13e3de4fca", size = 13414475, upload-time = "2025-11-28T20:54:54.569Z" }, + { url = "https://files.pythonhosted.org/packages/af/19/fddb4cd532299db9cdaf0efdc20f5c573ce9952a11cb532d3b859d6d9871/ruff-0.14.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3f64fe375aefaf36ca7d7250292141e39b4cea8250427482ae779a2aa5d90015", size = 13634613, upload-time = "2025-11-28T20:55:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/40/2b/469a66e821d4f3de0440676ed3e04b8e2a1dc7575cf6fa3ba6d55e3c8557/ruff-0.14.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93e83bd3a9e1a3bda64cb771c0d47cda0e0d148165013ae2d3554d718632d554", size = 12765458, upload-time = "2025-11-28T20:55:26.128Z" }, + { url = "https://files.pythonhosted.org/packages/f1/05/0b001f734fe550bcfde4ce845948ac620ff908ab7241a39a1b39bb3c5f49/ruff-0.14.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3838948e3facc59a6070795de2ae16e5786861850f78d5914a03f12659e88f94", size = 13236412, upload-time = "2025-11-28T20:55:28.602Z" }, + { url = "https://files.pythonhosted.org/packages/11/36/8ed15d243f011b4e5da75cd56d6131c6766f55334d14ba31cce5461f28aa/ruff-0.14.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24c8487194d38b6d71cd0fd17a5b6715cda29f59baca1defe1e3a03240f851d1", size = 13182949, upload-time = "2025-11-28T20:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cf/fcb0b5a195455729834f2a6eadfe2e4519d8ca08c74f6d2b564a4f18f553/ruff-0.14.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79c73db6833f058a4be8ffe4a0913b6d4ad41f6324745179bd2aa09275b01d0b", size = 13816470, upload-time = "2025-11-28T20:55:08.203Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5d/34a4748577ff7a5ed2f2471456740f02e86d1568a18c9faccfc73bd9ca3f/ruff-0.14.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:12eb7014fccff10fc62d15c79d8a6be4d0c2d60fe3f8e4d169a0d2def75f5dad", size = 15289621, upload-time = "2025-11-28T20:55:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/53/53/0a9385f047a858ba133d96f3f8e3c9c66a31cc7c4b445368ef88ebeac209/ruff-0.14.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c623bbdc902de7ff715a93fa3bb377a4e42dd696937bf95669118773dbf0c50", size = 14975817, upload-time = "2025-11-28T20:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d7/2f1c32af54c3b46e7fadbf8006d8b9bcfbea535c316b0bd8813d6fb25e5d/ruff-0.14.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f53accc02ed2d200fa621593cdb3c1ae06aa9b2c3cae70bc96f72f0000ae97a9", size = 14284549, upload-time = "2025-11-28T20:55:06.08Z" }, + { url = "https://files.pythonhosted.org/packages/92/05/434ddd86becd64629c25fb6b4ce7637dd52a45cc4a4415a3008fe61c27b9/ruff-0.14.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:281f0e61a23fcdcffca210591f0f53aafaa15f9025b5b3f9706879aaa8683bc4", size = 14071389, upload-time = "2025-11-28T20:55:35.617Z" }, + { url = "https://files.pythonhosted.org/packages/ff/50/fdf89d4d80f7f9d4f420d26089a79b3bb1538fe44586b148451bc2ba8d9c/ruff-0.14.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dbbaa5e14148965b91cb090236931182ee522a5fac9bc5575bafc5c07b9f9682", size = 14202679, upload-time = "2025-11-28T20:55:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/77/54/87b34988984555425ce967f08a36df0ebd339bb5d9d0e92a47e41151eafc/ruff-0.14.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1464b6e54880c0fe2f2d6eaefb6db15373331414eddf89d6b903767ae2458143", size = 13147677, upload-time = "2025-11-28T20:55:19.933Z" }, + { url = "https://files.pythonhosted.org/packages/67/29/f55e4d44edfe053918a16a3299e758e1c18eef216b7a7092550d7a9ec51c/ruff-0.14.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f217ed871e4621ea6128460df57b19ce0580606c23aeab50f5de425d05226784", size = 13151392, upload-time = "2025-11-28T20:55:21.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/69/47aae6dbd4f1d9b4f7085f4d9dcc84e04561ee7ad067bf52e0f9b02e3209/ruff-0.14.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6be02e849440ed3602d2eb478ff7ff07d53e3758f7948a2a598829660988619e", size = 13412230, upload-time = "2025-11-28T20:55:12.749Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4b/6e96cb6ba297f2ba502a231cd732ed7c3de98b1a896671b932a5eefa3804/ruff-0.14.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19a0f116ee5e2b468dfe80c41c84e2bbd6b74f7b719bee86c2ecde0a34563bcc", size = 14195397, upload-time = "2025-11-28T20:54:56.896Z" }, + { url = "https://files.pythonhosted.org/packages/69/82/251d5f1aa4dcad30aed491b4657cecd9fb4274214da6960ffec144c260f7/ruff-0.14.7-py3-none-win32.whl", hash = "sha256:e33052c9199b347c8937937163b9b149ef6ab2e4bb37b042e593da2e6f6cccfa", size = 13126751, upload-time = "2025-11-28T20:55:03.47Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b5/d0b7d145963136b564806f6584647af45ab98946660d399ec4da79cae036/ruff-0.14.7-py3-none-win_amd64.whl", hash = "sha256:e17a20ad0d3fad47a326d773a042b924d3ac31c6ca6deb6c72e9e6b5f661a7c6", size = 14531726, upload-time = "2025-11-28T20:54:59.121Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d2/1637f4360ada6a368d3265bf39f2cf737a0aaab15ab520fc005903e883f8/ruff-0.14.7-py3-none-win_arm64.whl", hash = "sha256:be4d653d3bea1b19742fcc6502354e32f65cd61ff2fbdb365803ef2c2aec6228", size = 13609215, upload-time = "2025-11-28T20:55:15.375Z" }, +] diff --git a/eta_prediction/bytewax/test_redis_predictions.py b/eta_prediction/bytewax/test_redis_predictions.py new file mode 100644 index 0000000..aa48aae --- /dev/null +++ b/eta_prediction/bytewax/test_redis_predictions.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +""" +Simple tester to verify ETA predictions are being stored in Redis. + +This script: +1. Connects to Redis +2. Reads predictions under 'predictions:*' keys +3. Prints predictions as they appear / get updated + +Usage: + python test_redis_predictions.py # One-time snapshot + python test_redis_predictions.py --continuous # Keep monitoring + python test_redis_predictions.py --vehicle bus_42 # Specific vehicle +""" + +import json +import time +import argparse +from datetime import datetime +from typing import Dict, Any +import redis + + +class PredictionMonitor: + """Monitor ETA predictions in Redis and print them.""" + + def __init__(self, redis_host: str = "localhost", redis_port: int = 6379, redis_db: int = 0): + self.client = redis.Redis( + host=redis_host, + port=redis_port, + db=redis_db, + decode_responses=True, + ) + # Track last-seen "version" per key so we also catch updates to existing keys + self.last_seen: Dict[str, str] = {} + + def connect(self) -> bool: + """Test Redis connection""" + try: + self.client.ping() + print("βœ“ Connected to Redis") + return True + except redis.ConnectionError as e: + print(f"βœ— Failed to connect to Redis: {e}") + return False + + def _print_prediction(self, key: str, data: Any): + """Pretty-print a single prediction entry.""" + print("=" * 80) + print(f"{datetime.now().isoformat()} | KEY: {key}") + print("-" * 80) + + # If JSON-parsed dict, pretty-print it + if isinstance(data, dict): + print(json.dumps(data, indent=2, sort_keys=True)) + else: + # Raw string + print(data) + + print("=" * 80) + print() + + def snapshot(self, pattern: str): + """One-time snapshot of all predictions matching the pattern.""" + keys = self.client.keys(pattern) + print(f"\nπŸ“Š Found {len(keys)} prediction keys in Redis (pattern: '{pattern}')\n") + + if not keys: + print("No predictions found.") + return + + for key in sorted(keys): + value = self.client.get(key) + if not value: + continue + + try: + data = json.loads(value) + except json.JSONDecodeError: + data = value # print raw + + self._print_prediction(key, data) + + def monitor(self, pattern: str, interval: int = 2): + """Continuously monitor Redis for new or updated predictions.""" + print(f"\nπŸ” Monitoring Redis for predictions (pattern: '{pattern}')...") + print("Press Ctrl+C to stop.\n") + + try: + while True: + keys = self.client.keys(pattern) + + for key in keys: + value = self.client.get(key) + if not value: + continue + + # Try to parse JSON so we can use 'computed_at' as a version + computed_at = None + parsed = None + try: + parsed = json.loads(value) + if isinstance(parsed, dict): + computed_at = str(parsed.get("computed_at") or "") + except json.JSONDecodeError: + parsed = value + + # Fallback: if no computed_at, use the raw string as version + version = computed_at or value + + # Only print if we've never seen this version for this key + if self.last_seen.get(key) == version: + continue + + self.last_seen[key] = version + self._print_prediction(key, parsed) + + time.sleep(interval) + + except KeyboardInterrupt: + print("\n\nβœ“ Monitoring stopped") + + +def main(): + parser = argparse.ArgumentParser( + description="Test ETA predictions stored in Redis", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python test_redis_predictions.py # One-time snapshot + python test_redis_predictions.py --continuous # Keep monitoring + python test_redis_predictions.py --vehicle bus_42 # Specific vehicle + python test_redis_predictions.py -c -i 5 # Monitor every 5 seconds + """, + ) + + parser.add_argument( + "--continuous", + "-c", + action="store_true", + help="Continuously monitor for predictions", + ) + + parser.add_argument( + "--interval", + "-i", + type=int, + default=2, + help="Polling interval in seconds for continuous mode (default: 2)", + ) + + parser.add_argument( + "--vehicle", + "-v", + type=str, + help="Monitor specific vehicle ID (uses predictions: pattern)", + ) + + parser.add_argument( + "--host", + type=str, + default="localhost", + help="Redis host (default: localhost)", + ) + + parser.add_argument( + "--port", + type=int, + default=6379, + help="Redis port (default: 6379)", + ) + + parser.add_argument( + "--db", + type=int, + default=0, + help="Redis database (default: 0)", + ) + + args = parser.parse_args() + + pattern = f"predictions:{args.vehicle}" if args.vehicle else "predictions:*" + + monitor = PredictionMonitor( + redis_host=args.host, + redis_port=args.port, + redis_db=args.db, + ) + + if not monitor.connect(): + return 1 + + if args.continuous: + monitor.monitor(pattern=pattern, interval=args.interval) + else: + monitor.snapshot(pattern=pattern) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/eta_prediction/bytewax/uv.lock b/eta_prediction/bytewax/uv.lock new file mode 100644 index 0000000..5eb0c8a --- /dev/null +++ b/eta_prediction/bytewax/uv.lock @@ -0,0 +1,665 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "asgiref" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, +] + +[[package]] +name = "billiard" +version = "4.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/50/cc2b8b6e6433918a6b9a3566483b743dcd229da1e974be9b5f259db3aad7/billiard-4.2.3.tar.gz", hash = "sha256:96486f0885afc38219d02d5f0ccd5bec8226a414b834ab244008cbb0025b8dcb", size = 156450, upload-time = "2025-11-16T17:47:30.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/cc/38b6f87170908bd8aaf9e412b021d17e85f690abe00edf50192f1a4566b9/billiard-4.2.3-py3-none-any.whl", hash = "sha256:989e9b688e3abf153f307b68a1328dfacfb954e30a4f920005654e276c69236b", size = 87042, upload-time = "2025-11-16T17:47:29.005Z" }, +] + +[[package]] +name = "bytewax" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prometheus-client" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/eb/ee253e60ab676e2a70aa306cffee67d6a6a40993c8467d3b83ea8bb91e99/bytewax-0.21.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:62f5edef91908e555b70f3794271cd29ff29132a63b0b5cd367c5f53695a3c14", size = 6140409, upload-time = "2024-11-25T20:09:56.772Z" }, + { url = "https://files.pythonhosted.org/packages/38/ef/4dd07083f32f9b019101837a10dc97806fbe3455c70f8cd976eb987dedab/bytewax-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e3ab20069f323ffe508249d72c8c708cac05fddea11b74943103c623676785b6", size = 6041464, upload-time = "2024-11-25T20:09:58.244Z" }, + { url = "https://files.pythonhosted.org/packages/20/1c/41b84d0de3949d75dd52b4b04341bed1c72250c4c1e017b527d7f7570f0b/bytewax-0.21.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8da9bfceb3299df8cf4e8e7efbbda924dbbfea990dff50c8602a059dc8b9cf", size = 7953966, upload-time = "2024-11-25T20:09:59.709Z" }, + { url = "https://files.pythonhosted.org/packages/87/f2/57829eea0d188bd1d54e2555a11ecfd3589ec66d710c8163fb7010a0c82e/bytewax-0.21.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba078e8bc7c6f1e7d7c99c4c07433977717b949112fc53377a0b0f3289398072", size = 7681915, upload-time = "2024-11-25T20:10:01.819Z" }, + { url = "https://files.pythonhosted.org/packages/1f/66/47d1dbdd2a035074b2be3eac983d15742cc2749fee24aa3cc0c505277622/bytewax-0.21.1-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:a118a61a5fc5a1849605e062f80e897922dbdadd37790cf8379e8f1e000d463c", size = 7983932, upload-time = "2024-11-25T20:10:03.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/bc/82686047e63fa709dc084399230c430c4fd2f2f2de3cf8be8d339ebd3f21/bytewax-0.21.1-cp312-none-win_amd64.whl", hash = "sha256:9b3c44984fe76da1fbac941392103610f884523a733a43fcd74543927c179854", size = 5183365, upload-time = "2024-11-25T20:10:04.676Z" }, +] + +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "django" +version = "5.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/a2/933dbbb3dd9990494960f6e64aca2af4c0745b63b7113f59a822df92329e/django-5.2.8.tar.gz", hash = "sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f", size = 10849032, upload-time = "2025-11-05T14:07:32.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/3d/a035a4ee9b1d4d4beee2ae6e8e12fe6dee5514b21f62504e22efcbd9fb46/django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f", size = 8289692, upload-time = "2025-11-05T14:07:28.761Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f9/0e84d593c0e12244150280a630999835a64f2852276161b62a0f98318de0/fonttools-4.61.0.tar.gz", hash = "sha256:ec520a1f0c7758d7a858a00f090c1745f6cde6a7c5e76fb70ea4044a15f712e7", size = 3561884, upload-time = "2025-11-28T17:05:49.491Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/5d/19e5939f773c7cb05480fe2e881d63870b63ee2b4bdb9a77d55b1d36c7b9/fonttools-4.61.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e24a1565c4e57111ec7f4915f8981ecbb61adf66a55f378fdc00e206059fcfef", size = 2846930, upload-time = "2025-11-28T17:04:46.639Z" }, + { url = "https://files.pythonhosted.org/packages/25/b2/0658faf66f705293bd7e739a4f038302d188d424926be9c59bdad945664b/fonttools-4.61.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2bfacb5351303cae9f072ccf3fc6ecb437a6f359c0606bae4b1ab6715201d87", size = 2383016, upload-time = "2025-11-28T17:04:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/29/a3/1fa90b95b690f0d7541f48850adc40e9019374d896c1b8148d15012b2458/fonttools-4.61.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0bdcf2e29d65c26299cc3d502f4612365e8b90a939f46cd92d037b6cb7bb544a", size = 4949425, upload-time = "2025-11-28T17:04:50.482Z" }, + { url = "https://files.pythonhosted.org/packages/af/00/acf18c00f6c501bd6e05ee930f926186f8a8e268265407065688820f1c94/fonttools-4.61.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6cd0d9051b8ddaf7385f99dd82ec2a058e2b46cf1f1961e68e1ff20fcbb61af", size = 4999632, upload-time = "2025-11-28T17:04:52.508Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e0/19a2b86e54109b1d2ee8743c96a1d297238ae03243897bc5345c0365f34d/fonttools-4.61.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e074bc07c31406f45c418e17c1722e83560f181d122c412fa9e815df0ff74810", size = 4939438, upload-time = "2025-11-28T17:04:54.437Z" }, + { url = "https://files.pythonhosted.org/packages/04/35/7b57a5f57d46286360355eff8d6b88c64ab6331107f37a273a71c803798d/fonttools-4.61.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a9b78da5d5faa17e63b2404b77feeae105c1b7e75f26020ab7a27b76e02039f", size = 5088960, upload-time = "2025-11-28T17:04:56.348Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0e/6c5023eb2e0fe5d1ababc7e221e44acd3ff668781489cc1937a6f83d620a/fonttools-4.61.0-cp312-cp312-win32.whl", hash = "sha256:9821ed77bb676736b88fa87a737c97b6af06e8109667e625a4f00158540ce044", size = 2264404, upload-time = "2025-11-28T17:04:58.149Z" }, + { url = "https://files.pythonhosted.org/packages/36/0b/63273128c7c5df19b1e4cd92e0a1e6ea5bb74a400c4905054c96ad60a675/fonttools-4.61.0-cp312-cp312-win_amd64.whl", hash = "sha256:0011d640afa61053bc6590f9a3394bd222de7cfde19346588beabac374e9d8ac", size = 2314427, upload-time = "2025-11-28T17:04:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/0c/14/634f7daea5ffe6a5f7a0322ba8e1a0e23c9257b80aa91458107896d1dfc7/fonttools-4.61.0-py3-none-any.whl", hash = "sha256:276f14c560e6f98d24ef7f5f44438e55ff5a67f78fa85236b218462c9f5d0635", size = 1144485, upload-time = "2025-11-28T17:05:47.573Z" }, +] + +[[package]] +name = "gtfs-realtime-bindings" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/55/ed46db9267d2615851bfba3c22b6c0e7f88751efb5d5d1468291935c7f65/gtfs-realtime-bindings-1.0.0.tar.gz", hash = "sha256:2e8ced8904400cc93ab7e8520adb6934cfa601edacc6f593fc2cb4448662bb47", size = 6197, upload-time = "2023-02-23T17:53:20.8Z" } + +[[package]] +name = "gtfsrt-tools" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "bytewax" }, + { name = "celery" }, + { name = "django" }, + { name = "gtfs-realtime-bindings" }, + { name = "matplotlib" }, + { name = "pandas" }, + { name = "psycopg" }, + { name = "redis" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "xgboost" }, +] + +[package.metadata] +requires-dist = [ + { name = "bytewax", specifier = ">=0.21.1" }, + { name = "celery", specifier = ">=5.5.3" }, + { name = "django", specifier = ">=4.2.25" }, + { name = "gtfs-realtime-bindings", specifier = ">=1.0.0" }, + { name = "matplotlib", specifier = ">=3.9.4" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "psycopg", specifier = ">=3.2.11" }, + { name = "redis", specifier = ">=7.0.1" }, + { name = "requests", specifier = ">=2.31" }, + { name = "scikit-learn", specifier = ">=1.7.2" }, + { name = "xgboost", specifier = ">=3.1.2" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389, upload-time = "2025-10-09T00:26:42.474Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247, upload-time = "2025-10-09T00:26:44.77Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996, upload-time = "2025-10-09T00:26:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153, upload-time = "2025-10-09T00:26:49.07Z" }, + { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093, upload-time = "2025-10-09T00:26:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771, upload-time = "2025-10-09T00:26:53.296Z" }, + { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812, upload-time = "2025-10-09T00:26:54.882Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, + { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883, upload-time = "2025-11-13T16:44:09.222Z" }, + { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522, upload-time = "2025-11-13T16:44:10.475Z" }, + { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445, upload-time = "2025-11-13T16:44:11.869Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161, upload-time = "2025-11-13T16:44:12.778Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171, upload-time = "2025-11-13T16:44:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/05/d4a05988f15fcf90e0088c735b1f2fc04a30b7fc65461d6ec278f5f2f17a/psycopg-3.2.13.tar.gz", hash = "sha256:309adaeda61d44556046ec9a83a93f42bbe5310120b1995f3af49ab6d9f13c1d", size = 160626, upload-time = "2025-11-21T22:34:32.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/14/f2724bd1986158a348316e86fdd0837a838b14a711df3f00e47fba597447/psycopg-3.2.13-py3-none-any.whl", hash = "sha256:a481374514f2da627157f767a9336705ebefe93ea7a0522a6cbacba165da179a", size = 206797, upload-time = "2025-11-21T22:29:39.733Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "redis" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "xgboost" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/64/42310363ecd814de5930981672d20da3d35271721ad2d2b4970b4092825b/xgboost-3.1.2.tar.gz", hash = "sha256:0f94496db277f5c227755e1f3ec775c59bafae38f58c94aa97c5198027c93df5", size = 1237438, upload-time = "2025-11-20T18:33:29.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/1e/efdd603db8cb37422b01d925f9cce1baaac46508661c73f6aafd5b9d7c51/xgboost-3.1.2-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b44f6ee43a28b998e289ab05285bd65a65d7999c78cf60b215e523d23dc94c5d", size = 2377854, upload-time = "2025-11-20T18:06:21.217Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c6/ed928cb106f56ab73b3f4edb5287c1352251eb9225b5932d3dd5e2803f60/xgboost-3.1.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:09690f7430504fcd3b3e62bf826bb1282bb49873b68b07120d2696ab5638df41", size = 2211078, upload-time = "2025-11-20T18:06:47.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/2f/5418f4b1deaf0886caf81c5e056299228ac2fc09b965a2dfe5e4496331c8/xgboost-3.1.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f9b83f39340e5852bbf3e918318e7feb7a2a700ac7e8603f9bc3a06787f0d86b", size = 4953319, upload-time = "2025-11-20T18:28:29.851Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ab/c60fcc137fa685533bb31e721de3ecc88959d393830d59d0204c5cbd2c85/xgboost-3.1.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:24879ac75c0ee21acae0101f791bc43303f072a86d70fdfc89dae10a0008767f", size = 115885060, upload-time = "2025-11-20T18:32:00.773Z" }, + { url = "https://files.pythonhosted.org/packages/30/7d/41847e45ff075f3636c95d1000e0b75189aed4f1ae18c36812575bb42b4b/xgboost-3.1.2-py3-none-win_amd64.whl", hash = "sha256:e627c50003269b4562aa611ed348dff8cb770e11a9f784b3888a43139a0f5073", size = 71979118, upload-time = "2025-11-20T18:27:55.23Z" }, +] diff --git a/eta_prediction/eta_service/estimator.py b/eta_prediction/eta_service/estimator.py index 1e2a04b..506da33 100644 --- a/eta_prediction/eta_service/estimator.py +++ b/eta_prediction/eta_service/estimator.py @@ -1,6 +1,7 @@ """ -ETA Service - Enhanced Implementation -Estimates stop arrival times from vehicle positions with route-specific model support. +ETA Service - Low-Latency Implementation with Direct Shape Support +Estimates stop arrival times from vehicle positions with optional pre-loaded shapes. +No database calls during inference for maximum performance. """ import sys @@ -15,9 +16,44 @@ sys.path.insert(0, str(project_root / "models")) from feature_engineering.temporal import extract_temporal_features + +# Import registry with proper path resolution +import os from models.common.registry import get_registry +# Set model registry directory before importing estimator +MODELS_DIR = project_root / "models" / "trained" +os.environ['MODEL_REGISTRY_DIR'] = str(MODELS_DIR) + +print(f"πŸ”§ Project root: {project_root}") +print(f"πŸ”§ Model registry: {MODELS_DIR}") +print(f"πŸ”§ Registry exists: {MODELS_DIR.exists()}") + +# Verify registry contents if it exists +if MODELS_DIR.exists(): + registry_files = list(MODELS_DIR.glob("*.pkl")) + print(f"πŸ”§ Found {len(registry_files)} .pkl model files") + if (MODELS_DIR / "registry.json").exists(): + print(f"πŸ”§ registry.json found βœ“") + else: + print(f"⚠️ registry.json NOT found") +else: + print(f"⚠️ WARNING: Model registry directory does not exist!") +print() + +# Import shape-aware spatial features +try: + from feature_engineering.spatial import ( + ShapePolyline, + calculate_distance_features_with_shape + ) + SHAPE_SUPPORT = True +except ImportError: + SHAPE_SUPPORT = False + print("⚠️ Shape-aware spatial features not available, using fallback") + + def haversine_distance(lat1, lon1, lat2, lon2): """Calculate distance between two points in meters.""" R = 6371000 # Earth radius in meters @@ -31,8 +67,11 @@ def haversine_distance(lat1, lon1, lat2, lon2): return R * c -def _progress_features(vehicle_position, stop, next_stop, total_segments_hint): - """Approximate distance/progress metrics without requiring shapes.""" +def _progress_features_fallback(vehicle_position, stop, next_stop, total_segments_hint): + """ + Fallback: Approximate distance/progress metrics without shapes. + Used when shape data is unavailable for low-latency inference. + """ vp_lat = vehicle_position["lat"] vp_lon = vehicle_position["lon"] stop_lat = stop["lat"] @@ -45,13 +84,9 @@ def _progress_features(vehicle_position, stop, next_stop, total_segments_hint): next_lat = next_stop["lat"] next_lon = next_stop["lon"] segment_length = haversine_distance(stop_lat, stop_lon, next_lat, next_lon) - if segment_length == 0.0: - distance_to_next = 0.0 - else: + if segment_length > 0: distance_to_next = haversine_distance(vp_lat, vp_lon, next_lat, next_lon) progress_on_segment = max(0.0, min(1.0, 1.0 - (distance_to_next / segment_length))) - else: - distance_to_next = None stop_seq = ( stop.get("stop_sequence") @@ -68,7 +103,38 @@ def _progress_features(vehicle_position, stop, next_stop, total_segments_hint): denom = max(float(total_segments), 1.0) progress_ratio = max(0.0, min(1.0, (completed + progress_on_segment) / denom)) - return distance_to_stop, progress_on_segment, progress_ratio + return { + 'distance_to_stop_m': distance_to_stop, + 'progress_on_segment': progress_on_segment, + 'progress_ratio': progress_ratio, + 'cross_track_error': None, + 'shape_progress': None, + 'shape_distance_to_stop': None + } + + +def _progress_features_with_shape(vehicle_position, stop, next_stop, shape, vehicle_stop_order, total_segments): + """ + Shape-aware: Calculate distance/progress using pre-loaded GTFS shape polyline. + Returns enhanced spatial features including cross-track error and shape-based distances. + """ + features = calculate_distance_features_with_shape( + vehicle_position=vehicle_position, + stop=stop, + next_stop=next_stop, + shape=shape, + vehicle_stop_order=vehicle_stop_order, + total_segments=total_segments + ) + + return { + 'distance_to_stop_m': features.get('distance_to_stop', 0.0), + 'progress_on_segment': features.get('progress_on_segment', 0.0), + 'progress_ratio': features.get('progress_ratio', 0.0), + 'cross_track_error': features.get('cross_track_error'), + 'shape_progress': features.get('shape_progress'), + 'shape_distance_to_stop': features.get('shape_distance_to_stop') + } def _predict_with_model(model_key, model_type, features, distance_m): @@ -120,10 +186,8 @@ def _predict_with_model(model_key, model_type, features, distance_m): wind_speed_kmh=features.get('wind_speed_kmh') ) - # βœ… NEW XGBOOST BRANCH (consistent with xgb/predict.py) elif model_type == 'xgboost': from models.xgb.predict import predict_eta - return predict_eta( model_key=model_key, distance_to_stop=distance_m, @@ -152,7 +216,30 @@ def estimate_stop_times( model_type: str = None, prefer_route_model: bool = True, max_stops: int = 3, + shape: object = None, ) -> dict: + """ + Estimate arrival times for upcoming stops based on vehicle position. + + LOW-LATENCY DESIGN: No database calls during inference! + All data (stops, shapes) must be pre-loaded and passed as arguments. + + Args: + vehicle_position: Dict with vehicle_id, route, lat, lon, speed, timestamp + upcoming_stops: List of dicts with stop_id, stop_sequence, lat, lon + route_id: Optional route override + trip_id: Optional trip ID for metadata + model_key: Optional explicit model to use + model_type: Optional model type filter + prefer_route_model: If True, prefer route-specific models over global + max_stops: Maximum number of stops to predict + shape: Optional pre-loaded ShapePolyline object for shape-aware features. + If None, falls back to haversine-based distance calculations. + Pass a ShapePolyline instance from the cache for best performance. + + Returns: + Dict with predictions, model info, and metadata + """ # Validate inputs if not vehicle_position or not upcoming_stops: @@ -185,6 +272,12 @@ def estimate_stop_times( if route_id is None: route_id = vehicle_position.get('route', 'unknown') + # Validate shape if provided + shape_available = shape is not None and SHAPE_SUPPORT + if shape and not SHAPE_SUPPORT: + print("[ETA Service] Shape provided but spatial module unavailable, using fallback") + shape = None + # Load registry registry = get_registry() model_scope = 'unknown' @@ -202,9 +295,7 @@ def estimate_stop_times( if model_key: model_scope = 'route' - print(f"[ETA Service] Using route-specific model for route {route_id}") else: - print(f"[ETA Service] No route model for {route_id}. Trying global.") model_key = registry.get_best_model( model_type=model_type, route_id='global', @@ -257,7 +348,7 @@ def estimate_stop_times( } # ============================================================ - # PREDICT STOP ETAs + # PREDICT STOP ETAs (SHAPE-AWARE IF AVAILABLE) # ============================================================ predictions = [] approx_total_segments = max( @@ -275,13 +366,6 @@ def estimate_stop_times( for idx, stop in enumerate(stops_to_predict): next_stop = stops_to_predict[idx + 1] if idx + 1 < len(stops_to_predict) else None - distance_m, progress_on_segment, progress_ratio = _progress_features( - vehicle_position, - stop, - next_stop, - approx_total_segments, - ) - stop_sequence_value = ( stop.get('stop_sequence') or stop.get('sequence') @@ -289,7 +373,36 @@ def estimate_stop_times( or idx + 1 ) + # Calculate spatial features (shape-aware if available) + if shape_available: + spatial_features = _progress_features_with_shape( + vehicle_position, + stop, + next_stop, + shape, + vehicle_stop_order=stop_sequence_value, + total_segments=approx_total_segments + ) + else: + spatial_features = _progress_features_fallback( + vehicle_position, + stop, + next_stop, + approx_total_segments + ) + + distance_m = spatial_features['distance_to_stop_m'] + # Build features + progress_on_segment = spatial_features['progress_on_segment'] + if progress_on_segment is None: + progress_on_segment = 0.0 + + progress_ratio = spatial_features['progress_ratio'] + if progress_ratio is None: + # If we truly cannot infer progress, treat as at start of segment + progress_ratio = 0.0 + features = { 'route_id': route_id, 'stop_sequence': stop_sequence_value, @@ -318,7 +431,7 @@ def estimate_stop_times( eta_ts = datetime.fromtimestamp(vp_timestamp.timestamp() + eta_seconds, tz=timezone.utc) - predictions.append({ + prediction = { 'stop_id': stop['stop_id'], 'stop_sequence': stop_sequence_value, 'distance_to_stop_m': round(distance_m, 1), @@ -326,7 +439,17 @@ def estimate_stop_times( 'eta_minutes': round(eta_minutes, 2), 'eta_formatted': eta_formatted, 'eta_timestamp': eta_ts.isoformat(), - }) + } + + # Add shape-based metrics if available + if spatial_features.get('cross_track_error') is not None: + prediction['cross_track_error_m'] = round(spatial_features['cross_track_error'], 1) + if spatial_features.get('shape_progress') is not None: + prediction['shape_progress'] = round(spatial_features['shape_progress'], 3) + if spatial_features.get('shape_distance_to_stop') is not None: + prediction['shape_distance_to_stop_m'] = round(spatial_features['shape_distance_to_stop'], 1) + + predictions.append(prediction) except Exception as e: predictions.append({ @@ -348,5 +471,6 @@ def estimate_stop_times( 'model_key': model_key, 'model_type': actual_model_type, 'model_scope': model_scope, + 'shape_used': shape_available, 'predictions': predictions } diff --git a/eta_prediction/models/common/registry.py b/eta_prediction/models/common/registry.py index fa9bd02..f97c665 100644 --- a/eta_prediction/models/common/registry.py +++ b/eta_prediction/models/common/registry.py @@ -4,13 +4,43 @@ """ import json +import os import pickle from pathlib import Path -from typing import Dict, Any, Optional, List +from typing import Dict, Any, Optional, List, Union from datetime import datetime import pandas as pd +def _discover_project_root() -> Path: + """Try to locate the repository root by walking up for pyproject or .git.""" + path = Path(__file__).resolve() + for parent in [path.parent, *path.parents]: + if (parent / "pyproject.toml").exists() or (parent / ".git").exists(): + return parent + # Fallback to historical assumption (two levels up) + return path.parents[2] + + +PROJECT_ROOT = _discover_project_root() +DEFAULT_REGISTRY_DIR = PROJECT_ROOT / "models" / "trained" + + +def _find_existing_registry_dir() -> Path: + """ + Search upwards from the current working directory for an existing registry. + This allows running tools from nested folders (e.g., bytewax/) while still + reusing the canonical models/trained directory at the project root. + """ + cwd = Path.cwd().resolve() + search_roots = [cwd] + list(cwd.parents) + for root in search_roots: + candidate = root / "models" / "trained" + if (candidate / "registry.json").exists(): + return candidate.resolve() + return DEFAULT_REGISTRY_DIR.resolve() + + class ModelRegistry: """ Manages model artifacts and metadata in a structured directory. @@ -23,14 +53,26 @@ class ModelRegistry: └── registry.json # Index of all models """ - def __init__(self, base_dir: str = "models/trained"): + def __init__(self, base_dir: Union[str, Path, None] = None): """ Initialize registry. Args: base_dir: Base directory for model storage """ - self.base_dir = Path(base_dir) + env_dir = os.getenv("MODEL_REGISTRY_DIR") + configured_dir = base_dir if base_dir is not None else env_dir + + if configured_dir is not None: + base_path = Path(configured_dir).expanduser() + if not base_path.is_absolute(): + base_path = (PROJECT_ROOT / base_path).resolve() + else: + base_path = base_path.resolve() + else: + base_path = _find_existing_registry_dir() + + self.base_dir = base_path self.base_dir.mkdir(parents=True, exist_ok=True) self.registry_file = self.base_dir / "registry.json" @@ -79,7 +121,10 @@ def save_model(self, # Enrich metadata metadata['model_key'] = model_key metadata['saved_at'] = datetime.now().isoformat() - metadata['model_path'] = str(model_path) + # Store registry paths relative to repo root for portability + relative_model_path = os.path.relpath(model_path, PROJECT_ROOT) + relative_meta_path = os.path.relpath(meta_path, PROJECT_ROOT) + metadata['model_path'] = relative_model_path # Save metadata with open(meta_path, 'w') as f: @@ -87,8 +132,8 @@ def save_model(self, # Update registry self.registry[model_key] = { - 'model_path': str(model_path), - 'meta_path': str(meta_path), + 'model_path': relative_model_path, + 'meta_path': relative_meta_path, 'saved_at': metadata['saved_at'], 'model_type': metadata.get('model_type', 'unknown'), 'route_id': metadata.get('route_id'), # Track route scope @@ -114,6 +159,8 @@ def load_model(self, model_key: str) -> Any: raise KeyError(f"Model {model_key} not found in registry") model_path = Path(self.registry[model_key]['model_path']) + if not model_path.is_absolute(): + model_path = (PROJECT_ROOT / model_path).resolve() with open(model_path, 'rb') as f: model = pickle.load(f) @@ -134,6 +181,8 @@ def load_metadata(self, model_key: str) -> Dict[str, Any]: raise KeyError(f"Model {model_key} not found in registry") meta_path = Path(self.registry[model_key]['meta_path']) + if not meta_path.is_absolute(): + meta_path = (PROJECT_ROOT / meta_path).resolve() with open(meta_path, 'r') as f: metadata = json.load(f) @@ -376,4 +425,4 @@ def get_registry() -> ModelRegistry: global _registry if _registry is None: _registry = ModelRegistry() - return _registry \ No newline at end of file + return _registry