diff --git a/analytics/buckets.ts b/analytics/buckets.ts index 0bf3be82a..e03040cda 100644 --- a/analytics/buckets.ts +++ b/analytics/buckets.ts @@ -31,11 +31,20 @@ export const HOURLY_FALLBACK_DAYS = 14; export interface HourBucket { hits: number; + /** Fresh page loads only (onHydrationEnd), not in-app navigations — see hit.ts. */ + landings: number; pages: Record; } export interface DayBucket { hits: number; + /** + * Absent on any day written before this field existed — every "umami" day + * and any older "beacon" day. Not retrofittable (see + * analytics/notebooks/umami_backfill.py's scoping note); treat a missing + * value as unknown, not zero. + */ + landings?: number; pages: Record; /** `"beacon"` for anything this service counted; `"umami"` for backfilled history. */ source: string; @@ -121,6 +130,7 @@ export async function readDayFromHourly(store: HitStorage, site: string, day: st const results = await Promise.all(hourKeys(site, day).map((key) => store.getWithMeta(key))); let hits = 0; + let landings = 0; let found = false; const pages: Record = {}; @@ -131,10 +141,11 @@ export async function readDayFromHourly(store: HitStorage, site: string, day: st found = true; const bucket = JSON.parse(result.body) as HourBucket; hits += bucket.hits ?? 0; + landings += bucket.landings ?? 0; // 0 for hours written before this field existed mergePages(pages, bucket.pages ?? {}); } - return found ? { hits, pages: topPages(pages), source: "beacon" } : null; + return found ? { hits, landings, pages: topPages(pages), source: "beacon" } : null; } async function readRollup(store: HitStorage, site: string, month: string): Promise { diff --git a/analytics/hit.ts b/analytics/hit.ts index a1a5933ce..0bdca9400 100644 --- a/analytics/hit.ts +++ b/analytics/hit.ts @@ -23,6 +23,37 @@ const MAX_PATH_LENGTH = 200; const MAX_PAGES_PER_BUCKET = 200; // caps distinct paths tracked per hour bucket const MAX_CAS_ATTEMPTS = 3; +/** + * Self-identifying crawlers only — catches honest bots, not the chronic + * evasive crawler found in analytics/notebooks/05_traffic_bursts.ipynb (that + * one never announces itself; see the notebook's "Tier 2" note on why an IP/CIDR + * approach was parked instead of built speculatively). Nothing is stored: the + * UA is inspected per-request to decide whether to write, then discarded — + * same privacy posture as everything else here. + */ +const BOT_USER_AGENTS = [ + "googlebot", + "bingbot", + "ahrefsbot", + "semrushbot", + "mj12bot", + "gptbot", + "ccbot", + "claudebot", + "perplexitybot", + "yandexbot", + "petalbot", + "bytespider", +]; + +function isKnownBot(userAgent: string | undefined): boolean { + if (!userAgent) { + return false; + } + const lower = userAgent.toLowerCase(); + return BOT_USER_AGENTS.some((bot) => lower.includes(bot)); +} + // `vike dev` serves on 3000; 5173 covers a plain `vite dev` fallback. const ALLOWED_ORIGINS = ["https://www.fretchen.eu", "http://localhost:3000", "http://localhost:5173"]; @@ -45,6 +76,8 @@ function getCorsHeaders(origin?: string): Record { interface HourBucket { hits: number; + /** Fresh page loads only (onHydrationEnd), not in-app navigations — see hitTracker.ts. */ + landings: number; pages: Record; } @@ -71,14 +104,25 @@ function hourKey(site: string, now: Date = new Date()): string { * read on a CAS conflict; after MAX_CAS_ATTEMPTS gives up silently — a lost * count is fine, never worth failing the request over. */ -async function incrementHit(store: HitStorage, site: string, path: string): Promise { +async function incrementHit(store: HitStorage, site: string, path: string, landing: boolean): Promise { const key = hourKey(site); for (let attempt = 1; attempt <= MAX_CAS_ATTEMPTS; attempt++) { const existing = await store.getWithMeta(key); - const bucket: HourBucket = existing ? (JSON.parse(existing.body) as HourBucket) : { hits: 0, pages: {} }; + // Stored data can predate `landings` even though the type says it's + // always there — read it as partial and default explicitly, rather than + // asserting the parsed JSON matches HourBucket outright. + const parsed = existing ? (JSON.parse(existing.body) as Partial) : null; + const bucket: HourBucket = { + hits: parsed?.hits ?? 0, + landings: parsed?.landings ?? 0, + pages: parsed?.pages ?? {}, + }; bucket.hits += 1; + if (landing) { + bucket.landings += 1; + } if (bucket.pages[path] !== undefined || Object.keys(bucket.pages).length < MAX_PAGES_PER_BUCKET) { bucket.pages[path] = (bucket.pages[path] ?? 0) + 1; } @@ -109,6 +153,14 @@ export async function handleHit(event: ScalewayEvent, _context: unknown): Promis }; } + if (isKnownBot(event.headers?.["user-agent"] ?? event.headers?.["User-Agent"])) { + return { + statusCode: 400, + headers: corsHeaders, + body: JSON.stringify({ error: "Not tracked" }), + }; + } + if (!event.body) { return { statusCode: 400, @@ -137,7 +189,12 @@ export async function handleHit(event: ScalewayEvent, _context: unknown): Promis }; } - await incrementHit(defaultStorage, ALLOWED_SITE, path); + // Defaults to false rather than rejecting the request: an old cached client + // bundle without this field should keep counting hits, just without the + // landing/navigation split. + const landing = parsed.landing === true; + + await incrementHit(defaultStorage, ALLOWED_SITE, path, landing); return { statusCode: 204, headers: corsHeaders, body: "" }; } diff --git a/analytics/notebooks/04_umami_vs_beacon.ipynb b/analytics/notebooks/04_umami_vs_beacon.ipynb new file mode 100644 index 000000000..9f8a353e0 --- /dev/null +++ b/analytics/notebooks/04_umami_vs_beacon.ipynb @@ -0,0 +1,588 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "# Umami vs. beacon: does the new counter agree with the old one?\n", + "\n", + "The `analytics` hit counter (`POST /hit`) has been live for a few days. This notebook\n", + "compares it against a fresh Umami export covering the same window, to answer one\n", + "question: **is the beacon actually collecting what Umami collected?**\n", + "\n", + "Two things make this comparison narrower than \"the whole export\":\n", + "\n", + "- `rollup/{site}/{YYYY-MM}.json` for the current month may already hold days backfilled\n", + " from the *original* Umami migration (`03_umami_backfill.ipynb`) — those are old data,\n", + " not a live measurement, and comparing them against a fresh export would just be Umami\n", + " against itself. Every stored day carries a `source` (`\"umami\"` or `\"beacon\"`), so this\n", + " notebook uses that instead of guessing from dates.\n", + "- The beacon side reads two layers, mirroring `analytics/buckets.ts`: the monthly rollup\n", + " first, then a 24-hourly-bucket fallback for any day the weekly compaction cron hasn't\n", + " reached yet — recent days live only in `counts/` until then." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-01", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:21.512744Z", + "iopub.status.busy": "2026-08-15T12:31:21.512619Z", + "iopub.status.idle": "2026-08-15T12:31:22.227539Z", + "shell.execute_reply": "2026-08-15T12:31:22.226923Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from collections import Counter\n", + "from datetime import date, timedelta\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from dotenv import load_dotenv\n", + "from matplotlib.ticker import MaxNLocator\n", + "\n", + "from storage import S3Storage\n", + "from umami_backfill import read_pageviews\n", + "\n", + "load_dotenv() # searches upward — finds ../.env (analytics/.env)\n", + "\n", + "SITE = \"fretchen.eu\"\n", + "UMAMI_CSV = \"export/umami_recent.csv\"\n", + "\n", + "storage = S3Storage(\n", + " access_key=os.environ[\"SCW_ACCESS_KEY\"],\n", + " secret_key=os.environ[\"SCW_SECRET_KEY\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-02", + "metadata": {}, + "source": [ + "## Umami side\n", + "\n", + "Reuses `umami_backfill.read_pageviews` as-is — it already filters to pageviews on the\n", + "production hostname and normalises paths to the beacon's canonical form (locale stripped,\n", + "trailing slash), so a day's count here is directly comparable to a beacon day's count." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-03", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:22.230047Z", + "iopub.status.busy": "2026-08-15T12:31:22.229763Z", + "iopub.status.idle": "2026-08-15T12:31:22.234988Z", + "shell.execute_reply": "2026-08-15T12:31:22.234411Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "187 pageviews, 2026-08-01 .. 2026-08-15\n" + ] + } + ], + "source": [ + "pageviews = read_pageviews(UMAMI_CSV)\n", + "umami_by_day = Counter(day for day, _path in pageviews)\n", + "\n", + "all_days = sorted(umami_by_day)\n", + "print(f\"{len(pageviews)} pageviews, {all_days[0]} .. {all_days[-1]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-04", + "metadata": {}, + "source": [ + "## Beacon side\n", + "\n", + "Same two-layer read `GET /stats` does — rollup first, hourly fallback for anything not\n", + "compacted yet — reimplemented here rather than imported, since the original is TypeScript.\n", + "An old backfilled day keeps whatever `source` it was stored with, so it can never be\n", + "mistaken for a live measurement just because it happens to fall in this date range." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-05", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:22.236544Z", + "iopub.status.busy": "2026-08-15T12:31:22.236412Z", + "iopub.status.idle": "2026-08-15T12:31:22.240454Z", + "shell.execute_reply": "2026-08-15T12:31:22.239903Z" + } + }, + "outputs": [], + "source": [ + "def read_beacon_range(storage, site: str, days: list[str]) -> dict[str, dict]:\n", + " \"\"\"day -> {\"hits\": int, \"pages\": dict, \"source\": \"beacon\" | \"umami\" | \"none\"}.\n", + "\n", + " Mirrors analytics/buckets.ts's collectRange: the monthly rollup first (kept as-is,\n", + " whatever source it carries), then a computed-key 24-hour fallback — never a listed\n", + " prefix — for any day not yet compacted by the weekly rollup cron.\n", + " \"\"\"\n", + " rollups: dict[str, dict] = {}\n", + " result: dict[str, dict] = {}\n", + "\n", + " for day in days:\n", + " month = day[:7]\n", + " if month not in rollups:\n", + " rollups[month] = storage.read(f\"rollup/{site}/{month}.json\") or {\"days\": {}}\n", + "\n", + " entry = rollups[month][\"days\"].get(day)\n", + " if entry is not None:\n", + " result[day] = entry\n", + " continue\n", + "\n", + " hours = [storage.read(f\"counts/{site}/{day}T{h:02d}.json\") for h in range(24)]\n", + " present = [h for h in hours if h is not None]\n", + " if not present:\n", + " result[day] = {\"hits\": 0, \"pages\": {}, \"source\": \"none\"}\n", + " continue\n", + "\n", + " pages: dict[str, int] = {}\n", + " for h in present:\n", + " for path, count in h.get(\"pages\", {}).items():\n", + " pages[path] = pages.get(path, 0) + count\n", + " result[day] = {\n", + " \"hits\": sum(h.get(\"hits\", 0) for h in present),\n", + " \"pages\": pages,\n", + " \"source\": \"beacon\",\n", + " }\n", + "\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:22.241727Z", + "iopub.status.busy": "2026-08-15T12:31:22.241631Z", + "iopub.status.idle": "2026-08-15T12:31:40.821690Z", + "shell.execute_reply": "2026-08-15T12:31:40.819826Z" + } + }, + "outputs": [], + "source": [ + "def days_between(from_day: str, to_day: str) -> list[str]:\n", + " d0, d1 = date.fromisoformat(from_day), date.fromisoformat(to_day)\n", + " return [(d0 + timedelta(days=i)).isoformat() for i in range((d1 - d0).days + 1)]\n", + "\n", + "\n", + "window = days_between(all_days[0], all_days[-1])\n", + "beacon_by_day = read_beacon_range(storage, SITE, window)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "## Side by side\n", + "\n", + "`comparable` (`True`) marks a day the beacon itself actually measured. Everything else is\n", + "excluded from totals for one of two different reasons, kept distinguishable via\n", + "`beacon_source`: `\"umami\"` means the old backfill still owns that day (comparing it would\n", + "just be Umami against itself); `\"none\"` means the beacon has *zero* data — worth knowing\n", + "about, not the same thing as \"not applicable\"." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:40.825305Z", + "iopub.status.busy": "2026-08-15T12:31:40.825051Z", + "iopub.status.idle": "2026-08-15T12:31:40.840600Z", + "shell.execute_reply": "2026-08-15T12:31:40.839403Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
umami_hitsbeacon_hitsbeacon_sourcecomparable
day
2026-08-0188umamiFalse
2026-08-0266umamiFalse
2026-08-0377umamiFalse
2026-08-0433umamiFalse
2026-08-0599umamiFalse
2026-08-0622umamiFalse
2026-08-0744umamiFalse
2026-08-085656umamiFalse
2026-08-093939umamiFalse
2026-08-1043umamiFalse
2026-08-11615beaconTrue
2026-08-1220noneFalse
2026-08-1330noneFalse
2026-08-1487beaconTrue
2026-08-153032beaconTrue
\n", + "
" + ], + "text/plain": [ + " umami_hits beacon_hits beacon_source comparable\n", + "day \n", + "2026-08-01 8 8 umami False\n", + "2026-08-02 6 6 umami False\n", + "2026-08-03 7 7 umami False\n", + "2026-08-04 3 3 umami False\n", + "2026-08-05 9 9 umami False\n", + "2026-08-06 2 2 umami False\n", + "2026-08-07 4 4 umami False\n", + "2026-08-08 56 56 umami False\n", + "2026-08-09 39 39 umami False\n", + "2026-08-10 4 3 umami False\n", + "2026-08-11 6 15 beacon True\n", + "2026-08-12 2 0 none False\n", + "2026-08-13 3 0 none False\n", + "2026-08-14 8 7 beacon True\n", + "2026-08-15 30 32 beacon True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.DataFrame(\n", + " [\n", + " {\n", + " \"day\": day,\n", + " \"umami_hits\": umami_by_day.get(day, 0),\n", + " \"beacon_hits\": beacon_by_day[day][\"hits\"],\n", + " \"beacon_source\": beacon_by_day[day][\"source\"],\n", + " \"comparable\": beacon_by_day[day][\"source\"] == \"beacon\",\n", + " }\n", + " for day in window\n", + " ]\n", + ").set_index(\"day\")\n", + "\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-09", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:40.842607Z", + "iopub.status.busy": "2026-08-15T12:31:40.842436Z", + "iopub.status.idle": "2026-08-15T12:31:40.851087Z", + "shell.execute_reply": "2026-08-15T12:31:40.850476Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Comparable window: 2026-08-11 .. 2026-08-15 (3 of 5 days since go-live)\n", + "Umami total (comparable days): 44\n", + "Beacon total (comparable days): 54\n", + "\n", + "Days with Umami traffic but ZERO beacon hits — the beacon likely wasn't\n", + "running (e.g. a broken deploy), not a genuinely quiet day:\n", + " umami_hits beacon_hits\n", + "day \n", + "2026-08-12 2 0\n", + "2026-08-13 3 0\n", + "\n", + "Still owned by the old backfill (10 days) — comparing these would just be Umami against itself:\n", + " umami_hits beacon_source\n", + "day \n", + "2026-08-01 8 umami\n", + "2026-08-02 6 umami\n", + "2026-08-03 7 umami\n", + "2026-08-04 3 umami\n", + "2026-08-05 9 umami\n", + "2026-08-06 2 umami\n", + "2026-08-07 4 umami\n", + "2026-08-08 56 umami\n", + "2026-08-09 39 umami\n", + "2026-08-10 4 umami\n" + ] + } + ], + "source": [ + "# \"Since the beacon went live\" = every day not still owned by the old Umami backfill —\n", + "# that includes real beacon measurements AND the zero-hit gap where nothing was recorded\n", + "# at all. Excluding umami-sourced days here (not just filtering to comparable=True) is\n", + "# what lets the gap check below actually see an outage instead of filtering it away first.\n", + "since_beacon = df[df[\"beacon_source\"] != \"umami\"]\n", + "backfilled = df[df[\"beacon_source\"] == \"umami\"]\n", + "\n", + "comparable = df[df[\"comparable\"]]\n", + "span = f\"{comparable.index.min()} .. {comparable.index.max()}\"\n", + "print(f\"Comparable window: {span} ({len(comparable)} of {len(since_beacon)} days since go-live)\")\n", + "print(f\"Umami total (comparable days): {comparable['umami_hits'].sum()}\")\n", + "print(f\"Beacon total (comparable days): {comparable['beacon_hits'].sum()}\")\n", + "\n", + "gaps = since_beacon[(since_beacon[\"umami_hits\"] > 0) & (since_beacon[\"beacon_hits\"] == 0)]\n", + "if len(gaps):\n", + " print(\"\\nDays with Umami traffic but ZERO beacon hits — the beacon likely wasn't\")\n", + " print(\"running (e.g. a broken deploy), not a genuinely quiet day:\")\n", + " print(gaps[[\"umami_hits\", \"beacon_hits\"]])\n", + "else:\n", + " print(\"\\nNo days with Umami traffic and zero beacon hits.\")\n", + "\n", + "if len(backfilled):\n", + " print(\n", + " f\"\\nStill owned by the old backfill ({len(backfilled)} days) — comparing these\"\n", + " \" would just be Umami against itself:\"\n", + " )\n", + " print(backfilled[[\"umami_hits\", \"beacon_source\"]])" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "## Chart\n", + "\n", + "Two categorical series (Umami, beacon) in a fixed order — validated colours, not defaults:\n", + "`node scripts/validate_palette.js \"#2a78d6,#eb6834\" --mode light` passes every check\n", + "(worst adjacent CVD ΔE 24.7, normal-vision ΔE 33.6 — both well clear of the ΔE ≥ 8 / ≥ 15\n", + "targets). Plots every day since the beacon went live, including the zero-hit gap days —\n", + "an outage should read as a visible dent between real bars, not vanish because it failed\n", + "the \"comparable\" filter." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:31:40.852609Z", + "iopub.status.busy": "2026-08-15T12:31:40.852489Z", + "iopub.status.idle": "2026-08-15T12:31:40.981310Z", + "shell.execute_reply": "2026-08-15T12:31:40.980778Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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2bOnhISEeKzz008/yc8//yyhoaHSrl07admypeR3u3btkgIFCkj58uU9lsfExMj+/fvNsQwKChJ/NGLECKlYsaK89tprvi4KAAAA4Eg+DfTGjh0ry5Ytk44dO5og7qGHHpJnn31Wvv/+eylSpIikpaXJlVdeaZ5r06aNnDt3Tp566im555575JVXXrmkZas/Zrvkpa2T62Zr/f79+0uDBg3k7bff9lj+2WefyZ133inx8fEmEPRHVapUkbJly/q6GAAAAIBj+TTQ69evn/zf//2f6/HDDz9sWqg+//xzufXWW02L1LRp06R169auda699lrT6jd48GDTaoWs7dmzRwoWLGgCq+PHj0tSUpJUqFDBPGdZluzdu1ciIyOlRIkSHq87dOiQnDp1ytwvWbKklCtXLte2/cgjj0hYWBgfHQAAAODEMXqNGzf2eJyammr+Fi1a1PzVQM89yFPNmjUzf7V7Ii5syJAhcvfdd0vdunVNl9caNWpI9+7dTVfYOnXqSIcOHUwQ98ADD3i8btasWSbY1pt+TtoKt3LlylzZtnbdnDRpEh8fAAAA4NQxen///bfMnj1bYmNjZdWqVTJu3DjTYpeZ+fPnS0REhDRv3jzTdRITE83NptvOz/S4rl692gTJerwbNmwov/32m1mu9zdv3myeGzBggFxxxRXmNePHjzc329SpU0130Z07d5pWvIvZNgAAAACHB3raha9YsWKSkpJixuBt3brV/C1UqNB56/74448mEJw8ebKULl06023q8xMnTrzEJQ8cN910k6sl9PLLLzddXrUFTgMx1ahRI6levbps3LjRIxjT7pfaJfPEiRPSqVMneeyxx2Tbtm0eQXZOtw0AAIDAc2Jo/kqKWGrGWglUPp9eoVq1ajJ69Gh56aWXZP369aYlaPr06eett3btWunVq5fp9jdq1KgstzlmzBiTfdK+5fdununH12kQndEyzXxqW758udSsWdMEb9dff73cfvvtJvA7ePDgRW8bAAAAgMNb9Nxp0o569eqZVj13GgB27drVJGB54YUXLrgd7dqpNyfTY2UnS3F38uRJKVy48EVl3NSxkpooR1tPNUFOcHCwJCcnmy6bmgkVAAAAgH/zWYueZmjUVjp32vKmXfzck7T8/vvv0qVLFxPkvfzyyz4oqX/SLpHr1q0zXV7d/fLLL2bahYuhk5lrS2jf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bVX8vvocdc5/4qTIyMjADfQiIiLM33Pnznksj4uLkwIFCmT6ujFjxsjDDz/sepyWlianTp0yAeKFIl9cupYVDbT3799/wUoJUOcQiPieA3UOTsf3nH+16F2IXwd6GhiEhobKvn37PJbr4+rVq2cZINpBos0e4wff0iCPQA/UOTgZ33OgzsHp+J4LDH6djEVb7Tp27CiLFi1yLTt58qR8++230qNHD5+WDQAAAAD8lV+36KkpU6aYefTuvvtuadOmjcycOVMuv/xyGTRokK+LBgAAAAB+ya9b9FSzZs1k/fr1JiGLzqF34403yurVq8/rmgn/pp/XhAkT+NxAnYNj8T0H6hycju+5wBJk6WR1AAAAAADH8PsWPQAAAABA9hDoAQAAAIDDEOgBAAAAgMP4fdZN+N6OHTtk69atctlll0mrVq0kOPj86wMbNmww8xtq0pyWLVtKwYIFvdr2kSNH5IcffjCZVJs0aZLjdeAsu3fvlk2bNknp0qXliiuukJCQkPPW2bJli1kvKipKmjdvLkWKFPFq2ydOnJDvv/9eqlataupqesnJyWbfWu9q164ttWrVypX3BP924MABk/hL51xt27athIWFnbfOtm3bZOfOnaauaaIwrXveiI6ONtMC6XfolVdemeE6f/31l7mVLVtWWrRokWGdh7Pod8xvv/1m6pPWi4ySzP3999/mpr+pTZs2lRIlSni17bi4OPn666/Nb3KHDh2yXHf58uVy+vRpuemmm8zcxXAu/f1bs2aNmb6sXbt2GZ6r7dq1S7Zv3y7h4eHSuHFjKVOmjFfbjo+PlxUrVphtdu3a1eO5lJQUj6nSbK1bt5Zq1apdxDvCBWkyFiAzjzzyiFW4cGGrS5cuVrly5ay2bdtaMTExrudPnDhhtWjRwjzXp08fq0GDBlaZMmWs1atXZ3lQ9+/fb91yyy1WhQoVrMjISLOfnKwD53nmmWesQoUKWddcc41VqVIlq2nTptbx48ddz8fGxlpXX321Vbp0aat3797m+eLFi1tfffVVlts9duyYdeedd1rly5e3SpQoYQ0ePPi8dRYuXGjVqFHD1OmePXuaenfjjTdaiYmJl+S9wj9MmzbNKliwoNWxY0erevXqVp06dawDBw64no+Pj7d69Ohh6tl1111ntWrVyipatKipL1mJjo429Uy/H/V7UetSert27TL1uX79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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "UMAMI_COLOR = \"#2a78d6\" # validated categorical slot 1 (blue)\n", + "BEACON_COLOR = \"#eb6834\" # validated categorical slot 2 (orange)\n", + "\n", + "x = range(len(since_beacon))\n", + "width = 0.38\n", + "\n", + "fig, ax = plt.subplots(figsize=(9, 4.5))\n", + "ax.bar(\n", + " [i - width / 2 for i in x], since_beacon[\"umami_hits\"], width, label=\"Umami\", color=UMAMI_COLOR\n", + ")\n", + "ax.bar(\n", + " [i + width / 2 for i in x],\n", + " since_beacon[\"beacon_hits\"],\n", + " width,\n", + " label=\"Beacon (analytics)\",\n", + " color=BEACON_COLOR,\n", + ")\n", + "\n", + "ax.set_xticks(list(x))\n", + "ax.set_xticklabels([d[5:] for d in since_beacon.index]) # MM-DD\n", + "ax.set_ylabel(\"Pageviews\")\n", + "ax.set_title(\n", + " f\"Umami vs. beacon since go-live, {since_beacon.index.min()} .. {since_beacon.index.max()}\"\n", + ")\n", + "ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n", + "ax.spines[[\"top\", \"right\"]].set_visible(False)\n", + "ax.legend(frameon=False)\n", + "\n", + "for i, (umami_hits, beacon_hits) in enumerate(\n", + " zip(since_beacon[\"umami_hits\"], since_beacon[\"beacon_hits\"])\n", + "):\n", + " if umami_hits > 0 and beacon_hits == 0:\n", + " ax.annotate(\n", + " \"no beacon\\ndata\",\n", + " (i, umami_hits),\n", + " textcoords=\"offset points\",\n", + " xytext=(0, 6),\n", + " ha=\"center\",\n", + " fontsize=8,\n", + " color=BEACON_COLOR,\n", + " )\n", + "\n", + "fig.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (analytics-notebooks)", + "language": "python", + "name": "analytics-notebooks" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/analytics/notebooks/05_traffic_bursts.ipynb b/analytics/notebooks/05_traffic_bursts.ipynb new file mode 100644 index 000000000..8ba724838 --- /dev/null +++ b/analytics/notebooks/05_traffic_bursts.ipynb @@ -0,0 +1,858 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "# Traffic bursts: organic, self-generated, or a crawler?\n", + "\n", + "Daily pageviews aren't flat — some days are 5-10x the usual rate. This notebook asks *why*,\n", + "using signals the live beacon never recorded (it only ever stored `{hits, pages}` — no\n", + "browser, referrer, session or geography, by design). Those signals only exist for the\n", + "pre-migration era, in the full Umami export (Jan 1 – Aug 10, 2026). **A burst in the\n", + "beacon-only era after that date is invisible to this kind of analysis** unless the schema\n", + "changes — noted here as a real limit, not something this notebook works around.\n", + "\n", + "The approach: flag statistically unusual days, then for each one compute five signals that,\n", + "together, distinguish a crawl from a crowd from a single recurring visitor — rather than\n", + "reading raw CSV rows by hand." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-01", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.243777Z", + "iopub.status.busy": "2026-08-15T12:43:41.243646Z", + "iopub.status.idle": "2026-08-15T12:43:41.622042Z", + "shell.execute_reply": "2026-08-15T12:43:41.621341Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1612 pageviews, 2026-01-01 .. 2026-08-10, 214 distinct days\n" + ] + } + ], + "source": [ + "import re\n", + "from collections import Counter\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "\n", + "CSV_PATH = \"export/website_event.csv\"\n", + "BLOG_DIR = Path(\"../../website/blog\")\n", + "SITE_HOSTNAME = \"www.fretchen.eu\"\n", + "PAGEVIEW = \"1\" # Umami event_type: 1 = pageview, 2 = custom event\n", + "\n", + "df = pd.read_csv(CSV_PATH, dtype=str, keep_default_na=False)\n", + "pv = df[(df[\"event_type\"] == PAGEVIEW) & (df[\"hostname\"] == SITE_HOSTNAME)].copy()\n", + "pv[\"day\"] = pv[\"created_at\"].str[:10]\n", + "pv[\"hour\"] = pv[\"created_at\"].str[11:13].astype(int)\n", + "\n", + "span = f\"{pv['day'].min()} .. {pv['day'].max()}\"\n", + "print(f\"{len(pv)} pageviews, {span}, {pv['day'].nunique()} distinct days\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-02", + "metadata": {}, + "source": [ + "## 1. Burst detection\n", + "\n", + "Flags days above `median + 3 × MAD` (median absolute deviation) — a robust outlier rule:\n", + "unlike mean + stdev, a handful of huge spikes can't drag the baseline itself upward and hide\n", + "smaller bursts. `k=3` was chosen by checking it against three days already inspected by hand\n", + "(2026-08-08, 2026-03-26, 2026-03-15) — all three clear it, and the resulting set (23 days)\n", + "is short enough to read in full rather than a list so long it stops being useful." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-03", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.624016Z", + "iopub.status.busy": "2026-08-15T12:43:41.623868Z", + "iopub.status.idle": "2026-08-15T12:43:41.629928Z", + "shell.execute_reply": "2026-08-15T12:43:41.629373Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "baseline: median 5/day, MAD 3 -> threshold 14\n", + "23 burst days out of 214:\n", + "\n", + "day\n", + "2026-01-13 26\n", + "2026-01-25 26\n", + "2026-02-05 47\n", + "2026-02-08 35\n", + "2026-03-15 26\n", + "2026-03-21 23\n", + "2026-03-23 19\n", + "2026-03-24 18\n", + "2026-03-26 38\n", + "2026-04-16 22\n", + "2026-04-20 24\n", + "2026-04-24 22\n", + "2026-04-26 19\n", + "2026-05-05 16\n", + "2026-05-13 30\n", + "2026-05-14 15\n", + "2026-05-25 32\n", + "2026-06-03 21\n", + "2026-06-06 21\n", + "2026-06-15 17\n", + "2026-07-14 20\n", + "2026-08-08 56\n", + "2026-08-09 39\n" + ] + } + ], + "source": [ + "daily = pv.groupby(\"day\").size().rename(\"pageviews\").sort_index()\n", + "\n", + "median = daily.median()\n", + "mad = (daily - median).abs().median()\n", + "K = 3\n", + "threshold = median + K * mad\n", + "\n", + "bursts = daily[daily > threshold].sort_index()\n", + "\n", + "print(f\"baseline: median {median:.0f}/day, MAD {mad:.0f} -> threshold {threshold:.0f}\")\n", + "print(f\"{len(bursts)} burst days out of {len(daily)}:\\n\")\n", + "print(bursts.to_string())" + ] + }, + { + "cell_type": "markdown", + "id": "cell-04", + "metadata": {}, + "source": [ + "## 2. Blog publish dates\n", + "\n", + "Mirrors the two fields `website/utils/blogLoader.ts` reads from frontmatter at build time —\n", + "reimplemented here rather than imported, since this is Python. Posts without a `tokenID`\n", + "aren't reachable at `/blog/{id}/` at all, so they're skipped rather than forced in." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-05", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.631378Z", + "iopub.status.busy": "2026-08-15T12:43:41.631244Z", + "iopub.status.idle": "2026-08-15T12:43:41.640561Z", + "shell.execute_reply": "2026-08-15T12:43:41.639993Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18 posts with a tokenID and a publishing_date\n" + ] + } + ], + "source": [ + "FRONTMATTER = re.compile(r\"^---\\n(.*?)\\n---\", re.DOTALL)\n", + "TOKEN_ID = re.compile(r\"^tokenID:\\s*(\\S+)\", re.MULTILINE)\n", + "PUBLISH_DATE = re.compile(r\"^publishing_date:\\s*[\\\"']?(\\d{4}-\\d{2}-\\d{2})[\\\"']?\", re.MULTILINE)\n", + "\n", + "\n", + "def load_publish_dates(blog_dir: Path) -> dict[str, str]:\n", + " \"\"\"tokenID (as it appears in /blog/{id}/) -> publishing_date, for posts that have both.\"\"\"\n", + " dates: dict[str, str] = {}\n", + " for path in sorted(blog_dir.glob(\"*.mdx\")) + sorted(blog_dir.glob(\"*.md\")):\n", + " match = FRONTMATTER.match(path.read_text())\n", + " if not match:\n", + " continue\n", + " frontmatter = match.group(1)\n", + " token_id = TOKEN_ID.search(frontmatter)\n", + " publish_date = PUBLISH_DATE.search(frontmatter)\n", + " if token_id and publish_date:\n", + " dates[token_id.group(1)] = publish_date.group(1)\n", + " return dates\n", + "\n", + "\n", + "publish_dates = load_publish_dates(BLOG_DIR)\n", + "print(f\"{len(publish_dates)} posts with a tokenID and a publishing_date\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "## 3. Per-burst diagnostics\n", + "\n", + "Five signals, read together rather than any one alone:\n", + "\n", + "- **session ratio** (distinct sessions ÷ pageviews) — near 1.0 means every view is a new\n", + " session with no repeat pages (bot-shaped); well below 1.0 means people browsing multiple\n", + " pages in one visit.\n", + "- **top fingerprint share** — the largest `(browser, os, device)` combination's share of the\n", + " day. One combination dominating means a single actor, whichever kind; several with no\n", + " majority means many distinct real visitors.\n", + "- **direct/no-referrer share** — high on its own is ambiguous (could be a dark-social share\n", + " or a bot), which is exactly why it's read alongside the other four, never alone.\n", + "- **top page share**, and whether that page is a `/blog/{id}/` post — if so, how many days\n", + " separate the burst from that post's `publishing_date` (0-3 days is a real \"the post did\n", + " this\" candidate; months apart is not).\n", + "- **hour spread** — the number of distinct UTC hours with at least one hit, out of 24. Tight\n", + " clustering (a handful of hours) points at a single session or a short-lived campaign;\n", + " spread across nearly all 24 suggests either a slow crawl or genuinely global organic reach." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.641896Z", + "iopub.status.busy": "2026-08-15T12:43:41.641810Z", + "iopub.status.idle": "2026-08-15T12:43:41.663317Z", + "shell.execute_reply": "2026-08-15T12:43:41.662724Z" + } + }, + "outputs": [], + "source": [ + "BLOG_PATH = re.compile(r\"^/(?:[a-z]{2}/)?blog/(\\d+)/?$\")\n", + "\n", + "\n", + "def diagnose(day: str) -> dict:\n", + " rows = pv[pv[\"day\"] == day]\n", + " n = len(rows)\n", + "\n", + " fingerprints = Counter(zip(rows[\"browser\"], rows[\"os\"], rows[\"device\"]))\n", + " top_fp, top_fp_count = fingerprints.most_common(1)[0]\n", + "\n", + " referrers = Counter(d if d else \"(direct/none)\" for d in rows[\"referrer_domain\"])\n", + " direct_count = referrers.get(\"(direct/none)\", 0)\n", + "\n", + " paths = Counter(rows[\"url_path\"])\n", + " top_path, top_path_count = paths.most_common(1)[0]\n", + "\n", + " blog_match = BLOG_PATH.match(top_path)\n", + " days_since_publish = None\n", + " if blog_match and blog_match.group(1) in publish_dates:\n", + " published = pd.Timestamp(publish_dates[blog_match.group(1)])\n", + " days_since_publish = (pd.Timestamp(day) - published).days\n", + "\n", + " return {\n", + " \"day\": day,\n", + " \"pageviews\": n,\n", + " \"sessions\": rows[\"session_id\"].nunique(),\n", + " \"session_ratio\": round(rows[\"session_id\"].nunique() / n, 2),\n", + " \"top_fingerprint\": top_fp,\n", + " \"top_fp_share\": round(top_fp_count / n, 2),\n", + " \"direct_share\": round(direct_count / n, 2),\n", + " \"top_referrers\": referrers.most_common(3),\n", + " \"countries\": Counter(rows[\"country\"]).most_common(5),\n", + " \"top_path\": top_path,\n", + " \"top_path_share\": round(top_path_count / n, 2),\n", + " \"days_since_publish\": days_since_publish,\n", + " \"distinct_hours\": rows[\"hour\"].nunique(),\n", + " }\n", + "\n", + "\n", + "diagnostics = [diagnose(day) for day in bursts.index]" + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "## 4. A heuristic hint, not a verdict\n", + "\n", + "One line per burst, built from the numbers directly above it — meant to save re-deriving the\n", + "same comparison by hand each time, not to replace your own read of the underlying table." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-09", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.664873Z", + "iopub.status.busy": "2026-08-15T12:43:41.664768Z", + "iopub.status.idle": "2026-08-15T12:43:41.669203Z", + "shell.execute_reply": "2026-08-15T12:43:41.668843Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-01-13 ( 26 views) ambiguous — read the table below\n", + " sessions=4/26 (ratio 0.15) top_fingerprint=('chrome', 'Mac OS', 'laptop') (62%) direct=77%\n", + " top_path=/ (77%)\n", + " countries=[('US', 18), ('LT', 7), ('ES', 1)]\n", + " hours_active=1/24\n", + "\n", + "2026-01-25 ( 26 views) ambiguous — read the table below\n", + " sessions=11/26 (ratio 0.42) top_fingerprint=('chrome', 'Mac OS', 'laptop') (65%) direct=77%\n", + " top_path=/ (69%)\n", + " countries=[('US', 15), ('GB', 8), ('SG', 2), ('DE', 1)]\n", + " hours_active=5/24\n", + "\n", + "2026-02-05 ( 47 views) ambiguous — read the table below\n", + " sessions=16/47 (ratio 0.34) top_fingerprint=('chrome', 'Mac OS', 'laptop') (57%) direct=77%\n", + " top_path=/ (66%)\n", + " countries=[('US', 26), ('FR', 8), ('RO', 5), ('SG', 4), ('DE', 4)]\n", + " hours_active=6/24\n", + "\n", + "2026-02-08 ( 35 views) ambiguous — read the table below\n", + " sessions=12/35 (ratio 0.34) top_fingerprint=('safari', 'Mac OS', 'laptop') (60%) direct=71%\n", + " top_path=/ (49%)\n", + " countries=[('DE', 26), ('SG', 3), ('US', 3), ('FR', 3)]\n", + " hours_active=8/24\n", + "\n", + "2026-03-15 ( 26 views) looks organic (diverse visitors, real browsing)\n", + " sessions=14/26 (ratio 0.54) top_fingerprint=('chrome', 'Mac OS', 'laptop') (35%) direct=65%\n", + " top_path=/blog/25/ (31%)\n", + " countries=[('DE', 10), ('DO', 8), ('SG', 5), ('US', 2), ('IE', 1)]\n", + " hours_active=11/24\n", + "\n", + "2026-03-21 ( 23 views) ambiguous — read the table below\n", + " sessions=22/23 (ratio 0.96) top_fingerprint=('chrome', 'Android OS', 'mobile') (43%) direct=100%\n", + " top_path=/ (17%)\n", + " countries=[('CN', 14), ('SG', 7), ('DO', 2)]\n", + " hours_active=10/24\n", + "\n", + "2026-03-23 ( 19 views) looks organic (diverse visitors, real browsing)\n", + " sessions=12/19 (ratio 0.63) top_fingerprint=('chrome', 'Mac OS', 'laptop') (37%) direct=68%\n", + " top_path=/ (37%)\n", + " countries=[('DE', 7), ('CN', 5), ('SG', 4), ('DO', 2), ('US', 1)]\n", + " hours_active=7/24\n", + "\n", + "2026-03-24 ( 18 views) ambiguous — read the table below\n", + " sessions=18/18 (ratio 1.0) top_fingerprint=('chrome', 'Android OS', 'mobile') (50%) direct=100%\n", + " top_path=/de/blog/24/ (11%), published 294d earlier\n", + " countries=[('CN', 15), ('SG', 3)]\n", + " hours_active=3/24\n", + "\n", + "2026-03-26 ( 38 views) looks organic (diverse visitors, real browsing)\n", + " sessions=21/38 (ratio 0.55) top_fingerprint=('chrome', 'Mac OS', 'laptop') (32%) direct=87%\n", + " top_path=/ (58%)\n", + " countries=[('US', 13), ('DO', 9), ('DE', 8), ('CN', 6), ('SG', 2)]\n", + " hours_active=9/24\n", + "\n", + "2026-04-16 ( 22 views) looks organic (diverse visitors, real browsing)\n", + " sessions=13/22 (ratio 0.59) top_fingerprint=('chrome', 'Mac OS', 'laptop') (36%) direct=95%\n", + " top_path=/ (68%)\n", + " countries=[('US', 11), ('DO', 4), ('NL', 3), ('CY', 2), ('CA', 1)]\n", + " hours_active=8/24\n", + "\n", + "2026-04-20 ( 24 views) looks organic (diverse visitors, real browsing)\n", + " sessions=12/24 (ratio 0.5) top_fingerprint=('chrome', 'Mac OS', 'desktop') (38%) direct=58%\n", + " top_path=/ (33%)\n", + " countries=[('DE', 8), ('SG', 4), ('CH', 4), ('GB', 4), ('US', 2)]\n", + " hours_active=7/24\n", + "\n", + "2026-04-24 ( 22 views) ambiguous — read the table below\n", + " sessions=12/22 (ratio 0.55) top_fingerprint=('chrome', 'Mac OS', 'laptop') (55%) direct=100%\n", + " top_path=/ (73%)\n", + " countries=[('US', 12), ('CY', 5), ('PL', 3), ('SG', 1), ('DE', 1)]\n", + " hours_active=10/24\n", + "\n", + "2026-04-26 ( 19 views) ambiguous — read the table below\n", + " sessions=4/19 (ratio 0.21) top_fingerprint=('ios', 'iOS', 'mobile') (63%) direct=47%\n", + " top_path=/blog/22/ (26%)\n", + " countries=[('KR', 13), ('DO', 5), ('SG', 1)]\n", + " hours_active=4/24\n", + "\n", + "2026-05-05 ( 16 views) looks organic (diverse visitors, real browsing)\n", + " sessions=11/16 (ratio 0.69) top_fingerprint=('chrome', 'Windows 10', 'laptop') (38%) direct=62%\n", + " top_path=/ (50%)\n", + " countries=[('US', 8), ('GB', 3), ('DE', 2), ('SG', 1), ('VN', 1)]\n", + " hours_active=6/24\n", + "\n", + "2026-05-13 ( 30 views) looks organic (diverse visitors, real browsing)\n", + " sessions=17/30 (ratio 0.57) top_fingerprint=('chrome', 'Windows 10', 'laptop') (40%) direct=73%\n", + " top_path=/ (30%)\n", + " countries=[('KR', 12), ('DE', 9), ('SG', 4), ('US', 1), ('VN', 1)]\n", + " hours_active=9/24\n", + "\n", + "2026-05-14 ( 15 views) ambiguous — read the table below\n", + " sessions=14/15 (ratio 0.93) top_fingerprint=('chrome', 'Android OS', 'mobile') (33%) direct=100%\n", + " top_path=/ (40%)\n", + " countries=[('CN', 5), ('KR', 4), ('JP', 2), ('TR', 1), ('US', 1)]\n", + " hours_active=9/24\n", + "\n", + "2026-05-25 ( 32 views) ambiguous — read the table below\n", + " sessions=11/32 (ratio 0.34) top_fingerprint=('chrome', 'Mac OS', 'laptop') (59%) direct=78%\n", + " top_path=/ (78%)\n", + " countries=[('US', 18), ('DE', 14)]\n", + " hours_active=4/24\n", + "\n", + "2026-06-03 ( 21 views) ambiguous — read the table below\n", + " sessions=8/21 (ratio 0.38) top_fingerprint=('safari', 'Mac OS', 'laptop') (67%) direct=33%\n", + " top_path=/blog/27/ (14%)\n", + " countries=[('DE', 14), ('CN', 6), ('GB', 1)]\n", + " hours_active=6/24\n", + "\n", + "2026-06-06 ( 21 views) looks organic (diverse visitors, real browsing)\n", + " sessions=16/21 (ratio 0.76) top_fingerprint=('chrome', 'Linux', 'laptop') (43%) direct=100%\n", + " top_path=/ (62%)\n", + " countries=[('US', 10), ('DE', 6), ('SG', 3), ('IT', 1), ('GB', 1)]\n", + " hours_active=9/24\n", + "\n", + "2026-06-15 ( 17 views) ambiguous — read the table below\n", + " sessions=17/17 (ratio 1.0) top_fingerprint=('chrome', 'Windows 10', 'laptop') (59%) direct=100%\n", + " top_path=/ (41%)\n", + " countries=[('SG', 8), ('CA', 2), ('VN', 1), ('AE', 1), ('US', 1)]\n", + " hours_active=9/24\n", + "\n", + "2026-07-14 ( 20 views) looks organic (diverse visitors, real browsing)\n", + " sessions=13/20 (ratio 0.65) top_fingerprint=('ios', 'iOS', 'mobile') (35%) direct=85%\n", + " top_path=/ (50%)\n", + " countries=[('DE', 10), ('US', 8), ('SG', 1), ('TR', 1)]\n", + " hours_active=7/24\n", + "\n", + "2026-08-08 ( 56 views) looks like a crawl (one fingerprint, one-shot sessions, no referrers)\n", + " sessions=56/56 (ratio 1.0) top_fingerprint=('chrome', 'Linux', 'laptop') (95%) direct=100%\n", + " top_path=/blog/16/ (4%)\n", + " countries=[('US', 29), ('GB', 11), ('JP', 6), ('SG', 3), ('DE', 3)]\n", + " hours_active=7/24\n", + "\n", + "2026-08-09 ( 39 views) looks like a crawl (one fingerprint, one-shot sessions, no referrers)\n", + " sessions=38/39 (ratio 0.97) top_fingerprint=('chrome', 'Linux', 'laptop') (92%) direct=100%\n", + " top_path=/quantum/qml/1/qml102 (5%)\n", + " countries=[('US', 23), ('SG', 9), ('GB', 4), ('JP', 2), ('DM', 1)]\n", + " hours_active=15/24\n", + "\n" + ] + } + ], + "source": [ + "def hint(d: dict) -> str:\n", + " if d[\"session_ratio\"] >= 0.9 and d[\"top_fp_share\"] >= 0.7 and d[\"direct_share\"] >= 0.8:\n", + " return \"looks like a crawl (one fingerprint, one-shot sessions, no referrers)\"\n", + " if d[\"top_fp_share\"] >= 0.7 and d[\"session_ratio\"] < 0.7:\n", + " return \"looks like a single visitor browsing repeatedly (maybe you)\"\n", + " if d[\"session_ratio\"] < 0.8 and d[\"top_fp_share\"] < 0.5:\n", + " note = \"\"\n", + " if d[\"days_since_publish\"] is not None and 0 <= d[\"days_since_publish\"] <= 3:\n", + " note = f\" — {d['days_since_publish']}d after {d['top_path']} published\"\n", + " return \"looks organic (diverse visitors, real browsing)\" + note\n", + " return \"ambiguous — read the table below\"\n", + "\n", + "\n", + "for d in diagnostics:\n", + " print(f\"{d['day']} ({d['pageviews']:>3} views) {hint(d)}\")\n", + " print(\n", + " f\" sessions={d['sessions']}/{d['pageviews']} (ratio {d['session_ratio']})\"\n", + " f\" top_fingerprint={d['top_fingerprint']} ({d['top_fp_share']:.0%})\"\n", + " f\" direct={d['direct_share']:.0%}\"\n", + " )\n", + " print(\n", + " f\" top_path={d['top_path']} ({d['top_path_share']:.0%})\"\n", + " + (\n", + " f\", published {d['days_since_publish']}d earlier\"\n", + " if d[\"days_since_publish\"] is not None\n", + " else \"\"\n", + " )\n", + " )\n", + " print(f\" countries={d['countries']}\")\n", + " print(f\" hours_active={d['distinct_hours']}/24\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "## 5. \"Is it me?\" — a baseline check, not a lookup\n", + "\n", + "Umami has no owner-identifying field, on purpose — this can't be answered directly. The best\n", + "available proxy: a fingerprint that's disproportionate on burst days *and* also shows up as a\n", + "small, steady background rate on ordinary (non-burst) days is consistent with one recurring\n", + "person, most plausibly you, rather than a crawler that only appears in bursts. This computes\n", + "that comparison for each burst's dominant fingerprint; recognizing your own setup in it is\n", + "up to you." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.670768Z", + "iopub.status.busy": "2026-08-15T12:43:41.670675Z", + "iopub.status.idle": "2026-08-15T12:43:41.693666Z", + "shell.execute_reply": "2026-08-15T12:43:41.693253Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-01-13: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-01-25: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-02-05: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-02-08: ('safari', 'Mac OS', 'laptop') also appears on 24/191 quiet days (54 hits, 2.2/day when present)\n", + "2026-03-15: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-03-21: ('chrome', 'Android OS', 'mobile') also appears on 17/191 quiet days (51 hits, 3.0/day when present)\n", + "2026-03-23: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-03-24: ('chrome', 'Android OS', 'mobile') also appears on 17/191 quiet days (51 hits, 3.0/day when present)\n", + "2026-03-26: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-04-16: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-04-20: ('chrome', 'Mac OS', 'desktop') also appears on 4/191 quiet days (6 hits, 1.5/day when present)\n", + "2026-04-24: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-04-26: ('ios', 'iOS', 'mobile') also appears on 15/191 quiet days (29 hits, 1.9/day when present)\n", + "2026-05-05: ('chrome', 'Windows 10', 'laptop') also appears on 168/191 quiet days (598 hits, 3.6/day when present)\n", + "2026-05-13: ('chrome', 'Windows 10', 'laptop') also appears on 168/191 quiet days (598 hits, 3.6/day when present)\n", + "2026-05-14: ('chrome', 'Android OS', 'mobile') also appears on 17/191 quiet days (51 hits, 3.0/day when present)\n", + "2026-05-25: ('chrome', 'Mac OS', 'laptop') also appears on 36/191 quiet days (63 hits, 1.8/day when present)\n", + "2026-06-03: ('safari', 'Mac OS', 'laptop') also appears on 24/191 quiet days (54 hits, 2.2/day when present)\n", + "2026-06-06: ('chrome', 'Linux', 'laptop') also appears on 47/191 quiet days (101 hits, 2.1/day when present)\n", + "2026-06-15: ('chrome', 'Windows 10', 'laptop') also appears on 168/191 quiet days (598 hits, 3.6/day when present)\n", + "2026-07-14: ('ios', 'iOS', 'mobile') also appears on 15/191 quiet days (29 hits, 1.9/day when present)\n", + "2026-08-08: ('chrome', 'Linux', 'laptop') also appears on 47/191 quiet days (101 hits, 2.1/day when present)\n", + "2026-08-09: ('chrome', 'Linux', 'laptop') also appears on 47/191 quiet days (101 hits, 2.1/day when present)\n" + ] + } + ], + "source": [ + "quiet_days = daily.index.difference(bursts.index)\n", + "quiet = pv[pv[\"day\"].isin(quiet_days)]\n", + "\n", + "for d in diagnostics:\n", + " fp = d[\"top_fingerprint\"]\n", + " quiet_hits = quiet[\n", + " (quiet[\"browser\"] == fp[0]) & (quiet[\"os\"] == fp[1]) & (quiet[\"device\"] == fp[2])\n", + " ]\n", + " quiet_days_seen = quiet_hits[\"day\"].nunique()\n", + " rate = len(quiet_hits) / max(quiet_days_seen, 1)\n", + " print(\n", + " f\"{d['day']}: {fp} also appears on {quiet_days_seen}/{len(quiet_days)} quiet days\"\n", + " f\" ({len(quiet_hits)} hits, {rate:.1f}/day when present)\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "## 6. The chronic crawler the baseline check missed\n", + "\n", + "Section 5's baseline check has a real gap: it flags a fingerprint as \"steady background,\n", + "possibly you\" using only its presence-rate across quiet days — it never checks whether that\n", + "presence is *one* visitor or *many distinct people* who happen to share a common\n", + "fingerprint. `chrome/Windows 10/laptop` cleared that check (168/191 quiet days) purely\n", + "because Chrome-on-Windows-laptop is the single most common desktop configuration on the\n", + "internet — not because it's one recurring device. Checking session and country diversity\n", + "immediately rules that out: 709 pageviews, **699 distinct sessions**, 31 countries, 1.01\n", + "views/session. That's hundreds of near-strangers sharing a coarse fingerprint, not a person.\n", + "\n", + "Screen resolution is the signal that actually separates them. A `screen` field this coarse\n", + "fingerprint doesn't use, but real hardware still constrains it — most people's laptops cluster\n", + "around a small set of genuine resolutions, so a **very sharp resolution spike inside an\n", + "already-suspicious fingerprint** is worth checking before ruling a fingerprint out." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.695415Z", + "iopub.status.busy": "2026-08-15T12:43:41.695309Z", + "iopub.status.idle": "2026-08-15T12:43:41.708084Z", + "shell.execute_reply": "2026-08-15T12:43:41.707657Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fingerprint n top resolution (share)\n", + "chrome/Android OS/mobile 89 375x812 (81%)\n", + "chrome/Linux/laptop 207 1920x1080 (55%)\n", + "chrome/Mac OS/laptop 224 1920x1080 (48%)\n", + "chrome/Windows 10/laptop 709 1280x1200 (80%)\n", + "ios/iOS/mobile 104 480x720 (41%)\n", + "safari/Mac OS/laptop 129 1440x900 (71%)\n" + ] + } + ], + "source": [ + "top_fingerprints = (\n", + " pv.groupby([\"browser\", \"os\", \"device\"])\n", + " .filter(lambda g: len(g) > 50)\n", + " .groupby([\"browser\", \"os\", \"device\"])\n", + ")\n", + "\n", + "print(f\"{'fingerprint':<32} {'n':>5} top resolution (share)\")\n", + "for (browser, os_, device), grp in top_fingerprints:\n", + " counts = grp[\"screen\"].value_counts()\n", + " top_res, top_share = counts.index[0], counts.iloc[0] / len(grp)\n", + " label = f\"{browser}/{os_}/{device}\"\n", + " print(f\"{label:<32} {len(grp):>5} {top_res} ({top_share:.0%})\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "`chrome/Windows 10/laptop` is the only one where a single resolution clears ~75% — the\n", + "others top out around 50-70% with genuinely common real-laptop resolutions (1920×1080,\n", + "1440×900). `1280×1200` is not a common real Windows-laptop resolution; it's a legacy\n", + "aspect ratio more associated with default headless-browser viewports than actual hardware.\n", + "Profiling that one slice on its own:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-15", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.709518Z", + "iopub.status.busy": "2026-08-15T12:43:41.709419Z", + "iopub.status.idle": "2026-08-15T12:43:41.715928Z", + "shell.execute_reply": "2026-08-15T12:43:41.715463Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "568 pageviews (35.2% of all traffic)\n", + " distinct sessions: 568 (100% of rows are their own session)\n", + " no-referrer share: 100%\n", + " countries: 17\n", + " distinct days active: 179 of 214 observed\n", + " distinct pages touched: 182\n", + "\n", + "Top pages it hits — broad, indiscriminate coverage rather than one target:\n", + "url_path\n", + "/de/ 25\n", + "/amo/11 17\n", + "/de/quantum/amo/ 16\n", + "/amo/10 16\n", + "/amo/13/ 13\n", + "/quantum/ 13\n", + "/quantum/basics/ 12\n", + "/quantum 11\n", + "/de/quantum/amo 11\n", + "/blog/15/ 11\n" + ] + } + ], + "source": [ + "crawler_fp = pv[\n", + " (pv[\"browser\"] == \"chrome\")\n", + " & (pv[\"os\"] == \"Windows 10\")\n", + " & (pv[\"device\"] == \"laptop\")\n", + " & (pv[\"screen\"] == \"1280x1200\")\n", + "]\n", + "\n", + "print(f\"{len(crawler_fp)} pageviews ({len(crawler_fp) / len(pv):.1%} of all traffic)\")\n", + "print(\n", + " f\" distinct sessions: {crawler_fp['session_id'].nunique()} \"\n", + " f\"({crawler_fp['session_id'].nunique() / len(crawler_fp):.0%} of rows are their own session)\"\n", + ")\n", + "print(f\" no-referrer share: {(crawler_fp['referrer_domain'] == '').mean():.0%}\")\n", + "print(f\" countries: {crawler_fp['country'].nunique()}\")\n", + "print(f\" distinct days active: {crawler_fp['day'].nunique()} of {len(daily)} observed\")\n", + "print(f\" distinct pages touched: {crawler_fp['url_path'].nunique()}\")\n", + "print()\n", + "print(\"Top pages it hits — broad, indiscriminate coverage rather than one target:\")\n", + "print(crawler_fp[\"url_path\"].value_counts().head(10).to_string())" + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "Every signal from the acute Aug 8-9 spike is here too, just spread thin instead of\n", + "concentrated: one-shot sessions (nearly 100%), zero referrers, wide geography, hundreds\n", + "of distinct pages touched with no single target. The difference is duration — this one has\n", + "been running at a low, steady rate across nearly the entire observed history (179 of ~214\n", + "days), which is exactly the shape that fooled section 5's presence-only baseline check." + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "### Combined crawler share\n", + "\n", + "The acute spike (section 3, `chrome/Linux/laptop`, one-shot no-referrer sessions,\n", + "spanning 3+ countries same-day) and this chronic one don't overlap — different `os`\n", + "values — so their shares add directly." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.717352Z", + "iopub.status.busy": "2026-08-15T12:43:41.717264Z", + "iopub.status.idle": "2026-08-15T12:43:41.724626Z", + "shell.execute_reply": "2026-08-15T12:43:41.724074Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Acute spike (Aug 8-9, multi-country same-day): 87 (5.4%)\n", + "Chronic crawler (chrome/Win10/1280x1200): 568 (35.2%)\n", + "Combined: 655 (40.6%)\n" + ] + } + ], + "source": [ + "session_sizes = pv.groupby(\"session_id\").size()\n", + "pv[\"session_size\"] = pv[\"session_id\"].map(session_sizes)\n", + "\n", + "linux_fp = pv[\n", + " (pv[\"browser\"] == \"chrome\")\n", + " & (pv[\"os\"] == \"Linux\")\n", + " & (pv[\"device\"] == \"laptop\")\n", + " & (pv[\"session_size\"] == 1)\n", + " & (pv[\"referrer_domain\"] == \"\")\n", + "]\n", + "multi_country_days = linux_fp.groupby(\"day\")[\"country\"].nunique().loc[lambda s: s >= 3].index\n", + "acute_spike = linux_fp[linux_fp[\"day\"].isin(multi_country_days)]\n", + "\n", + "combined = len(acute_spike) + len(crawler_fp)\n", + "acute_share = len(acute_spike) / len(pv)\n", + "crawler_share = len(crawler_fp) / len(pv)\n", + "combined_share = combined / len(pv)\n", + "print(f\"Acute spike (Aug 8-9, multi-country same-day): {len(acute_spike):>4} ({acute_share:.1%})\")\n", + "print(\n", + " f\"Chronic crawler (chrome/Win10/1280x1200): {len(crawler_fp):>4} ({crawler_share:.1%})\"\n", + ")\n", + "print(f\"Combined: {combined:>4} ({combined_share:.1%})\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "## Chart\n", + "\n", + "Daily pageviews with burst days marked — a status color (not an arbitrary red), validated\n", + "the same way as the last notebook's series colors." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell-20", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T12:43:41.725886Z", + "iopub.status.busy": "2026-08-15T12:43:41.725801Z", + "iopub.status.idle": "2026-08-15T12:43:42.249104Z", + "shell.execute_reply": "2026-08-15T12:43:42.248450Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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C13lItRjqOZhBTylOb00T7R+wa9FmLRgoR4r3377bXOxrVPdNZgTd2qy5pAGEqMpOHQmmp6T6bFPg8xxZcmSRfr06WNy32vAR2eCADY6C0hzPh89elTuuece0z/paGdbnEFnC+loVUc6e8PxfAwBSIuZwHfmz59v5cmTx2rfvr1111136bAu69lnn7WuX7/ucv1t27ZZYWFhVr169azo6Og0316kD7Gxsdbjjz9uVahQwerQoYOVO3duK1++fNa8efPirTtjxgyz3uHDh63SpUtbEyZM8Mk2w//MnTvX9E/ahqpXr26FhIRYffr0sWJiYlyuf/v2bbPu008/nebbisA0e/ZsexusWrWqaYP9+vWzt8FRo0ZZGTNmtIYOHer0uFWrVlmXL1/20VbD3926dctq06aNVblyZXP+lTNnTqtgwYLWwoULE3yMnn9FRERYO3fuTNNtRfp39uxZq2LFilbDhg2thx56yMqUKZM5ptra0rFjx6wqVapYGTJksCZOnGidPn3amjZtmpU/f35r3759vt58+LFvv/3WHCP1nF/7M21DgwYNsm7evBlv3ZMnT1qZM2e2Pv30U59sK/yT9jd33nmn1ahRI+vBBx8051R33323tXv3bvs67dq1s8qUKWMdOHDA3u60fzp06JAPtxypjUChD129etXKmzev9fvvv9uXff3111bWrFnNH6veH1f//v0JEiJJX331lTkJtQWcIyMjzUmEnkB89tlnTuvOnDnTKlSoEEFCONEgiwaYV69ebV/25ZdfWuHh4VbTpk2ta9euOa3/77//Wi1atLCaNWtmXblyhb2JFNMvwzSAs27dOvsyvXjOkiWLaWvav2k7/eSTT9jb8MiUKVOs2rVrWzdu3DC/nz9/3mrbtq0VGhpqzsPitsPp06dbhQsXjncf4I6ePXuaL9lsdu3aZVWrVs0EeDZt2mSW6Tn/8OHDzcW3DhrQ9rllyxZ2MBJ04cIFK0eOHNZff/1l/7J28uTJJhj48MMP2/s3R927d7eeeuop9irsunXrZuILNjt27DBfXGiMwtYHHTx40CpRooQJIhYoUMAqXry4tXnzZvZigCNQ6EN6cqDfKmrH7mjNmjVW9uzZzQgKR/rHev/99zOSEEl65plnzKgbR9rOdJleCC1evNi+/KeffjInpYwkhCMNzmhAJq4//vjDypYtm9WxY0en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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import MaxNLocator\n", + "\n", + "BASE_COLOR = \"#8a8a86\" # muted — the ordinary-day baseline is not the story\n", + "BURST_COLOR = \"#d03b3b\" # fixed status color \"critical\" — see palette.md, never themed\n", + "\n", + "colors = [BURST_COLOR if day in bursts.index else BASE_COLOR for day in daily.index]\n", + "\n", + "fig, ax = plt.subplots(figsize=(13, 4.5))\n", + "ax.bar(range(len(daily)), daily.values, width=1.0, color=colors)\n", + "\n", + "tick_idx = [i for i, day in enumerate(daily.index) if day.endswith(\"-01\")]\n", + "ax.set_xticks(tick_idx)\n", + "ax.set_xticklabels([daily.index[i][:7] for i in tick_idx], rotation=45, ha=\"right\")\n", + "ax.set_ylabel(\"Pageviews\")\n", + "ax.set_title(\n", + " f\"Daily pageviews, {daily.index.min()} .. {daily.index.max()} — {len(bursts)} bursts flagged\"\n", + ")\n", + "ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n", + "ax.spines[[\"top\", \"right\"]].set_visible(False)\n", + "ax.axhline(threshold, color=BURST_COLOR, linewidth=1, linestyle=\"--\", alpha=0.5)\n", + "ax.annotate(\n", + " f\"threshold ({threshold:.0f})\",\n", + " (0, threshold),\n", + " textcoords=\"offset points\",\n", + " xytext=(4, 4),\n", + " fontsize=8,\n", + " color=BURST_COLOR,\n", + ")\n", + "\n", + "fig.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (analytics-notebooks)", + "language": "python", + "name": "analytics-notebooks" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/analytics/notebooks/06_clean_umami_crawlers.ipynb b/analytics/notebooks/06_clean_umami_crawlers.ipynb new file mode 100644 index 000000000..82ee78b7c --- /dev/null +++ b/analytics/notebooks/06_clean_umami_crawlers.ipynb @@ -0,0 +1,386 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "# Cleaning crawlers out of the imported Umami data\n", + "\n", + "`05_traffic_bursts.ipynb` found that ~40.6% of the imported Umami history (655 of 1612\n", + "pageviews) is two crawler signatures, not real visitors. This applies that finding: recompute\n", + "the affected `rollup/{site}/{YYYY-MM}.json` objects from the same CSV with those rows\n", + "excluded, and overwrite.\n", + "\n", + "**Why this can only touch the Umami era.** The live beacon (`hit.ts`) has only ever stored\n", + "`{hits, pages}` — no browser, screen, referrer, session, or country. Once a visit is\n", + "aggregated into that shape there's nothing left to filter. Only the raw CSV export still has\n", + "the signals `05_traffic_bursts.ipynb` used, so cleaning can only mean: re-aggregate the CSV,\n", + "overwrite the `source: \"umami\"` objects it produced. Nothing from the beacon era can be\n", + "touched this way — `analytics/hit.ts` and `website/utils/hitTracker.ts` (Tier 1, shipped\n", + "separately) stop new crawler traffic from being recorded going forward; this notebook only\n", + "corrects what's already there.\n", + "\n", + "**The real scope is Jan 1 – Aug 9, not the full CSV range.** The CSV runs through 2026-08-10,\n", + "but that's a partial day (3 rows, last at 05:12 UTC) that real, uncontaminated beacon data\n", + "already covers (two hourly buckets already sit under `counts/fretchen.eu/2026-08-10T*.json`).\n", + "Aug 10 doesn't need cleaning — it needs *removing* from the rollup so the system's own\n", + "existing fallback (`collectRange` in `stats.ts`, the weekly `rollup` cron) picks up the real\n", + "data it already has, as `source: \"beacon\"`.\n", + "\n", + "**`source` stays `\"umami\"`.** Cleaning improves data *within* the historical category; it\n", + "doesn't create a new one. Changing the label would need a matching change to\n", + "`website/utils/analyticsBuckets.ts`'s `historic` flag — out of scope for a data correction." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-01", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:19.487169Z", + "iopub.status.busy": "2026-08-15T18:20:19.487046Z", + "iopub.status.idle": "2026-08-15T18:20:19.692570Z", + "shell.execute_reply": "2026-08-15T18:20:19.691930Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from storage import S3Storage\n", + "from umami_backfill import SITE, read_pageviews, to_monthly_rollups\n", + "\n", + "load_dotenv() # searches upward — finds ../.env (analytics/.env)\n", + "\n", + "CSV_PATH = \"export/website_event.csv\"\n", + "CUTOFF = \"2026-08-10\" # exclusive — the true Umami-only era ends the day before this\n", + "\n", + "storage = S3Storage(\n", + " access_key=os.environ[\"SCW_ACCESS_KEY\"],\n", + " secret_key=os.environ[\"SCW_SECRET_KEY\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-02", + "metadata": {}, + "source": [ + "## 1. Recompute with crawlers excluded, and cross-check against notebook 05\n", + "\n", + "`exclude_crawlers=True` applies the two signatures from `05_traffic_bursts.ipynb`\n", + "(`is_chronic_crawler`, `find_acute_crawler_session_ids`) before rows are reduced to\n", + "`(day, path)`. The removed count is asserted against that notebook's already-established\n", + "figure — if the CSV or the logic ever drifts, this fails loudly instead of silently\n", + "producing a different cleanup than what was actually analysed." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-03", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:19.694390Z", + "iopub.status.busy": "2026-08-15T18:20:19.694225Z", + "iopub.status.idle": "2026-08-15T18:20:19.718492Z", + "shell.execute_reply": "2026-08-15T18:20:19.717868Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "before: 1612\n", + "after: 957\n", + "removed: 655 (40.6%)\n" + ] + } + ], + "source": [ + "before = read_pageviews(CSV_PATH)\n", + "after = read_pageviews(CSV_PATH, exclude_crawlers=True)\n", + "removed = len(before) - len(after)\n", + "\n", + "print(f\"before: {len(before)}\")\n", + "print(f\"after: {len(after)}\")\n", + "print(f\"removed: {removed} ({removed / len(before):.1%})\")\n", + "\n", + "assert removed == 655, f\"expected 655 removed (per notebook 05), got {removed}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-04", + "metadata": {}, + "source": [ + "## 2. Cap to the true Umami-only era\n", + "\n", + "`2026-08-10` is excluded here — handled separately in section 4, not recomputed." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-05", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:19.720321Z", + "iopub.status.busy": "2026-08-15T18:20:19.720206Z", + "iopub.status.idle": "2026-08-15T18:20:19.724788Z", + "shell.execute_reply": "2026-08-15T18:20:19.724350Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "month before after removed\n", + "2026-01 199 93 106\n", + "2026-02 184 107 77\n", + "2026-03 315 183 132\n", + "2026-04 202 146 56\n", + "2026-05 217 158 59\n", + "2026-06 203 123 80\n", + "2026-07 155 119 36\n", + "2026-08 134 27 107\n" + ] + } + ], + "source": [ + "before_capped = [(day, path) for day, path in before if day < CUTOFF]\n", + "after_capped = [(day, path) for day, path in after if day < CUTOFF]\n", + "\n", + "before_rollups = to_monthly_rollups(before_capped)\n", + "cleaned_rollups = to_monthly_rollups(after_capped)\n", + "\n", + "print(f\"{'month':<10} {'before':>8} {'after':>8} {'removed':>8}\")\n", + "for month in sorted(before_rollups):\n", + " before_hits = sum(d[\"hits\"] for d in before_rollups[month][\"days\"].values())\n", + " after_hits = sum(d[\"hits\"] for d in cleaned_rollups.get(month, {\"days\": {}})[\"days\"].values())\n", + " print(f\"{month:<10} {before_hits:>8} {after_hits:>8} {before_hits - after_hits:>8}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "## 3. Full months (Jan–Jul) — direct overwrite, no merge\n", + "\n", + "`merge_into_existing`'s \"existing day always wins\" rule exists to protect the *original*\n", + "incremental backfill from clobbering good data on a re-run — the wrong tool for a deliberate\n", + "correction. These seven months contain only Umami-sourced days (the beacon didn't exist yet),\n", + "so a wholesale overwrite is safe and simpler than merging." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:19.726379Z", + "iopub.status.busy": "2026-08-15T18:20:19.726254Z", + "iopub.status.idle": "2026-08-15T18:20:24.363730Z", + "shell.execute_reply": "2026-08-15T18:20:24.362943Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7 full months to overwrite: ['2026-01', '2026-02', '2026-03', '2026-04', '2026-05', '2026-06', '2026-07']\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "wrote 7 objects\n" + ] + } + ], + "source": [ + "WRITE_TO_S3 = True # flip to True to write for real\n", + "\n", + "full_months = [m for m in cleaned_rollups if m != \"2026-08\"]\n", + "print(f\"{len(full_months)} full months to overwrite: {full_months}\")\n", + "\n", + "if WRITE_TO_S3:\n", + " for month in sorted(full_months):\n", + " storage.write(f\"rollup/{SITE}/{month}.json\", cleaned_rollups[month])\n", + " print(f\"wrote {len(full_months)} objects\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "## 4. August — read-modify-write, not overwrite\n", + "\n", + "The existing August rollup holds a mix of `source: \"umami\"` days (01-10) and real\n", + "`source: \"beacon\"` days (11 onward, already compacted by the live `/stats` write-back or the\n", + "weekly cron) — overwriting the whole object would destroy that real data. Instead: keep every\n", + "day that isn't `\"umami\"`-sourced untouched, and replace the umami days with the freshly\n", + "cleaned set. Since the cleaned set is capped before `CUTOFF`, day 10 is simply absent from\n", + "it — dropped, not recomputed, exactly as section 2 above." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-09", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:24.366281Z", + "iopub.status.busy": "2026-08-15T18:20:24.366112Z", + "iopub.status.idle": "2026-08-15T18:20:25.211887Z", + "shell.execute_reply": "2026-08-15T18:20:25.210871Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "preserved (non-umami) days: ['2026-08-11', '2026-08-14']\n", + "cleaned umami days (01-09): ['2026-08-01', '2026-08-02', '2026-08-03', '2026-08-04', '2026-08-05', '2026-08-07', '2026-08-09']\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "wrote rollup/fretchen.eu/2026-08.json\n" + ] + } + ], + "source": [ + "existing_aug = storage.read(f\"rollup/{SITE}/2026-08.json\") or {\n", + " \"site\": SITE,\n", + " \"month\": \"2026-08\",\n", + " \"days\": {},\n", + "}\n", + "preserved_days = {day: b for day, b in existing_aug[\"days\"].items() if b[\"source\"] != \"umami\"}\n", + "cleaned_aug_days = cleaned_rollups.get(\"2026-08\", {\"days\": {}})[\"days\"]\n", + "\n", + "print(f\"preserved (non-umami) days: {sorted(preserved_days)}\")\n", + "print(f\"cleaned umami days (01-09): {sorted(cleaned_aug_days)}\")\n", + "assert \"2026-08-10\" not in cleaned_aug_days, \"cutoff should have excluded Aug 10\"\n", + "\n", + "new_aug = {\n", + " \"site\": SITE,\n", + " \"month\": \"2026-08\",\n", + " \"days\": dict(sorted({**preserved_days, **cleaned_aug_days}.items())),\n", + "}\n", + "\n", + "if WRITE_TO_S3:\n", + " storage.write(f\"rollup/{SITE}/2026-08.json\", new_aug)\n", + " print(\"wrote rollup/fretchen.eu/2026-08.json\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "## 5. Read back and confirm\n", + "\n", + "Only meaningful after a real write (`WRITE_TO_S3 = True`)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-15T18:20:25.214672Z", + "iopub.status.busy": "2026-08-15T18:20:25.214492Z", + "iopub.status.idle": "2026-08-15T18:20:26.368650Z", + "shell.execute_reply": "2026-08-15T18:20:26.367565Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-01: 19 days, 93 hits, sources=['umami'], aug10=False\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-02: 15 days, 107 hits, sources=['umami'], aug10=False\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-03: 26 days, 183 hits, sources=['umami'], aug10=False\n", + "2026-04: 24 days, 146 hits, sources=['umami'], aug10=False\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-05: 27 days, 158 hits, sources=['umami'], aug10=False\n", + "2026-06: 26 days, 123 hits, sources=['umami'], aug10=False\n", + "2026-07: 25 days, 119 hits, sources=['umami'], aug10=False\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-08: 9 days, 49 hits, sources=['beacon', 'umami'], aug10=False\n" + ] + } + ], + "source": [ + "if WRITE_TO_S3:\n", + " for month in sorted(cleaned_rollups):\n", + " stored = storage.read(f\"rollup/{SITE}/{month}.json\")\n", + " n_days = len(stored[\"days\"])\n", + " hits = sum(d[\"hits\"] for d in stored[\"days\"].values())\n", + " sources = sorted({d[\"source\"] for d in stored[\"days\"].values()})\n", + " has_aug_10 = \"2026-08-10\" in stored[\"days\"]\n", + " print(f\"{month}: {n_days} days, {hits} hits, sources={sources}, aug10={has_aug_10}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (analytics-notebooks)", + "language": "python", + "name": "analytics-notebooks" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/analytics/notebooks/README.md b/analytics/notebooks/README.md index 771b156f9..e1cfb3d4f 100644 --- a/analytics/notebooks/README.md +++ b/analytics/notebooks/README.md @@ -11,6 +11,8 @@ service, kept here rather than in the repo's general-purpose root | `01_smoke_test.ipynb` | POSTs real hits to `/hit` (valid and invalid), then reads the resulting object back to confirm the write landed. `LOCAL` toggle at the top switches between `npm run dev` (file storage, no credentials) and the deployed service (real S3). | | `02_readout.ipynb` | Aggregates one day's hourly buckets (sum `hits`, merge `pages`), then reads a date range out of the monthly rollups — each half validated against a local fixture first. Prototype for the eventual `GET /stats`; not wired into any endpoint. | | `03_umami_backfill.ipynb` | One-off: folds the cloud.umami.is data export into monthly rollups, so the ~7 months predating the hit counter aren't lost. Dry-runs to `state/` by default; `WRITE_TO_S3` flips it to the real write. | +| `04_umami_vs_beacon.ipynb` | One-off: compares a fresh Umami export against what the beacon actually recorded for the same days, per-day table + chart. Reads `beacon_source` (`"beacon"` vs. `"umami"`) rather than guessing from dates, so it can't accidentally compare an old backfilled day against itself; a day with Umami traffic and zero beacon hits is flagged as a likely outage, not a quiet day. | +| `05_traffic_bursts.ipynb` | One-off: flags statistically unusual days in the full pre-migration Umami export (`median + 3×MAD`) and, for each, computes session-ratio/fingerprint-concentration/referrer/geo/path signals to distinguish a crawl from an organic spike from the site owner's own recurring traffic. Cross-references bursts against blog `publishing_date`s. Only possible against the old export — the live beacon records no browser/referrer/session data by design, so this kind of analysis can't be repeated for beacon-only-era traffic without a schema change. | `umami_backfill.py` holds that import's logic — CSV parsing, path normalisation, monthly grouping, and an idempotent merge where days already @@ -20,6 +22,9 @@ live counter would have recorded. Delete the export when you're done; `analytics/.gitignore` covers `*.zip`/`*.csv` so it can't be committed by accident. +`04_umami_vs_beacon.ipynb` is the one notebook here needing `pandas`/`matplotlib` +(added to `pyproject.toml` for it) — the others only ever print dicts. + `storage.py` provides `LocalStorage` (JSON files on disk — fixture-driven, no credentials needed) and `S3Storage` (real Scaleway Object Storage via `boto3`), mirroring `growth-agent/agent/storage.py`'s shape. diff --git a/analytics/notebooks/pyproject.toml b/analytics/notebooks/pyproject.toml index a0d3d52f8..8a98e79bf 100644 --- a/analytics/notebooks/pyproject.toml +++ b/analytics/notebooks/pyproject.toml @@ -9,6 +9,8 @@ dependencies = [ "python-dotenv>=1.0", "requests>=2.33.0", "boto3>=1.34", + "pandas>=2.2", + "matplotlib>=3.9", ] [tool.uv] diff --git a/analytics/notebooks/umami_backfill.py b/analytics/notebooks/umami_backfill.py index 1d26d84c5..0943ba742 100644 --- a/analytics/notebooks/umami_backfill.py +++ b/analytics/notebooks/umami_backfill.py @@ -70,22 +70,94 @@ def normalize_path(raw: str) -> str | None: return path -def read_pageviews(csv_path: str | Path, hostname: str = f"www.{SITE}") -> list[tuple[str, str]]: +# The two crawler signatures identified and validated in +# analytics/notebooks/05_traffic_bursts.ipynb against the full export. Kept as +# the only two rules here deliberately -- a more general bot detector would be +# over-engineering for a one-off cleanup of a fixed, already-characterised +# dataset. + + +def is_chronic_crawler(row: dict) -> bool: + """The persistent Chrome/Windows/1280x1200 signature: 568 rows spread + across 179 of ~214 days, every one its own one-shot session, no referrer, + no single dominant page -- a distributed sweep disguised as the world's + most common desktop browser configuration. Screen resolution is the tell: + 1280x1200 is not a resolution real Windows laptops actually ship with. + """ + return ( + row["browser"] == "chrome" + and row["os"] == "Windows 10" + and row["device"] == "laptop" + and row["screen"] == "1280x1200" + ) + + +def find_acute_crawler_session_ids(rows: list[dict]) -> set[str]: + """The two-day Aug 8-9 spike: chrome/Linux/laptop, one-shot no-referrer + sessions, restricted to days where that exact fingerprint spans 3+ + countries -- something no single real device can do. Needs two passes + (session size, then per-day country count) over just this fingerprint's + own rows, mirroring notebook 05's validated logic exactly. + """ + linux_rows = [ + r + for r in rows + if r["browser"] == "chrome" and r["os"] == "Linux" and r["device"] == "laptop" + ] + + session_sizes: dict[str, int] = defaultdict(int) + for r in linux_rows: + session_sizes[r["session_id"]] += 1 + + one_shot_no_referrer = [ + r for r in linux_rows if session_sizes[r["session_id"]] == 1 and not r["referrer_domain"] + ] + + countries_per_day: dict[str, set[str]] = defaultdict(set) + for r in one_shot_no_referrer: + countries_per_day[r["created_at"][:10]].add(r["country"]) + crawl_days = {day for day, countries in countries_per_day.items() if len(countries) >= 3} + + return {r["session_id"] for r in one_shot_no_referrer if r["created_at"][:10] in crawl_days} + + +def read_pageviews( + csv_path: str | Path, hostname: str = f"www.{SITE}", exclude_crawlers: bool = False +) -> list[tuple[str, str]]: """Extract (day, canonical_path) pairs from a Umami `website_event.csv`. Drops custom events, other hostnames (localhost dev traffic), and any path that fails normalisation. Returns one tuple per pageview — no dedup, no sessionisation, matching how the beacon counts. + + `exclude_crawlers=True` (default off, so existing callers are unaffected) + additionally drops rows matching `is_chronic_crawler` or + `find_acute_crawler_session_ids` before paths are extracted -- the + filtering needs the full row (browser/os/device/screen/referrer/session/ + country), which is why it happens here rather than after reduction to + `(day, path)`. """ - pageviews: list[tuple[str, str]] = [] with open(csv_path, newline="", encoding="utf-8") as handle: - for row in csv.DictReader(handle): - if row["event_type"] != PAGEVIEW or row["hostname"] != hostname: - continue - path = normalize_path(row["url_path"]) - if path is None: - continue - pageviews.append((row["created_at"][:10], path)) + rows = [ + row + for row in csv.DictReader(handle) + if row["event_type"] == PAGEVIEW and row["hostname"] == hostname + ] + + if exclude_crawlers: + acute_session_ids = find_acute_crawler_session_ids(rows) + rows = [ + row + for row in rows + if not is_chronic_crawler(row) and row["session_id"] not in acute_session_ids + ] + + pageviews: list[tuple[str, str]] = [] + for row in rows: + path = normalize_path(row["url_path"]) + if path is None: + continue + pageviews.append((row["created_at"][:10], path)) return pageviews diff --git a/analytics/notebooks/uv.lock b/analytics/notebooks/uv.lock index 960949b65..0ab9053fd 100644 --- a/analytics/notebooks/uv.lock +++ b/analytics/notebooks/uv.lock @@ -2,8 +2,15 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.11' and python_full_version < '3.14'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version < '3.11'", ] @@ -15,6 +22,10 @@ dependencies = [ { name = "boto3" }, { name = "ipykernel" }, { name = "jupyter" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-dotenv" }, { name = "requests" }, ] @@ -29,6 +40,8 @@ requires-dist = [ { name = "boto3", specifier = ">=1.34" }, { name = "ipykernel", specifier = ">=6.25.0" }, { name = "jupyter", specifier = ">=1.0.0" }, + { name = "matplotlib", specifier = ">=3.9" }, + { name = "pandas", specifier = ">=2.2" }, { name = "python-dotenv", specifier = ">=1.0" }, { name = "requests", specifier = ">=2.33.0" }, ] @@ -441,6 +454,179 @@ 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 = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +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 = "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 = "debugpy" version = "1.8.21" @@ -518,6 +704,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/e1/62cc96341f01bdff2ba967441939178fcd1900d11ce7e6554d9954a5d7ec/fastjsonschema-2.22.1-py3-none-any.whl", hash = "sha256:cf377ff5c9a6f4f3125fb35f75a2c5767bd824ffbcf62c209a93cd48d1453999", size = 26239, upload-time = "2026-07-27T13:31:03.251Z" }, ] +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + [[package]] name = "fqdn" version = "1.5.1" @@ -628,8 +871,15 @@ name = "ipython" version = "9.16.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.11' and python_full_version < '3.14'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, @@ -999,6 +1249,130 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + [[package]] name = "lark" version = "1.3.1" @@ -1093,6 +1467,158 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "cycler", marker = "python_full_version < '3.11'" }, + { name = "fonttools", marker = "python_full_version < '3.11'" }, + { name = "kiwisolver", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "pyparsing", marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.11'" }, + { name = "fonttools", marker = "python_full_version >= '3.11'" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "pyparsing", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + [[package]] name = "matplotlib-inline" version = "0.2.2" @@ -1210,6 +1736,236 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, ] +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, +] + [[package]] name = "overrides" version = "7.7.0" @@ -1228,6 +1984,136 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +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/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { 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 = "pandas" +version = "3.0.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, +] + [[package]] name = "pandocfilters" version = "1.5.1" @@ -1251,13 +2137,107 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess" }, + { name = "ptyprocess", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] 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 = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + [[package]] name = "platformdirs" version = "4.11.1" @@ -1352,6 +2332,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1382,6 +2371,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, ] +[[package]] +name = "pytz" +version = "2026.3.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, +] + [[package]] name = "pywinpty" version = "3.0.5" @@ -1734,8 +2732,15 @@ name = "rpds-py" version = "2026.6.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.11' and python_full_version < '3.14'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } wheels = [ diff --git a/analytics/test/buckets.test.ts b/analytics/test/buckets.test.ts index df2795246..cc29e1f10 100644 --- a/analytics/test/buckets.test.ts +++ b/analytics/test/buckets.test.ts @@ -65,10 +65,32 @@ describe("readDayFromHourly", () => { const day = await readDayFromHourly(store, SITE, "2026-08-10"); - expect(day).toEqual({ hits: 7, pages: { "/": 4, "/blog/": 2, "/x402/": 1 }, source: "beacon" }); + expect(day).toEqual({ hits: 7, landings: 0, pages: { "/": 4, "/blog/": 2, "/x402/": 1 }, source: "beacon" }); expect(Object.keys(day!.pages)).toEqual(["/", "/blog/", "/x402/"]); }); + it("sums landings across the day's buckets alongside hits", async () => { + const store = new MemoryHitStorage({ + "counts/fretchen.eu/2026-08-10T00.json": hourBucket(5, { "/": 5 }, 3), + "counts/fretchen.eu/2026-08-10T13.json": hourBucket(2, { "/": 2 }, 1), + }); + + const day = await readDayFromHourly(store, SITE, "2026-08-10"); + + expect(day).toMatchObject({ hits: 7, landings: 4 }); + }); + + it("treats an hourly bucket written before landings existed as landings: 0", async () => { + const store = new MemoryHitStorage({ + // No `landings` key at all — simulates a real pre-migration S3 object. + "counts/fretchen.eu/2026-08-10T00.json": { hits: 5, pages: { "/": 5 } }, + }); + + const day = await readDayFromHourly(store, SITE, "2026-08-10"); + + expect(day).toMatchObject({ hits: 5, landings: 0 }); + }); + it("returns null for a day with no objects, so no-traffic is distinguishable from not-compacted", async () => { const store = new MemoryHitStorage(); expect(await readDayFromHourly(store, SITE, "2026-08-10")).toBeNull(); diff --git a/analytics/test/hit.test.ts b/analytics/test/hit.test.ts index 969f34dcc..9234472c6 100644 --- a/analytics/test/hit.test.ts +++ b/analytics/test/hit.test.ts @@ -61,6 +61,31 @@ describe("hit handler", () => { expect(res.statusCode).toBe(405); }); + it.each([ + "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", + "Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)", + "ClaudeBot/1.0 (+https://www.anthropic.com)", + "AhrefsBot/7.0 (+http://ahrefs.com/robot/)", + ])("rejects a known-bot User-Agent with 400, without writing", async (userAgent) => { + const res = await handleHit(makeEvent({ headers: { "user-agent": userAgent } }), {}); + expect(res.statusCode).toBe(400); + expect(mockGetS3ObjectWithMeta).not.toHaveBeenCalled(); + expect(mockPutS3ObjectConditional).not.toHaveBeenCalled(); + }); + + it("does not flag an ordinary browser User-Agent as a bot", async () => { + mockGetS3ObjectWithMeta.mockResolvedValue(null); + mockPutS3ObjectConditional.mockResolvedValue({ ok: true, etag: '"new-etag"' }); + + const res = await handleHit( + makeEvent({ headers: { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0" } }), + {}, + ); + + expect(res.statusCode).toBe(204); + expect(mockPutS3ObjectConditional).toHaveBeenCalled(); + }); + it("rejects a missing body with 400", async () => { const res = await handleHit(makeEvent({ body: undefined }), {}); expect(res.statusCode).toBe(400); @@ -112,7 +137,7 @@ describe("hit handler", () => { expect(res.statusCode).toBe(204); const [key, body, opts] = mockPutS3ObjectConditional.mock.calls[0]; expect(key).toMatch(/^counts\/fretchen\.eu\/\d{4}-\d{2}-\d{2}T\d{2}\.json$/); - expect(JSON.parse(body)).toEqual({ hits: 1, pages: { "/blog/foo": 1 } }); + expect(JSON.parse(body)).toEqual({ hits: 1, landings: 0, pages: { "/blog/foo": 1 } }); expect(opts.ifNoneMatch).toBe("*"); expect(opts.ifMatch).toBeUndefined(); }); @@ -141,10 +166,59 @@ describe("hit handler", () => { expect(res.statusCode).toBe(204); const [, body, opts] = mockPutS3ObjectConditional.mock.calls[0]; - expect(JSON.parse(body)).toEqual({ hits: 6, pages: { "/blog/foo": 3, "/": 3 } }); + // The stored bucket predates `landings` — this also covers the backward-compat + // default (0), not just the increment. + expect(JSON.parse(body)).toEqual({ hits: 6, landings: 0, pages: { "/blog/foo": 3, "/": 3 } }); expect(opts.ifMatch).toBe('"etag-1"'); }); + it("increments landings only when the request says landing: true", async () => { + mockGetS3ObjectWithMeta.mockResolvedValue(null); + mockPutS3ObjectConditional.mockResolvedValue({ ok: true, etag: '"e"' }); + + await handleHit(makeEvent({ body: JSON.stringify({ site: "fretchen.eu", path: "/blog/foo", landing: true }) }), {}); + + const [, body] = mockPutS3ObjectConditional.mock.calls[0]; + expect(JSON.parse(body)).toMatchObject({ hits: 1, landings: 1 }); + }); + + it("does not increment landings for an in-app navigation (landing: false)", async () => { + mockGetS3ObjectWithMeta.mockResolvedValue(null); + mockPutS3ObjectConditional.mockResolvedValue({ ok: true, etag: '"e"' }); + + await handleHit( + makeEvent({ body: JSON.stringify({ site: "fretchen.eu", path: "/blog/foo", landing: false }) }), + {}, + ); + + const [, body] = mockPutS3ObjectConditional.mock.calls[0]; + expect(JSON.parse(body)).toMatchObject({ hits: 1, landings: 0 }); + }); + + it("treats a missing landing field as false rather than rejecting the request", async () => { + mockGetS3ObjectWithMeta.mockResolvedValue(null); + mockPutS3ObjectConditional.mockResolvedValue({ ok: true, etag: '"e"' }); + + const res = await handleHit(makeEvent(), {}); // makeEvent's default body has no `landing` + + expect(res.statusCode).toBe(204); + const [, body] = mockPutS3ObjectConditional.mock.calls[0]; + expect(JSON.parse(body)).toMatchObject({ landings: 0 }); + }); + + it("keeps incrementing landings across repeat visits", async () => { + mockGetS3ObjectWithMeta.mockResolvedValue({ + body: JSON.stringify({ hits: 4, landings: 2, pages: { "/blog/foo": 4 } }), + etag: '"etag-1"', + }); + mockPutS3ObjectConditional.mockResolvedValue({ ok: true, etag: '"etag-2"' }); + + await handleHit(makeEvent({ body: JSON.stringify({ site: "fretchen.eu", path: "/blog/foo", landing: true }) }), {}); + + const [, body] = mockPutS3ObjectConditional.mock.calls[0]; + expect(JSON.parse(body)).toMatchObject({ hits: 5, landings: 3 }); + }); + it("still increments hits but stops adding new distinct paths once the pages cap is reached", async () => { const pages: Record = {}; for (let i = 0; i < 200; i++) { @@ -199,7 +273,7 @@ describe("hit handler", () => { expect(mockGetS3ObjectWithMeta).toHaveBeenCalledTimes(2); expect(mockPutS3ObjectConditional).toHaveBeenCalledTimes(2); const [, finalBody] = mockPutS3ObjectConditional.mock.calls[1]; - expect(JSON.parse(finalBody)).toEqual({ hits: 3, pages: { "/blog/foo": 1 } }); + expect(JSON.parse(finalBody)).toEqual({ hits: 3, landings: 0, pages: { "/blog/foo": 1 } }); }); it("gives up silently after exceeding max CAS attempts, still returning 204", async () => { diff --git a/analytics/test/memoryStorage.ts b/analytics/test/memoryStorage.ts index 4571cb3d8..522bf4780 100644 --- a/analytics/test/memoryStorage.ts +++ b/analytics/test/memoryStorage.ts @@ -57,6 +57,6 @@ export class MemoryHitStorage implements HitStorage { } /** Seeds one hour bucket, in the layout `hit.ts` writes. */ -export function hourBucket(hits: number, pages: Record) { - return { hits, pages }; +export function hourBucket(hits: number, pages: Record, landings = 0) { + return { hits, landings, pages }; } diff --git a/analytics/test/rollup.test.ts b/analytics/test/rollup.test.ts index 73b020665..621747a41 100644 --- a/analytics/test/rollup.test.ts +++ b/analytics/test/rollup.test.ts @@ -21,6 +21,7 @@ describe("rollupRecentDays", () => { expect(summary.written).toEqual(["2026-08-09"]); expect(store.read(rollupKey(SITE, "2026-08"))?.days["2026-08-09"]).toEqual({ hits: 4, + landings: 0, pages: { "/blog/": 2, "/": 2 }, source: "beacon", }); diff --git a/analytics/test/stats.test.ts b/analytics/test/stats.test.ts index 81f052cff..3edfaeae4 100644 --- a/analytics/test/stats.test.ts +++ b/analytics/test/stats.test.ts @@ -52,7 +52,7 @@ describe("collectRange", () => { const days = await collectRange(store, SITE, "2026-08-08", "2026-08-10", NOW); expect(Object.keys(days).sort()).toEqual(["2026-08-08", "2026-08-09", "2026-08-10"]); - expect(days["2026-08-09"]).toEqual({ hits: 4, pages: { "/blog/": 4 }, source: "beacon" }); + expect(days["2026-08-09"]).toEqual({ hits: 4, landings: 0, pages: { "/blog/": 4 }, source: "beacon" }); }); it("returns the same days whether or not they have been rolled up", async () => { @@ -67,8 +67,8 @@ describe("collectRange", () => { site: SITE, month: "2026-08", days: { - "2026-08-08": { hits: 6, pages: { "/": 6 }, source: "beacon" }, - "2026-08-09": { hits: 4, pages: { "/blog/": 4 }, source: "beacon" }, + "2026-08-08": { hits: 6, landings: 0, pages: { "/": 6 }, source: "beacon" }, + "2026-08-09": { hits: 4, landings: 0, pages: { "/blog/": 4 }, source: "beacon" }, }, }, }); @@ -122,6 +122,7 @@ describe("collectRange write-back", () => { expect(store.read(rollupKey(SITE, "2026-08"))?.days["2026-08-09"]).toEqual({ hits: 4, + landings: 0, pages: { "/blog/": 4 }, source: "beacon", }); @@ -166,7 +167,7 @@ describe("collectRange write-back", () => { // Only today still needs its 24 hourly keys; the two rebuilt days are now rollup reads. expect(store.gets.filter((key) => key.startsWith("counts/")).length).toBe(24); expect(store.gets.length).toBeLessThan(firstCallGets); - expect(second["2026-08-09"]).toEqual({ hits: 4, pages: { "/blog/": 4 }, source: "beacon" }); + expect(second["2026-08-09"]).toEqual({ hits: 4, landings: 0, pages: { "/blog/": 4 }, source: "beacon" }); }); it("still serves the data when the write-back fails", async () => { @@ -191,7 +192,7 @@ describe("buildStats", () => { expect(stats.from).toBe("2025-08-11"); expect(stats.to).toBe("2026-08-10"); - expect(stats.days).toEqual({ "2026-08-10": { hits: 2, pages: { "/": 2 }, source: "beacon" } }); + expect(stats.days).toEqual({ "2026-08-10": { hits: 2, landings: 0, pages: { "/": 2 }, source: "beacon" } }); }); it("carries per-day pages and source so the client can slice any range", async () => { diff --git a/website/pages/+onHydrationEnd.ts b/website/pages/+onHydrationEnd.ts index 145ed5e4e..0e5f2457c 100644 --- a/website/pages/+onHydrationEnd.ts +++ b/website/pages/+onHydrationEnd.ts @@ -2,5 +2,5 @@ import type { PageContextClient } from "vike/types"; import { trackHit } from "../utils/hitTracker"; export function onHydrationEnd(pageContext: PageContextClient) { - trackHit(pageContext.urlPathname); + trackHit(pageContext.urlPathname, true); } diff --git a/website/pages/+onPageTransitionEnd.ts b/website/pages/+onPageTransitionEnd.ts index f8ddfc664..2d2167ca1 100644 --- a/website/pages/+onPageTransitionEnd.ts +++ b/website/pages/+onPageTransitionEnd.ts @@ -3,5 +3,5 @@ import { trackHit } from "../utils/hitTracker"; export function onPageTransitionEnd(pageContext: PageContext) { document.querySelector("body")?.classList.remove("page-is-transitioning"); - trackHit(pageContext.urlPathname); + trackHit(pageContext.urlPathname, false); } diff --git a/website/test/hitTracker.test.ts b/website/test/hitTracker.test.ts index 85c6a6102..582e84e84 100644 --- a/website/test/hitTracker.test.ts +++ b/website/test/hitTracker.test.ts @@ -7,11 +7,26 @@ describe("trackHit", () => { }); it("sends a beacon to /hit with the site and path", () => { - trackHit("/blog/foo"); + trackHit("/blog/foo", true); expect(navigator.sendBeacon).toHaveBeenCalledTimes(1); const [url, body] = vi.mocked(navigator.sendBeacon).mock.calls[0]; expect(url).toMatch(/\/hit$/); - expect(JSON.parse(body as string)).toEqual({ site: "fretchen.eu", path: "/blog/foo" }); + expect(JSON.parse(body as string)).toEqual({ site: "fretchen.eu", path: "/blog/foo", landing: true }); + }); + + it("forwards isLanding=false for an in-app navigation", () => { + trackHit("/blog/foo", false); + + const [, body] = vi.mocked(navigator.sendBeacon).mock.calls[0]; + expect(JSON.parse(body as string)).toMatchObject({ landing: false }); + }); + + it("skips the beacon entirely when navigator.webdriver is set", () => { + vi.stubGlobal("navigator", { sendBeacon: vi.fn(), webdriver: true }); + + trackHit("/blog/foo", true); + + expect(navigator.sendBeacon).not.toHaveBeenCalled(); }); }); diff --git a/website/types/analytics.ts b/website/types/analytics.ts index f36e04026..1de525696 100644 --- a/website/types/analytics.ts +++ b/website/types/analytics.ts @@ -2,6 +2,13 @@ export interface DayBucket { hits: number; + /** + * Fresh page loads only, not in-app navigations — a proxy for how many + * pages a visit covered. Absent on any day written before this field + * existed (every "umami" day, and any older "beacon" day) — not + * retrofittable, so treat a missing value as unknown, not zero. + */ + landings?: number; /** Path → count for that day. Summed client-side over whatever range is shown. */ pages: Record; /** diff --git a/website/utils/hitTracker.ts b/website/utils/hitTracker.ts index d68782127..61bc06eea 100644 --- a/website/utils/hitTracker.ts +++ b/website/utils/hitTracker.ts @@ -1,5 +1,21 @@ import { ANALYTICS_URL } from "./analyticsApi"; -export function trackHit(path: string) { - navigator.sendBeacon(`${ANALYTICS_URL}/hit`, JSON.stringify({ site: "fretchen.eu", path })); +/** + * `navigator.webdriver` is set by unmodified automation frameworks + * (Selenium/Playwright/Puppeteer defaults) — a free, zero-data-sent signal to + * skip the beacon entirely. It won't catch a crawler that deliberately hides + * this property, but it costs nothing to check and sends nothing new either way. + * + * `isLanding` distinguishes a fresh page load (`+onHydrationEnd.ts`) from an + * in-app navigation (`+onPageTransitionEnd.ts`) — not PII, since it's a fact + * about this one isolated hit, never linked to any other hit or to a person. + * "Landings ÷ hits" is a close proxy for how many pages a visit covers, the + * signal that separated real browsing from crawler sweeps in + * analytics/notebooks/05_traffic_bursts.ipynb. + */ +export function trackHit(path: string, isLanding: boolean) { + if (navigator.webdriver) { + return; + } + navigator.sendBeacon(`${ANALYTICS_URL}/hit`, JSON.stringify({ site: "fretchen.eu", path, landing: isLanding })); }