diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 774c119..ea3e173 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,11 +1,44 @@ name: Continuous Integration and Deployment on: + pull_request: + push: + branches: [master, offline-test-suite] # TEMP: remove `offline-test-suite` before merge — used to bootstrap CI while the pull_request trigger isn't yet on master release: types: [created] workflow_dispatch: jobs: + # Offline test suite — runs on every PR and push to master. + # test_performance_stats.py covers `custom_tables` and still hits AEMO + # + references local files that aren't in CI. Parked until ported. + # Everything else runs against the in-process dummy server from + # tests/conftest.py and is fully offline. + test: + name: Tests (Python ${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10", "3.11", "3.12", "3.13"] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + - name: Sync dependencies + run: uv sync --python ${{ matrix.python-version }} + - name: Run tests + run: > + uv run pytest tests/ + --ignore=tests/test_performance_stats.py + -v + # Publishes to PyPi if tests are passed and release is created publish: if: github.event_name == 'release' && github.event.action == 'created' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa61ef5..45d99cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,19 +51,22 @@ Ready to contribute? Here's how to set up `ispypsa` for local development. $ git checkout -b name-of-your-bugfix-or-feature ``` -6. When you're done making changes, check that your changes pass any tests. - - - Configure an environment variable called "NEMOSIS_TEST_CACHE" specifying the location of the raw data cache to use - while testing - - Run all tests by running `uv run -m unittest discover tests` - - Run a subset of tests using the name of the test class or method, for example - `uv run -m unittest discover tests -v -k TestDynamicDataCompilerWithSettlementDateFiltering` - - All tests should pass before a PR is considered good to merge into main. However, the tests are very long running - so it is best to test on a small subset of tests that are likely to fail, before running the full suite. - Additionally, the full suite should be run twice, once with an empty cache, and then again with cache prefilled. - - We acknowledge the testing process is currently a bit cumbersome and slow, any contributions to improve this - process are welcomed :) - - A bit more info to come on testing once I get them all working again . . . +6. When you're done making changes, check that your changes pass the tests: + + ```console + $ uv run pytest tests/ \ + --ignore=tests/test_data_fetch_methods.py \ + --ignore=tests/test_errors_and_warnings.py \ + --ignore=tests/test_format_options.py \ + --ignore=tests/test_performance_stats.py \ + --ignore=tests/test_processing_info_maps.py + ``` + + The suite is fully offline and runs in under a minute. The five ignored files are legacy + network-hitting tests slated for removal. CI runs the same invocation on every PR. + + For details on how the test suite is structured, how to add tests for new tables, and how + fixtures are maintained, see [testing_and_maintenance.md](testing_and_maintenance.md). 7. Commit your changes and open a pull request. diff --git a/pyproject.toml b/pyproject.toml index dbeb9c7..71904c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "openpyxl>=3.1.5", ] readme = "README.md" -requires-python = ">= 3.9" +requires-python = ">= 3.10" [build-system] requires = ["hatchling"] diff --git a/src/nemosis/defaults.py b/src/nemosis/defaults.py index 5a24442..e52126e 100644 --- a/src/nemosis/defaults.py +++ b/src/nemosis/defaults.py @@ -1,7 +1,7 @@ import os names = { - "FCAS Providers": "NEM Registration and Exemption List.xls", + "FCAS Providers": "NEM Registration and Exemption List.xlsx", "DISPATCHLOAD": "PUBLIC_DVD_DISPATCHLOAD", "NEXT_DAY_DISPATCHLOAD": "PUBLIC_NEXT_DAY_DISPATCHLOAD", "DUDETAILSUMMARY": "PUBLIC_DVD_DUDETAILSUMMARY", @@ -22,7 +22,7 @@ "FCAS_4_SECOND": "FCAS", "ELEMENTS_FCAS_4_SECOND": "Elements_FCAS.csv", "VARIABLES_FCAS_4_SECOND": "Ancillary Services Market Causer Pays Variables File.csv", - "Generators and Scheduled Loads": "NEM Registration and Exemption List.xls", + "Generators and Scheduled Loads": "NEM Registration and Exemption List.xlsx", "MNSP_INTERCONNECTOR": "PUBLIC_DVD_MNSP_INTERCONNECTOR", "MNSP_PEROFFER": "PUBLIC_DVD_MNSP_PEROFFER", "INTERCONNECTOR": "PUBLIC_DVD_INTERCONNECTOR", @@ -133,8 +133,8 @@ static_table_url = { "ELEMENTS_FCAS_4_SECOND": "https://www.nemweb.com.au/Reports/Current/Causer_Pays_Elements/", "VARIABLES_FCAS_4_SECOND": "https://aemo.com.au/-/media/files/electricity/nem/settlements_and_payments/settlements/auction-reports/archive/ancillary-services-market-causer-pays-variables-file.csv", - "Generators and Scheduled Loads": "https://www.aemo.com.au/-/media/Files/Electricity/NEM/Participant_Information/NEM-Registration-and-Exemption-List.xls", - "_downloader.download_xl": "https://www.aemo.com.au/-/media/Files/Electricity/NEM/Participant_Information/NEM-Registration-and-Exemption-List.xls", + "Generators and Scheduled Loads": "https://www.aemo.com.au/-/media/files/electricity/nem/Participant_Information/NEM-Registration-and-Exemption-List.xlsx", + "_downloader.download_xl": "https://www.aemo.com.au/-/media/files/electricity/nem/Participant_Information/NEM-Registration-and-Exemption-List.xlsx", } aemo_mms_url = "http://www.nemweb.com.au/Data_Archive/Wholesale_Electricity/MMSDM/{}/MMSDM_{}_{}/MMSDM_Historical_Data_SQLLoader/DATA/{}.zip" diff --git a/testing_and_maintenance.md b/testing_and_maintenance.md new file mode 100644 index 0000000..2dd01d8 --- /dev/null +++ b/testing_and_maintenance.md @@ -0,0 +1,224 @@ +# Testing and maintenance + +This document covers how the NEMOSIS test suite is structured, how to run it, and how to extend or +maintain it as AEMO's data formats evolve. + +## Overview + +The end-to-end suite runs against a local dummy HTTP server (defined in `tests/conftest.py`) that +serves pre-filtered AEMO data committed to `tests/fixtures/data/`. The server is a bare +`SimpleHTTPServer` rooted at the fixture tree, which mirrors AEMO's URL paths verbatim — at test +time NEMOSIS's hostname is swapped for the local server and everything else is real. + +The whole suite is fast (under a minute) and network-free. + +## Running tests + +```console +$ uv run pytest tests/ --ignore=tests/test_performance_stats.py +``` + +`test_performance_stats.py` is the last legacy network-hitting file. It covers the +`custom_tables` module (VWAP, capacity factors, plant stats) which hasn't been ported offline +yet — most of its assertions run on synthetic DataFrames and are trivially portable, but a +few depend on local plant-stats files (`E:\plants_stats_test_data\`) that exist only on one +developer's machine. CI uses the same invocation (see `.github/workflows/cicd.yml`). + +To run a single file: + +```console +$ uv run pytest tests/end_to_end_table_tests/test_dispatch_price.py -v +``` + +## Suite layout + +``` +tests/ + conftest.py # Dummy HTTP server fixture + fixtures/ + spec.py # What to download for each table + build.py # Downloads + filters + writes fixture files + data/ # Committed fixture tree (mirrors AEMO URLs) + end_to_end_table_tests/ + _boundaries.py # Shared boundary-test helper + test_.py # One file per AEMO table + DECISIONS.md # Internal design notes (transitional) + test_filters.py # Pure unit tests + test_date_generators.py + test_query_wrappers.py + test_errors.py # Argument validation + NoDataToReturn + cache idempotency + test_processing_info_maps.py # Cross-table validation of search_type classification + test_performance_stats.py # Legacy custom_tables tests — ignored in CI +``` + +Each test file in `end_to_end_table_tests/` covers one AEMO table. Files start with a module +docstring explaining the table's shape and which eras the fixture covers. + +## The fixture system + +`tests/fixtures/data/` is a verbatim mirror of AEMO's URL tree. For example: + +``` +http://www.nemweb.com.au/Data_Archive/Wholesale_Electricity/MMSDM/2018/... + ↓ +tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/... +``` + +This means the dummy server doesn't need any clever routing — it just serves static files at the +matching paths. + +Fixtures are filtered down (~50× smaller than raw AEMO archives) by: + +- **Row filter**: each table keeps only two DUIDs / two REGIONIDs / one INTERCONNECTORID. The + selection lives in `tests/fixtures/spec.py` (`DUIDS`, `REGIONS`, etc.). +- **Time filter**: only the first 3 and last 2 days of each month, controlled by + `KEEP_FIRST_DAYS` / `KEEP_LAST_DAYS` in `build.py`. +- **Section filter** (scrape-pattern files only): for files like `PUBLIC_DAILY` that bundle many + logical tables in one CSV, the build script keeps only the section NEMOSIS reads and strips the + D-rows from the rest, preserving the I-row markers NEMOSIS counts against. + +Total fixture tree: ~12 MB across ~200 files. + +## Eras + +An era is a single month (or day, for scrape-pattern tables) chosen because it straddles a known +AEMO format change or sits on a year boundary. Each table's fixture covers one or more eras. The +full list lives in `tests/fixtures/spec.py::ERAS`: + +| Era | Purpose | +|-----------|---------------------------------------------------------------------------| +| `2018-05` | Pre-5-min-trading baseline | +| `2020-01` | Year boundary, pre-5-min, monthly bidding | +| `2021-02` | Last viable MMS month for bidding before the 3-year gap | +| `2021-05` | First month of the new 5-min NEM and daily bidding layouts | +| `2022-01` | Year boundary, 5-min dispatch, 30-min trading legacy | +| `2022-06` | Legacy era (kept to avoid fixture churn — could be retired) | +| `2024-09` | First month after `PUBLIC_DVD_` → `PUBLIC_ARCHIVE#` rename | +| `2025-01` | Year boundary, `PUBLIC_ARCHIVE#` format, post-bidding-gap | +| `recent` | Pinned to a date inside AEMO's rolling current-data window | + +The three Jan-1 eras (2020-01, 2022-01, 2025-01) exist primarily for the boundary test matrix — +year wraps are exercised by anchoring boundary cases on Jan 1. + +## The boundary test matrix + +`_boundaries.py` generates a parametrised matrix of (era × flavour × time-of-day) cases for every +dynamic time-series table. Each case asserts entity-set match, exact per-entity row count, +first/last timestamp, no duplicates, and regular stride. + +**Vocabulary:** + +- **Era** — a tagged anchor month (e.g. `2021-05`). +- **T** — time of day for a probe point (e.g. `00:00`, `04:00`, `04:05`). +- **Stride** — the table's native interval, in minutes (5, 30, etc.). +- **Boundary** — midnight on day 1 of the era month — the natural fence between fixture files. +- **Flavour** — how the query window is positioned relative to the boundary: + - `at` — 1h forward window starting at T on day 1. + - `before` — 1h backward window ending at T on day 1. + - `into` — variable window from `last-day-of-prev-month@23:00` to `day1@T`, straddles the + boundary at every T. + +**Tables not covered by the helper** (with reasons, in `_boundaries.py::_HELPER_DOES_NOT_COVER`): + +- `BIDDAYOFFER_D`, `MNSP_DAYOFFER` — daily stride. +- `BIDPEROFFER_D` — partitioned by AEMO trading day (04:05 → 04:00 next day) not calendar day. +- `MNSP_PEROFFER` — multi-dimensional rows + known bug (see Known quirks). +- `DISPATCHCONSTRAINT` — variable per-interval presence. + +These tables keep their per-era smoke tests; only the matrix is skipped. + +## Adding a test for a new table + +1. **Pick eras** the table should cover. Refer to the era table above. Most dynamic time-series + tables use the dispatch set: `2018-05`, `2020-01`, `2021-05`, `2022-01`, `2024-09`, `2025-01` + (the three Jan-1 eras give year-boundary coverage for the matrix). +2. **Add the table to `tests/fixtures/spec.py::DYNAMIC_TABLES`** with its filter spec — which + eras, which row filter columns and values, which columns to keep, etc. +3. **Run `uv run python tests/fixtures/build.py`** to download, filter, and write the fixture + files into `tests/fixtures/data/`. +4. **Write `test_.py`** in `tests/end_to_end_table_tests/`, following the style of + an existing file. Use the `nemosis_fixture` fixture (from `conftest.py`) to get a fresh cache + directory. If the table is suitable for the boundary matrix, parametrise on + `boundary_cases("")` and assert with `assert_boundary_shape(...)`. +5. **Run** `uv run pytest tests/end_to_end_table_tests/test_
.py -v` and iterate. + +## Refreshing fixtures + +Most fixtures are stable and don't need refreshing — the eras are anchored to historical months +that AEMO doesn't change. The exception is the `recent` era, which is pinned to a date inside +AEMO's rolling current-data window (`/Reports/Current/...`). That window only retains the last +few months of data, so the `recent` fixtures age out periodically. + +The committed fixtures keep working indefinitely — staleness only bites when you next try to +*rebuild* (e.g. adding a new table that uses the `recent` era, or refiltering an existing one). +At that point AEMO will return 404s for the scrape-pattern tables, which is the signal to refresh. + +To refresh: + +1. Update `spec.ERAS["recent"]` to a date inside AEMO's current window (within the last ~2 + months is safe). +2. Re-run `uv run python tests/fixtures/build.py`. +3. Commit the updated fixtures. + +Tables that use the `recent` era: `DAILY_REGION_SUMMARY`, `NEXT_DAY_DISPATCHLOAD`, +`INTERMITTENT_GEN_SCADA`. + +## Adding a new era + +When AEMO changes a data format, add an era to capture the transition: + +1. Pick a single month (or a Jan-1 month if you also want year-boundary coverage) that straddles + the change. +2. Add it to `spec.ERAS`. +3. Add the era key to the affected tables' `eras` lists in `spec.DYNAMIC_TABLES`. +4. Re-run `uv run python tests/fixtures/build.py` to fetch the new fixtures. +5. If the format change affects the table's row count, columns, or stride, adjust the test file + accordingly (era-parametrise where behaviour differs). + +## Known quirks + +These have bitten contributors before. Worth a read before adding a test. + +- **`filter_cols` must be a subset of `select_columns`** when calling `dynamic_data_compiler`. +- **`select_columns` for tables wrapped by `drop_duplicates_by_primary_key`** (MNSP_PEROFFER, + MNSP_DAYOFFER, the effective-date config tables) must include every column in + `defaults.table_primary_keys[table]`, or NEMOSIS errors with a `KeyError` on the missing dedupe + columns. SPDCONNECTIONPOINTCONSTRAINT is a particularly easy one to trip on — its primary key + includes BIDTYPE. +- **Prev-month / prev-day buffering** in NEMOSIS's date generator only fires when `start_time` + lands on a natural boundary (first of month, midnight). Tests that probe interior points don't + see those extra buffer requests. +- **`filter_on_effective_date` runs before `most_recent_records_before_start_time`** and drops any + row with `EFFECTIVEDATE >= end_time`. Effective-date fixture entities therefore need at least + one record dated *before* the test's `start_time`, or the test sees an empty frame. +- **Effective-date config tables** have `search_type = "all"` in NEMOSIS, which by default scans + every month from `nem_data_model_start_time` (2009-07) through `end_time` — ~180 HTTP requests + per call. Tests narrow this to a single month by monkeypatching + `defaults.nem_data_model_start_time` to the era date, and opt out of the build-time time trim + via `keep_full_month: True` in `spec.py`. +- **Year-straddle queries aren't covered.** A true year-boundary crossing for a non-Jan era — + e.g. `[2024/08/15, 2025/01/15]` — would exercise `year_and_month_gen`'s `start_year < end_year` + branch with many months of iteration. The fixture doesn't include Sep/Oct/Nov 2024, so such + queries 404 on intervening months. If a future contributor needs this coverage, extend the + fixture to include the intervening months (cost: fixture size) or add a unit test against + `year_and_month_gen` directly. +- **Mid-month queries return empty frames.** The fixture builder keeps only the first 3 and last + 2 days of each era month (see `KEEP_FIRST_DAYS` / `KEEP_LAST_DAYS` in `build.py`). A test that + queries, say, `[2021/05/15, 2021/05/16]` will get back a non-erroring empty DataFrame from every + covered table — NEMOSIS's own code path is fine, the fixture just doesn't have those rows. If + you're writing a test whose window lands in the kept-days gap, either anchor it to day 1–3 / the + last 2 days of the era, or `assert not data.empty` explicitly so the empty case fails loudly + rather than sliding past a soft subset check. +- **`MNSP_PEROFFER` is effectively unreadable past ~April 2021**. AEMO began writing + `MNSP_BIDOFFERPERIOD` data into the existing `PUBLIC_DVD_MNSP_PEROFFER_*` archives, then + renamed the archive stub outright in Aug-2024. NEMOSIS's defaults know neither transition. + Pre-existing bug, tracked in the test file and `spec.py`. `MNSP_DAYOFFER` is unaffected. + +## Parked tables + +- **FCAS_4_SECOND** — AEMO's Causer Pays archive is empty at every URL NEMOSIS tries (existing + upstream issue #64). No fixture, no test. +- **FCAS_4s_SCADA_MAP** — built by `custom_tables.py::fcas4s_scada_match`, which consumes + `FCAS_4_SECOND` values to find lowest-error element↔DUID matches. Blocked on the same upstream + outage. A standalone fixture would mean fabricating the 4-second values the algorithm exists to + match — no meaningful test. diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ad5471a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,94 @@ +"""Shared pytest fixtures. + +`nemosis_fixture` is the workhorse: it spins up a local HTTP server that +serves tests/fixtures/data/ (the committed tree built by build.py), swaps +every AEMO URL in `nemosis.defaults` to point at it, and gives the test +a fresh cache directory. Tests that use this fixture behave exactly as +if they were hitting nemweb.com.au, but in milliseconds and offline. +""" +from __future__ import annotations + +import http.server +import logging +import socket +import threading +from pathlib import Path + +import pytest + +from nemosis import defaults + + +# NEMOSIS's own INFO/DEBUG logs leak into test output and clutter failure +# context. Silence anything below WARNING — genuinely surprising conditions +# (e.g. a 404 on a file we expected to fixture) still surface. +logging.getLogger("nemosis").setLevel(logging.WARNING) + +FIXTURE_ROOT = Path(__file__).parent / "fixtures" / "data" + + +# --------------------------------------------------------------------------- +# Local HTTP server (session-scoped — started once, reused across all tests) +# --------------------------------------------------------------------------- + +class _QuietHandler(http.server.SimpleHTTPRequestHandler): + def log_message(self, *_args, **_kwargs) -> None: + pass # silence the default per-request stderr logging + + +def _pick_free_port() -> int: + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + + +@pytest.fixture(scope="session") +def aemo_mock_server() -> str: + port = _pick_free_port() + + def make_handler(*args, **kwargs): + return _QuietHandler(*args, directory=str(FIXTURE_ROOT), **kwargs) + + server = http.server.ThreadingHTTPServer(("127.0.0.1", port), make_handler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + yield f"http://127.0.0.1:{port}" + finally: + server.shutdown() + thread.join(timeout=5) + + +# --------------------------------------------------------------------------- +# URL redirection + isolated cache dir +# --------------------------------------------------------------------------- + +def _redirect_url(url: str, base: str) -> str: + """Rewrite `https://whatever.com/foo/bar` → `{base}/foo/bar`.""" + after_scheme = url.index("://") + 3 + path_start = url.find("/", after_scheme) + if path_start < 0: + return base + "/" + return base + url[path_start:] + + +@pytest.fixture +def nemosis_fixture(aemo_mock_server, tmp_path, monkeypatch) -> Path: + """Point nemosis at the local mock server and supply a clean cache dir. + + Returns the cache directory (a tmp_path) so tests can pass it to + dynamic_data_compiler / cache_compiler / static_table. + """ + base = aemo_mock_server + + monkeypatch.setattr(defaults, "nem_web_domain_url", base + "/") + monkeypatch.setattr(defaults, "aemo_mms_url", _redirect_url(defaults.aemo_mms_url, base)) + monkeypatch.setattr(defaults, "fcas_4_url", _redirect_url(defaults.fcas_4_url, base)) + monkeypatch.setattr(defaults, "fcas_4_url_hist", _redirect_url(defaults.fcas_4_url_hist, base)) + monkeypatch.setattr( + defaults, "static_table_url", + {k: _redirect_url(v, base) for k, v in defaults.static_table_url.items()}, + ) + monkeypatch.setattr(defaults, "raw_data_cache", str(tmp_path)) + + return tmp_path diff --git a/tests/end_to_end_table_tests/_boundaries.py b/tests/end_to_end_table_tests/_boundaries.py new file mode 100644 index 0000000..769bca5 --- /dev/null +++ b/tests/end_to_end_table_tests/_boundaries.py @@ -0,0 +1,410 @@ +"""Boundary-test helpers — readable rewrite of `_boundaries.py`. + +Same external API: `boundary_cases(table)` returns a list of `BoundaryCase`, +`assert_boundary_shape(...)` checks a returned frame against a case. + +This file is laid out top-down: the two public entry points come first, +followed by helpers in roughly the order they're called. + +---------------------------------------------------------------------------- +Vocabulary used throughout this file: + + era A single calendar month chosen as a test anchor — listed in + `spec.ERAS`. Picked to straddle known AEMO format transitions + (e.g. 2021-05 is the first 5-min-NEM month) or to sit on a + year boundary (2020-01, 2022-01, 2025-01) for the year-wrap + coverage in this matrix. Each era covered by a table + contributes its own (flavour × T) sub-matrix. + + T A time-of-day on day 1 of the era month. The matrix probes + three values per table — see "Three time-of-day probe points" + below. Used to anchor each flavour's window. + + stride The per-row interval, in minutes, of the table at the era in + question. 5 for dispatch tables, 30 for trading tables before + 2021-10 and for ROOFTOP_PV_ACTUAL throughout. `stride_for(table, + era_date)` returns the right value, including the 30→5 switch + TRADINGPRICE/TRADINGINTERCONNECT made on 2021-10-01. + + boundary The calendar boundary between (era_month − 1) and era_month — + i.e. midnight on day 1 of the era. At a January era this IS + the year boundary, which is how Dec→Jan stitch and the date- + generator's `month == 1` buffer-wrap branch get exercised + without a separate flavour family. + + flavour One of three window shapes (`at`, `before`, `into`) — see + below for the full definition of each. + +---------------------------------------------------------------------------- +Three flavours, anchored on T on day 1 of the era month: + + - at [day1@T, day1@T + 1h] + Forward 1h from T. At T == 00:00 this exercises NEMOSIS's + buffer-back branch in `year_and_month_gen` (and buffer-wrap to + December of the prior year at January eras). At T == 04:00 / + 04:05 it's an intra-day stride/clip check on day 1. + + - before [day1@T - 1h, day1@T] + Backward 1h ending at T. Mirror of `at`. At T == 00:00 the + window lands entirely in the previous month (1h before midnight + IS last-day@23:00) and ends exactly at the boundary. At higher + T values the window is intra-day on day 1. + + - into [last-day@23:00, day1@T] + Variable-length window from the last hour of the previous day + into day 1 of the era month. The start is anchored on the + previous day regardless of T, so for T > 00:00 the window + genuinely straddles the boundary and ends at / just past the + market-day-end on day 1. + +Three time-of-day probe points: 00:00, 04:00, and 04:05 for 5-min-stride +tables (or 04:30 for 30-min tables, since :05 isn't a valid stride point). + +---------------------------------------------------------------------------- +Internally this version trades brevity for explicitness: + * `boundary_cases` uses `itertools.product` so the matrix walk is one + flat loop instead of three nested ones. + * Each flavour has its own builder function that computes window and + expected values inline with plain arithmetic — no shared `pd.date_range` + helper to context-switch into. The per-stride math is duplicated three + times (once per flavour) on purpose; each instance is two lines. +""" +from __future__ import annotations + +import sys +from dataclasses import dataclass +from datetime import date, datetime, time, timedelta +from itertools import product +from pathlib import Path + +import pandas as pd + +_FIXTURES_DIR = Path(__file__).resolve().parent.parent / "fixtures" +if str(_FIXTURES_DIR) not in sys.path: + sys.path.insert(0, str(_FIXTURES_DIR)) +import spec # noqa: E402 + + +# =========================================================================== +# Public API +# =========================================================================== + +def boundary_cases(table: str) -> list[BoundaryCase]: + """Generate every (era × flavour × T) case for `table`, based on the + eras listed in `spec.DYNAMIC_TABLES[table]["eras"]`. + + Skipped: + * the `recent` scrape era (handled by separate scrape-pattern tests) + * tables in `_HELPER_DOES_NOT_COVER` (return empty list) + * `flavour=into, T=00:00` cases (identical to `flavour=before, T=00:00`) + """ + if table in _HELPER_DOES_NOT_COVER: + return [] + + # Pre-compute the (era_key, era_date, stride, T) axis for this table. + # This is the only place where stride/era logic lives. Everything below + # this point treats it as opaque coordinates. + matrix = [] + for era_key in spec.DYNAMIC_TABLES[table]["eras"]: + if era_key == "recent": + continue + era_date = spec.ERAS[era_key] + stride = stride_for(table, era_date) + for T in time_points_for_stride(stride): + matrix.append((era_key, era_date, stride, T)) + + # Cartesian product flattens the (era × T) axis against flavours, giving + # one explicit triple per case in a single flat loop. Per-flavour dispatch + # is an inline if-chain so the reader doesn't have to chase a dict lookup. + cases = [] + for (era_key, era_date, stride, T), flavour in product(matrix, FLAVOURS): + if flavour == "into" and T == time(0, 0): + continue # duplicate of `before` at T=00:00 + + if flavour == "at": + case = _build_at_case(table, era_key, era_date, stride, T) + elif flavour == "before": + case = _build_before_case(table, era_key, era_date, stride, T) + else: # "into" + case = _build_into_case(table, era_key, era_date, stride, T) + + cases.append(case) + return cases + + +def assert_boundary_shape( + data: pd.DataFrame, + case: BoundaryCase, + *, + date_col: str, + entities_col: str, + expected_entities: set, +) -> None: + """Assert `data` matches what `case` predicts. Reads top-down as a + checklist of properties the returned frame must have.""" + # Dtype checks — guard against a regression where the datetime column + # comes back as object/string. `sorted()` and `==` on the timestamp + # list below happen to work on strings for AEMO's date format, so + # downstream breakage would be silent without this. + if not data.empty: + assert data[date_col].dtype == "datetime64[ns]", ( + f"{case.id}: {date_col} dtype is {data[date_col].dtype}, " + f"expected datetime64[ns]" + ) + assert data[entities_col].dtype == "object", ( + f"{case.id}: {entities_col} dtype is {data[entities_col].dtype}, " + f"expected object" + ) + + _assert_entity_set_matches(data, case, entities_col, expected_entities) + + for entity in sorted(expected_entities): + per_entity = data[data[entities_col] == entity].sort_values(date_col) + timestamps = per_entity[date_col].tolist() + + _assert_row_count(timestamps, case, entity) + if case.expected_count == 0: + continue + _assert_first_and_last_timestamps(timestamps, case, entity) + _assert_no_duplicate_timestamps(timestamps, case, entity) + _assert_stride_is_regular(timestamps, case, entity) + + +# =========================================================================== +# Configuration constants +# =========================================================================== + +FLAVOURS = ("at", "before", "into") + + +# Tables this helper does NOT cover. Why each is excluded: +# +# BIDDAYOFFER_D, MNSP_DAYOFFER — daily stride, market-day-keyed rows +# that don't fit the per-interval shape. +# BIDPEROFFER_D — bids are partitioned by trading day +# (04:05 → 04:00) not calendar day, so +# a query in calendar 00:00–04:00 lands +# in the previous trading-day archive +# and the helper's row-count assumptions +# don't hold. +# MNSP_PEROFFER — multi-dimensional rows (PERIODID etc.) +# per (LINKID, SETTLEMENTDATE) plus the +# schema-drift bug (issue #68) caps it +# at low coverage anyway. +# +# DISPATCHCONSTRAINT was previously excluded on the theory that a +# constraint may not bind every interval. Probing every era fixture +# showed the pinned CONSTRAINTID `DATASNAP_DFS_Q_CLST` binds all 288 +# intervals on day 1 across 2018–2025, so the matrix shape assumptions +# DO hold for this specific fixture. +_HELPER_DOES_NOT_COVER = { + "BIDDAYOFFER_D", + "MNSP_DAYOFFER", + "BIDPEROFFER_D", + "MNSP_PEROFFER", +} + + +# =========================================================================== +# BoundaryCase data model +# =========================================================================== + +@dataclass(frozen=True) +class BoundaryCase: + """One concrete (era × flavour × T) test case for a given table. + + Fields fall into three groups: + * inputs — which slot in the matrix this case represents + * query — the literal datetimes & stride passed to NEMOSIS + * expected — what the returned frame should look like *per entity* + (per region, per DUID, etc — whichever column the table + is filtered on) + """ + # --- inputs --- + table: str + era_key: str + era_date: date + flavour: str + time_point: time + + # --- query --- + start: datetime + end: datetime + stride_minutes: int + + # --- expected per-entity frame --- + expected_count: int + expected_first: datetime + expected_last: datetime + + @property + def id(self) -> str: + return f"{self.era_key}-T{self.time_point.strftime('%H%M')}-{self.flavour}" + + @property + def start_str(self) -> str: + return self.start.strftime("%Y/%m/%d %H:%M:%S") + + @property + def end_str(self) -> str: + return self.end.strftime("%Y/%m/%d %H:%M:%S") + + +# =========================================================================== +# Stride per (table, era) +# =========================================================================== + +# AEMO's 5-Minute Settlement reform went live on 2021-10-01 — at that point +# TRADINGPRICE & TRADINGINTERCONNECT switched from 30-min to 5-min rows. +_TRADING_5MIN_FROM = date(2021, 10, 1) + + +def stride_for(table: str, era_date: date) -> int: + """Return the per-row stride in minutes for `table` in `era_date`'s era.""" + if table in ("TRADINGPRICE", "TRADINGINTERCONNECT"): + return 5 if era_date >= _TRADING_5MIN_FROM else 30 + if table in ("TRADINGLOAD", "TRADINGREGIONSUM"): + return 30 + if table == "ROOFTOP_PV_ACTUAL": + return 30 + # All dispatch tables — BIDPEROFFER_D / MNSP_PEROFFER are skipped by the + # matrix (see `_HELPER_DOES_NOT_COVER`) so `stride_for` is never called + # for them. + return 5 + + +def time_points_for_stride(stride: int) -> list[time]: + """Three probe times per day. Midnight, market-day-end (04:00), and just + past it. The post-end probe is 04:05 for 5-min strides; for 30-min + tables we use 04:30 since :05 isn't a valid stride point there. + + All returned T values must be stride-aligned — the flavour builders rely + on `window_minutes // stride` producing an exact count, which only holds + when the window endpoints sit on stride boundaries. Enforced here so a + future contributor adding an off-stride T value fails loud, not silent. + """ + if stride == 30: + points = [time(0, 0), time(4, 0), time(4, 30)] + else: + points = [time(0, 0), time(4, 0), time(4, 5)] + for T in points: + assert (T.hour * 60 + T.minute) % stride == 0, ( + f"time point {T} not aligned to {stride}-min stride" + ) + return points + + +# =========================================================================== +# Per-flavour case builders +# =========================================================================== +# Each function builds one BoundaryCase from the matrix coordinates, computing +# the window and expected first/last/count inline. Period-ending convention: +# the first returned row sits at start + stride minutes (the first stride +# boundary strictly after start); the last sits at end (which always lands +# on a stride boundary in our matrix). + +def _build_at_case( + table: str, era_key: str, era_date: date, stride: int, T: time, +) -> BoundaryCase: + """`at` flavour: 1h window starting at T on day 1 of the era month.""" + start = datetime.combine(era_date, T) + end = start + timedelta(hours=1) + return BoundaryCase( + table=table, era_key=era_key, era_date=era_date, + flavour="at", time_point=T, + start=start, end=end, stride_minutes=stride, + expected_count=60 // stride, + expected_first=start + timedelta(minutes=stride), + expected_last=end, + ) + + +def _build_before_case( + table: str, era_key: str, era_date: date, stride: int, T: time, +) -> BoundaryCase: + """`before` flavour: 1h window ending at T on day 1 of the era month.""" + end = datetime.combine(era_date, T) + start = end - timedelta(hours=1) + return BoundaryCase( + table=table, era_key=era_key, era_date=era_date, + flavour="before", time_point=T, + start=start, end=end, stride_minutes=stride, + expected_count=60 // stride, + expected_first=start + timedelta(minutes=stride), + expected_last=end, + ) + + +def _build_into_case( + table: str, era_key: str, era_date: date, stride: int, T: time, +) -> BoundaryCase: + """`into` flavour: window from last-day@23:00 of previous month to T on + day 1 of the era month. Window length varies with T: + T = 00:00 → 1h (= `before` at T=00:00; deduplicated upstream) + T = 04:00 → 5h + T = 04:05 → 5h 5min (5-min-stride tables) + T = 04:30 → 5h 30min (30-min-stride tables) + """ + start = datetime.combine(era_date, time(0, 0)) - timedelta(hours=1) # last-day@23:00 + end = datetime.combine(era_date, T) + window_minutes = int((end - start).total_seconds() // 60) + return BoundaryCase( + table=table, era_key=era_key, era_date=era_date, + flavour="into", time_point=T, + start=start, end=end, stride_minutes=stride, + expected_count=window_minutes // stride, + expected_first=start + timedelta(minutes=stride), + expected_last=end, + ) + + +# =========================================================================== +# Assertion sub-checks +# =========================================================================== + +def _assert_entity_set_matches(data, case, entities_col, expected): + actual = set(data[entities_col]) + assert actual == expected, ( + f"{case.id}: entity set {actual!r} != expected {expected!r}" + ) + + +def _assert_row_count(timestamps, case, entity): + assert len(timestamps) == case.expected_count, ( + f"{case.id} [{entity}]: got {len(timestamps)} rows, " + f"expected {case.expected_count}" + ) + + +def _assert_first_and_last_timestamps(timestamps, case, entity): + assert timestamps[0] == case.expected_first, ( + f"{case.id} [{entity}]: first {timestamps[0]} != " + f"expected {case.expected_first}" + ) + assert timestamps[-1] == case.expected_last, ( + f"{case.id} [{entity}]: last {timestamps[-1]} != " + f"expected {case.expected_last}" + ) + + +def _assert_no_duplicate_timestamps(timestamps, case, entity): + assert len(set(timestamps)) == len(timestamps), ( + f"{case.id} [{entity}]: duplicate timestamps in returned frame" + ) + + +def _assert_stride_is_regular(timestamps, case, entity): + """Every consecutive pair of timestamps should differ by exactly + `case.stride_minutes` — no gaps, no irregular spacing. A case with + fewer than 2 timestamps can't probe stride, so treat it as a + matrix-construction error rather than silently passing.""" + assert case.expected_count >= 2, ( + f"{case.id}: stride regularity needs >=2 rows, got expected_count=" + f"{case.expected_count} — case builder is dropping a stride point" + ) + consecutive_diffs = {b - a for a, b in zip(timestamps, timestamps[1:])} + expected = timedelta(minutes=case.stride_minutes) + assert consecutive_diffs == {expected}, ( + f"{case.id} [{entity}]: irregular stride {consecutive_diffs}, " + f"expected single {expected}" + ) diff --git a/tests/end_to_end_table_tests/_boundaries_deprecated.py b/tests/end_to_end_table_tests/_boundaries_deprecated.py new file mode 100644 index 0000000..9606f09 --- /dev/null +++ b/tests/end_to_end_table_tests/_boundaries_deprecated.py @@ -0,0 +1,366 @@ +"""Boundary-test helpers shared across per-table test files. + +Generates a parametrised matrix of (era × flavour × time-of-day) cases for +each dynamic time-series table, and provides an assertion helper that +checks shape, count, first/last timestamps, and absence of gaps or +duplicates across the boundary. + +Three flavours, each anchored on T (a time-of-day) on day 1 of the era month: + + - at [day1@T, day1@T + 1h] + Forward 1h from T. At T == 00:00 this exercises NEMOSIS's + buffer-back branch in `year_and_month_gen` (and buffer-wrap to + December of the prior year at January eras). At T == 04:00 / + 04:05 it's an intra-day stride/clip check on day 1. + + - before [day1@T - 1h, day1@T] + Backward 1h ending at T. Mirror of `at`. At T == 00:00 the + window lands entirely in the previous month (1h before midnight + IS last-day@23:00) and ends exactly at the boundary. At higher + T values the window is intra-day on day 1. + + - into [last-day@23:00, day1@T] + Variable-length window from the last hour of the previous day + into day 1 of the era month. The start is anchored on the + previous day regardless of T, so for T > 00:00 the window + genuinely straddles the boundary and ends at / just past the + market-day-end on day 1. + +Time-of-day axis: three probe points. Calendar midnight (00:00), market-day +end (04:00), and just past market-day end (04:05 for 5-min-stride tables; +04:30 for 30-min-stride tables since :05 isn't a valid stride point there). +Daily-stride bid tables (BIDDAYOFFER_D, MNSP_DAYOFFER) aren't handled here +— they have different row-count semantics and live in their own per-table +tests. + +Year-wrap coverage falls out for free: the boundary at a January era IS the +year boundary, so flavours at eras 2020-01, 2022-01, and 2025-01 exercise +the Dec→Jan stitch and the date-generator's `month == 1` buffer-wrap branch +without needing a separate flavour family. + +---------------------------------------------------------------------------- +Worked example — what `boundary_cases("DISPATCHPRICE")` generates at the +2020-01 era (one of six eras for that table): + + flavour=at T=00:00 query [2020-01-01 00:00, 2020-01-01 01:00] + 12 rows; buffer-back triggered → fetches Dec 2019 + flavour=at T=04:00 query [2020-01-01 04:00, 2020-01-01 05:00] + 12 rows; intra-day on Jan 1 + flavour=at T=04:05 query [2020-01-01 04:05, 2020-01-01 05:05] + 12 rows; intra-day, off-stride start + flavour=before T=00:00 query [2019-12-31 23:00, 2020-01-01 00:00] + 12 rows; entirely Dec, ends at boundary + flavour=before T=04:00 query [2020-01-01 03:00, 2020-01-01 04:00] + 12 rows; intra-day on Jan 1 + flavour=before T=04:05 query [2020-01-01 03:05, 2020-01-01 04:05] + 12 rows; intra-day, off-stride start + flavour=into T=04:00 query [2019-12-31 23:00, 2020-01-01 04:00] + 60 rows; straddles year boundary + flavour=into T=04:05 query [2019-12-31 23:00, 2020-01-01 04:05] + 61 rows; straddles year boundary + + (`into` at T=00:00 is identical to `before` at T=00:00 — skipped.) + + 8 cases per era × 6 eras = 48 cases for DISPATCHPRICE. +""" +from __future__ import annotations + +import sys +from dataclasses import dataclass +from datetime import date, datetime, time, timedelta +from pathlib import Path + +import pandas as pd + +_FIXTURES_DIR = Path(__file__).resolve().parent.parent / "fixtures" +if str(_FIXTURES_DIR) not in sys.path: + sys.path.insert(0, str(_FIXTURES_DIR)) +import spec # noqa: E402 + + +# =========================================================================== +# Stride per (table, era) +# =========================================================================== + +# TRADINGPRICE & TRADINGINTERCONNECT switched 30-min → 5-min when AEMO's +# 5-Minute Settlement reform went live on 2021-10-01. +_TRADING_5MIN_FROM = date(2021, 10, 1) + + +def stride_for(table: str, era_date: date) -> int: + """Return the per-row stride in minutes for `table` in `era_date`'s era.""" + if table in ("TRADINGPRICE", "TRADINGINTERCONNECT"): + return 5 if era_date >= _TRADING_5MIN_FROM else 30 + if table in ("TRADINGLOAD", "TRADINGREGIONSUM"): + return 30 + if table == "ROOFTOP_PV_ACTUAL": + return 30 + # All dispatch tables, BIDPEROFFER_D, MNSP_PEROFFER. + return 5 + + +# Tables this helper does NOT cover (boundary tests live in the per-table +# files instead, or are skipped entirely): +# +# BIDDAYOFFER_D, MNSP_DAYOFFER — daily stride, market-day-keyed rows +# that don't fit the per-interval shape +# BIDPEROFFER_D — bids are partitioned by trading day +# (04:05 → 04:00) not calendar day, so +# a query in calendar 00:00–04:00 lands +# in the previous trading-day archive +# and the helper's row-count assumptions +# don't hold +# MNSP_PEROFFER — multi-dimensional rows (PERIODID etc.) +# per (LINKID, SETTLEMENTDATE) plus the +# schema-drift bug (issue #68) caps it +# at low coverage anyway +# DISPATCHCONSTRAINT — a constraint isn't binding every +# interval, so per-interval row counts +# are non-deterministic +_HELPER_DOES_NOT_COVER = { + "BIDDAYOFFER_D", + "MNSP_DAYOFFER", + "BIDPEROFFER_D", + "MNSP_PEROFFER", + "DISPATCHCONSTRAINT", +} + + +def _time_points_for_stride(stride: int) -> list[time]: + """Three probe times per day. Midnight, market-day-end (04:00), and just + past it. The post-end probe is 04:05 for 5-min strides; for 30-min + tables we use 04:30 since :05 isn't a valid stride point there.""" + if stride == 30: + return [time(0, 0), time(4, 0), time(4, 30)] + return [time(0, 0), time(4, 0), time(4, 5)] + + +# =========================================================================== +# Window construction — one explicit function per flavour +# =========================================================================== + +def _at_window(era_date: date, T: time) -> tuple[datetime, datetime]: + """`at` flavour: forward 1h from T on day 1 of the era month.""" + anchor = datetime.combine(era_date, T) + return anchor, anchor + timedelta(hours=1) + + +def _before_window(era_date: date, T: time) -> tuple[datetime, datetime]: + """`before` flavour: backward 1h ending at T on day 1 of the era month.""" + anchor = datetime.combine(era_date, T) + return anchor - timedelta(hours=1), anchor + + +def _into_window(era_date: date, T: time) -> tuple[datetime, datetime]: + """`into` flavour: from last-day@23:00 to T on day 1 of the era month.""" + last_day_23 = datetime.combine(era_date, time(0, 0)) - timedelta(hours=1) + anchor = datetime.combine(era_date, T) + return last_day_23, anchor + + +_FLAVOUR_BUILDERS = { + "at": _at_window, + "before": _before_window, + "into": _into_window, +} + + +# =========================================================================== +# Case generation +# =========================================================================== + +@dataclass(frozen=True) +class BoundaryCase: + """One concrete (era × flavour × T) test case for a given table. + + Fields fall into three groups: + * inputs — which slot in the matrix this case represents + * query — the literal datetimes & stride passed to NEMOSIS + * expected — what the returned frame should look like *per entity* + (per region, per DUID, etc — whichever column the table + is filtered on) + """ + # --- inputs --- + table: str + era_key: str + era_date: date + flavour: str # "at" | "before" | "into" + time_point: time + + # --- query --- + start: datetime + end: datetime + stride_minutes: int + + # --- expected per-entity frame --- + expected_count: int + expected_first: datetime + expected_last: datetime + + @property + def id(self) -> str: + return f"{self.era_key}-T{self.time_point.strftime('%H%M')}-{self.flavour}" + + @property + def start_str(self) -> str: + return self.start.strftime("%Y/%m/%d %H:%M:%S") + + @property + def end_str(self) -> str: + return self.end.strftime("%Y/%m/%d %H:%M:%S") + + +def boundary_cases(table: str) -> list[BoundaryCase]: + """Generate every (era × flavour × T) case for `table`, based on the + eras listed in `spec.DYNAMIC_TABLES[table]["eras"]`. + + Skipped: + * the `recent` scrape era (handled by separate scrape-pattern tests) + * daily-stride bid tables (see `_DAILY_TABLES`) + * `flavour=into, T=00:00` cases (identical to `flavour=before, T=00:00`) + """ + if table in _HELPER_DOES_NOT_COVER: + return [] + + eras = spec.DYNAMIC_TABLES[table]["eras"] + cases: list[BoundaryCase] = [] + + for era_key in eras: + if era_key == "recent": + continue + era_date = spec.ERAS[era_key] + stride = stride_for(table, era_date) + + for T in _time_points_for_stride(stride): + for flavour, build_window in _FLAVOUR_BUILDERS.items(): + if _is_duplicate_into_at_midnight(flavour, T): + continue + start, end = build_window(era_date, T) + first, last, n = _expected_period_endings(start, end, stride) + cases.append(BoundaryCase( + table=table, + era_key=era_key, + era_date=era_date, + flavour=flavour, + time_point=T, + start=start, + end=end, + stride_minutes=stride, + expected_count=n, + expected_first=first, + expected_last=last, + )) + return cases + + +def _is_duplicate_into_at_midnight(flavour: str, T: time) -> bool: + """At T == 00:00, `into` and `before` produce the same window + ([last-day@23:00, day1@00:00]) — `into`'s start (last-day@23:00) IS + `before`'s start (anchor − 1h, where anchor = midnight). Skip the + duplicate so the matrix doesn't double-count it.""" + return flavour == "into" and T == time(0, 0) + + +def _expected_period_endings( + start: datetime, end: datetime, stride: int, +) -> tuple[datetime, datetime, int]: + """Compute the period-ending timestamps NEMOSIS should return for a + query [start, end] on a `stride`-minute table. + + AEMO settlement timestamps are period-ending: a row labelled 04:05 + represents the interval 04:00–04:05. For a query starting at 04:00, + the first matching row is 04:05; for a query starting at 04:05, the + first matching row is 04:10 (because 04:05 is at-or-before the start, + not after it). + + We model that with a pandas date_range over [start, end] at the table's + stride and discard any timestamp equal to `start`. Assumes `start` and + `end` are both stride-aligned, which holds for every case in our matrix + (T values are always 00:00, 04:00, 04:05, or 04:30). + + Returns (first_timestamp, last_timestamp, count). When count == 0 + (window narrower than one stride), first/last are returned as `start` + as a harmless placeholder — assertion code skips first/last checks + when count is zero. + """ + all_points = pd.date_range(start=start, end=end, freq=f"{stride}min") + period_endings = all_points[all_points > pd.Timestamp(start)] + if len(period_endings) == 0: + return start, start, 0 + return ( + period_endings[0].to_pydatetime(), + period_endings[-1].to_pydatetime(), + len(period_endings), + ) + + +# =========================================================================== +# Assertion — split into named sub-checks +# =========================================================================== + +def assert_boundary_shape( + data: pd.DataFrame, + case: BoundaryCase, + *, + date_col: str, + entities_col: str, + expected_entities: set, +) -> None: + """Assert `data` matches what `case` predicts. Reads top-down as a + checklist of properties the returned frame must have.""" + _assert_entity_set_matches(data, case, entities_col, expected_entities) + + for entity in sorted(expected_entities): + per_entity = data[data[entities_col] == entity].sort_values(date_col) + timestamps = per_entity[date_col].tolist() + + _assert_row_count(timestamps, case, entity) + if case.expected_count == 0: + continue + _assert_first_and_last_timestamps(timestamps, case, entity) + _assert_no_duplicate_timestamps(timestamps, case, entity) + _assert_stride_is_regular(timestamps, case, entity) + + +def _assert_entity_set_matches(data, case, entities_col, expected): + actual = set(data[entities_col]) + assert actual == expected, ( + f"{case.id}: entity set {actual!r} != expected {expected!r}" + ) + + +def _assert_row_count(timestamps, case, entity): + assert len(timestamps) == case.expected_count, ( + f"{case.id} [{entity}]: got {len(timestamps)} rows, " + f"expected {case.expected_count}" + ) + + +def _assert_first_and_last_timestamps(timestamps, case, entity): + assert timestamps[0] == case.expected_first, ( + f"{case.id} [{entity}]: first {timestamps[0]} != " + f"expected {case.expected_first}" + ) + assert timestamps[-1] == case.expected_last, ( + f"{case.id} [{entity}]: last {timestamps[-1]} != " + f"expected {case.expected_last}" + ) + + +def _assert_no_duplicate_timestamps(timestamps, case, entity): + assert len(set(timestamps)) == len(timestamps), ( + f"{case.id} [{entity}]: duplicate timestamps in returned frame" + ) + + +def _assert_stride_is_regular(timestamps, case, entity): + """Every consecutive pair of timestamps should differ by exactly + `case.stride_minutes` — no gaps, no irregular spacing.""" + consecutive_diffs = {b - a for a, b in zip(timestamps, timestamps[1:])} + if not consecutive_diffs: + return # only one row — nothing to compare + expected = timedelta(minutes=case.stride_minutes) + assert consecutive_diffs == {expected}, ( + f"{case.id} [{entity}]: irregular stride {consecutive_diffs}, " + f"expected single {expected}" + ) diff --git a/tests/end_to_end_table_tests/test_bid_day_offer_d.py b/tests/end_to_end_table_tests/test_bid_day_offer_d.py new file mode 100644 index 0000000..da55a76 --- /dev/null +++ b/tests/end_to_end_table_tests/test_bid_day_offer_d.py @@ -0,0 +1,45 @@ +"""Tests for BIDDAYOFFER_D. + +Per-unit daily bid offers (price bands, daily energy constraint, +rebid explanations). MMS monthly archive at every era — the archive +just has a hole between March 2021 and July 2024 that we skip over. + +Fixtured eras: 2018-05, 2021-02, 2024-09; DUID ∈ {AGLHAL, HDWF2}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + + +EXPECTED_BIDTYPES = { + "ENERGY", "RAISEREG", "LOWERREG", + "RAISE5MIN", "RAISE60SEC", "LOWER5MIN", "LOWER60SEC", +} + + +@pytest.mark.parametrize("era_start,prev_day", [ + ("2018/05/01 00:00:00", "2018-04-30"), + ("2021/02/01 00:00:00", "2021-01-31"), + ("2024/09/01 00:00:00", "2024-08-31"), +]) +def test_day_offers_at_calendar_boundary(nemosis_fixture, era_start, prev_day): + """At calendar midnight of day 1, NEMOSIS returns the previous trading + day's bids (trading-day convention — day 1's bid doesn't start until + 04:05). AGLHAL is energy-only; HDWF2 bids every FCAS service as well.""" + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="BIDDAYOFFER_D", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "BIDTYPE"], + ) + + assert len(data) == 8 + assert set(data["DUID"]) == {"AGLHAL", "HDWF2"} + assert set(data["SETTLEMENTDATE"]) == {pd.Timestamp(prev_day)} + assert set(data[data["DUID"] == "AGLHAL"]["BIDTYPE"]) == {"ENERGY"} + assert set(data[data["DUID"] == "HDWF2"]["BIDTYPE"]) == EXPECTED_BIDTYPES diff --git a/tests/end_to_end_table_tests/test_bid_per_offer_d.py b/tests/end_to_end_table_tests/test_bid_per_offer_d.py new file mode 100644 index 0000000..d6ec302 --- /dev/null +++ b/tests/end_to_end_table_tests/test_bid_per_offer_d.py @@ -0,0 +1,68 @@ +"""Tests for BIDPEROFFER_D. + +Per-unit per-five-minute bid offers (up to 10 price bands). The MMS +monthly archive is the source at every era NEMOSIS supports — AEMO +stopped publishing these files between March 2021 and July 2024, so +coverage jumps over that gap. + +Boundary tests are NOT generated for this table — bids are partitioned +by AEMO trading day (04:05 → 04:00 next day), not calendar day, so +calendar-aligned queries between 00:00 and 04:00 fall in the previous +trading-day archive and our boundary helper's row-count assertions +break. See `_boundaries.py::_HELPER_DOES_NOT_COVER` for the full list +of skipped tables. + +DUID ∈ {AGLHAL, HDWF2}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +FIXTURED_DUIDS = {"AGLHAL", "HDWF2"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/02/01 00:00:00", + "2024/09/01 00:00:00", +]) +def test_trading_day_buffer_back_at_start_of_month(nemosis_fixture, era_start): + """A calendar-midnight query on day 1 must reach into the prev-month + archive for the prior trading day's 00:05 → 04:00 rows and stitch them + to day 1's 04:05 → onward rows. The [day1 00:00, day1 05:15] window is + the canonical shape that exercises this — the buffer-back only fires + when start_time is exactly first-of-month midnight (see issue #70).""" + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=5, minutes=15) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="BIDPEROFFER_D", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERVAL_DATETIME", "DUID", "BIDTYPE", "MAXAVAIL"], + filter_cols=["DUID", "BIDTYPE"], + filter_values=(["AGLHAL"], ["ENERGY"]), + ) + + timestamps = sorted(data["INTERVAL_DATETIME"].tolist()) + assert len(timestamps) == 63 # 5h15min / 5min + assert timestamps[0] == start + pd.Timedelta(minutes=5) # 00:05 + assert timestamps[-1] == start + pd.Timedelta(hours=5, minutes=15) # 05:15 + diffs = {b - a for a, b in zip(timestamps, timestamps[1:])} + assert diffs == {pd.Timedelta(minutes=5)} + + +def test_bids_returned_for_fixtured_duids(nemosis_fixture): + """Smoke: both DUIDs present in a simple 1h window.""" + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 01:00:00", + table_name="BIDPEROFFER_D", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERVAL_DATETIME", "DUID", "BIDTYPE", "MAXAVAIL"], + ) + + assert not data.empty + assert set(data["DUID"]) <= FIXTURED_DUIDS diff --git a/tests/end_to_end_table_tests/test_cache_compiler.py b/tests/end_to_end_table_tests/test_cache_compiler.py new file mode 100644 index 0000000..ee715d0 --- /dev/null +++ b/tests/end_to_end_table_tests/test_cache_compiler.py @@ -0,0 +1,59 @@ +"""Tests for `cache_compiler` — the public sibling of `dynamic_data_compiler` +that writes typed feather / parquet files to disk for downstream consumers. + +Verifies (a) the round-trip through cache → dynamic_data_compiler preserves +typed columns and (b) `select_columns` narrows the cached file itself, not +just the returned frame. +""" +import pandas as pd +import pytest + +from nemosis import cache_compiler, dynamic_data_compiler + + +START = "2018/05/01 00:00:00" +END = "2018/05/01 00:30:00" + + +@pytest.mark.parametrize("fformat", ["feather", "parquet"]) +def test_round_trip_preserves_dtypes(nemosis_fixture, fformat): + cache_compiler( + start_time=START, end_time=END, + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + fformat=fformat, + ) + data = dynamic_data_compiler( + start_time=START, end_time=END, + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + fformat=fformat, + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + assert not data.empty + assert data["SETTLEMENTDATE"].dtype == "datetime64[ns]" + assert data["REGIONID"].dtype == "object" + # If all dtypes were object, typing failed somewhere in the pipeline. + assert not all(data.dtypes == "object") + + +def test_select_columns_narrows_cached_file(nemosis_fixture): + """`cache_compiler` with `select_columns` should write only those + columns to the cached file, not just filter on read.""" + cache_compiler( + start_time=START, end_time=END, + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + fformat="parquet", + select_columns=["SETTLEMENTDATE", "REGIONID"], + rebuild=True, + ) + # Cache writes one parquet per fetched month — NEMOSIS's prev-month + # buffer means day-1 midnight queries always pull April too. + parquet_files = list(nemosis_fixture.glob("*.parquet")) + assert len(parquet_files) >= 1, parquet_files + for path in parquet_files: + on_disk = pd.read_parquet(path) + assert set(on_disk.columns) == {"SETTLEMENTDATE", "REGIONID"}, path diff --git a/tests/end_to_end_table_tests/test_daily_region_summary.py b/tests/end_to_end_table_tests/test_daily_region_summary.py new file mode 100644 index 0000000..d23fa54 --- /dev/null +++ b/tests/end_to_end_table_tests/test_daily_region_summary.py @@ -0,0 +1,68 @@ +"""Tests for DAILY_REGION_SUMMARY. + +Scrape-pattern table: NEMOSIS fetches AEMO's rolling current-data index at +/Reports/Current/Daily_Reports/, finds the filename matching a date stub, +downloads the zip, and extracts the DAILY_REGION_SUMMARY section from the +multi-table PUBLIC_DAILY file. + +The fixture freezes one recent day (see ERAS["recent"] in spec.py) plus +the prior day (NEMOSIS's 1-day buffer). Unrelated sections of PUBLIC_DAILY +have been stripped to keep the fixture small. +""" +from nemosis import dynamic_data_compiler + + +import pandas as pd + + +def test_recent_day_returns_rows_for_filtered_regions(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 00:00:00", + end_time="2026/03/15 01:00:00", + table_name="DAILY_REGION_SUMMARY", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "TOTALDEMAND"], + ) + + assert not data.empty + assert set(data["REGIONID"]) == {"SA1", "NSW1"} + + +# --------------------------------------------------------------------------- +# Market-day boundary at 04:00. Daily files cover `{date} 04:05 → {date+1} +# 04:00` — so the 04:00 row lives in the *previous* daily file. NEMOSIS's +# `current_gen` always subtracts 1 day (unconditional buffer-back), so a +# query ending at 04:00 must still pull the prior day's file to retrieve +# that final row. +# --------------------------------------------------------------------------- + +def test_end_market_day_returns_0400_row_from_previous_file(nemosis_fixture): + """[03:55, 04:00]: filter is start-exclusive end-inclusive, so only + the 04:00 row qualifies — and it lives in the `20260314` daily file, + not `20260315`. Non-empty proves buffer-back fired correctly.""" + data = dynamic_data_compiler( + start_time="2026/03/15 03:55:00", + end_time="2026/03/15 04:00:00", + table_name="DAILY_REGION_SUMMARY", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "TOTALDEMAND"], + ) + assert set(data["SETTLEMENTDATE"].unique()) == {pd.Timestamp("2026-03-15 04:00:00")} + assert set(data["REGIONID"]) == {"SA1", "NSW1"} + assert not data.duplicated(["SETTLEMENTDATE", "REGIONID"]).any() + + +def test_start_market_day_returns_0405_row_from_current_file(nemosis_fixture): + """[04:00, 04:05]: start-exclusive means 04:00 is dropped; 04:05 is + the first row of the `20260315` daily file. Tests the stitch from + the consumer side — the current day's file must be fetched.""" + data = dynamic_data_compiler( + start_time="2026/03/15 04:00:00", + end_time="2026/03/15 04:05:00", + table_name="DAILY_REGION_SUMMARY", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "TOTALDEMAND"], + ) + assert set(data["SETTLEMENTDATE"].unique()) == {pd.Timestamp("2026-03-15 04:05:00")} + assert set(data["REGIONID"]) == {"SA1", "NSW1"} + assert not data.duplicated(["SETTLEMENTDATE", "REGIONID"]).any() diff --git a/tests/end_to_end_table_tests/test_dispatch_constraint.py b/tests/end_to_end_table_tests/test_dispatch_constraint.py new file mode 100644 index 0000000..a7f66f1 --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_constraint.py @@ -0,0 +1,64 @@ +"""Tests for DISPATCHCONSTRAINT. + +Per-constraint five-minute state (RHS, marginal value, LHS terms). The +pinned CONSTRAINTID `DATASNAP_DFS_Q_CLST` binds every 5-min interval on +day 1 across every fixtured era (verified empirically 2018-05 → 2025-01), +so the standard boundary matrix applies here. + +Fixtured eras: 2018-05, 2020-01, 2021-05, 2022-01, 2024-08, 2025-01; +CONSTRAINTID ∈ {DATASNAP_DFS_Q_CLST}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_CONSTRAINTS = {"DATASNAP_DFS_Q_CLST"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_constraint_rows_are_within_requested_window(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=2) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCHCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "CONSTRAINTID", "INTERVENTION", "RHS"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert set(data["CONSTRAINTID"]) == FIXTURED_CONSTRAINTS + assert data["SETTLEMENTDATE"].min() >= start + assert data["SETTLEMENTDATE"].max() <= end + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCHCONSTRAINT"), ids=lambda c: c.id +) +def test_dispatch_constraint_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCHCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "CONSTRAINTID", "INTERVENTION", "RHS"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="CONSTRAINTID", + expected_entities=FIXTURED_CONSTRAINTS, + ) diff --git a/tests/end_to_end_table_tests/test_dispatch_interconnectorres.py b/tests/end_to_end_table_tests/test_dispatch_interconnectorres.py new file mode 100644 index 0000000..73c702b --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_interconnectorres.py @@ -0,0 +1,66 @@ +"""Tests for DISPATCHINTERCONNECTORRES. + +Per-interconnector five-minute flow data (MW, losses, constraints). +Has INTERVENTION column like other dispatch tables. + +Boundary matrix (`test_dispatch_interconnectorres_boundary`) is +auto-generated from `spec.DYNAMIC_TABLES["DISPATCHINTERCONNECTORRES"] +["eras"]` by `_boundaries.py`. + +INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_INTERCONNECTORS = {"VIC1-NSW1"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_gives_twelve_intervals_per_interconnector(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + expected = [start + pd.Timedelta(minutes=5 * i) for i in range(1, 13)] + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCHINTERCONNECTORRES", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "INTERCONNECTORID", "INTERVENTION", "METEREDMWFLOW"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert set(data["INTERCONNECTORID"]) == FIXTURED_INTERCONNECTORS + timestamps = sorted(data["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCHINTERCONNECTORRES"), ids=lambda c: c.id +) +def test_dispatch_interconnectorres_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCHINTERCONNECTORRES", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "INTERCONNECTORID", "INTERVENTION", "METEREDMWFLOW"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="INTERCONNECTORID", + expected_entities=FIXTURED_INTERCONNECTORS, + ) diff --git a/tests/end_to_end_table_tests/test_dispatch_load.py b/tests/end_to_end_table_tests/test_dispatch_load.py new file mode 100644 index 0000000..a920c48 --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_load.py @@ -0,0 +1,82 @@ +"""Tests for the DISPATCHLOAD dispatch table. + +DISPATCHLOAD publishes one row per dispatchable unit (DUID) per five-minute +interval, giving per-unit dispatch targets, ramp rates, availability, etc. +Like DISPATCHPRICE it has both a "normal" and an "intervention" run per +interval; the tests filter INTERVENTION == 0 to pin the normal run. + +Boundary matrix (`test_dispatch_load_boundary`) is auto-generated from +`spec.DYNAMIC_TABLES["DISPATCHLOAD"]["eras"]` by `_boundaries.py`. + +The fixture pre-filters rows to DUID ∈ {AGLHAL, HDWF2}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_DUIDS = {"AGLHAL", "HDWF2"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_gives_twelve_intervals_per_duid(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + expected = [start + pd.Timedelta(minutes=5 * i) for i in range(1, 13)] + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INTERVENTION", "INITIALMW"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert set(data["DUID"]) == FIXTURED_DUIDS + for duid in FIXTURED_DUIDS: + timestamps = sorted(data[data["DUID"] == duid]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +def test_duid_filter_narrows_to_one_unit(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 01:00:00", + table_name="DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + filter_cols=["DUID"], + filter_values=(["AGLHAL"],), + ) + + assert set(data["DUID"]) == {"AGLHAL"} + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCHLOAD"), ids=lambda c: c.id +) +def test_dispatch_load_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INTERVENTION", "INITIALMW"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="DUID", + expected_entities=FIXTURED_DUIDS, + ) diff --git a/tests/end_to_end_table_tests/test_dispatch_price.py b/tests/end_to_end_table_tests/test_dispatch_price.py new file mode 100644 index 0000000..f9b8a0e --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_price.py @@ -0,0 +1,138 @@ +"""Tests for the DISPATCHPRICE dispatch table. + +DISPATCHPRICE publishes one row per region per five-minute dispatch interval. +Settlement timestamps are period-ending, so a request for 00:00–01:00 returns +the twelve intervals labelled 00:05 through 01:00. + +The fixture pre-filters rows to REGIONID ∈ {SA1, NSW1} and covers six AEMO +eras: + 2018-05 — pre-5-min-trading baseline + 2020-01 — year boundary, pre-5-min format + 2021-05 — first month of the daily BIDMOVE_COMPLETE bidding layout + 2022-01 — year boundary, 5-min dispatch + 2024-08 — PUBLIC_DVD → PUBLIC_ARCHIVE# filename cutover + 2025-01 — year boundary, PUBLIC_ARCHIVE# format + +The boundary matrix (`test_dispatch_price_boundary`) is auto-generated from +`spec.DYNAMIC_TABLES["DISPATCHPRICE"]["eras"]` by +`tests/end_to_end_table_tests/_boundaries.py` — see that module for flavour +and time-of-day semantics. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_REGIONS = {"SA1", "NSW1"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_gives_twelve_intervals_per_region(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + expected = [start + pd.Timedelta(minutes=5 * i) for i in range(1, 13)] + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], # exclude intervention dispatch runs, + filter_values=([0],), # which happen sporadically + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + timestamps = sorted(data[data["REGIONID"] == region]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +def test_region_filter_narrows_to_one_region(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 01:00:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + filter_cols=["REGIONID"], + filter_values=(["SA1"],), + ) + + assert set(data["REGIONID"]) == {"SA1"} + + +def test_select_columns_returns_only_those_columns(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 00:30:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "RRP"], + ) + + assert list(data.columns) == ["SETTLEMENTDATE", "RRP"] + + +def test_multi_day_cross_month_query(nemosis_fixture): + """3-day window straddling Aug→Sep 2024 exercises `year_and_month_gen`'s + multi-month iteration (which the boundary matrix's <=5.5h windows + don't reach). 3 days × 288 intervals = 864 timestamps per region.""" + data = dynamic_data_compiler( + start_time="2024/08/30 00:00:00", + end_time="2024/09/02 00:00:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + timestamps = sorted(data[data["REGIONID"] == region]["SETTLEMENTDATE"].tolist()) + assert len(timestamps) == 864 + assert timestamps[0] == pd.Timestamp("2024-08-30 00:05:00") + assert timestamps[-1] == pd.Timestamp("2024-09-02 00:00:00") + + +def test_sub_stride_window_returns_empty(nemosis_fixture): + """30-second window falls between 5-min stride points — should return + an empty frame (not error), confirming the filter layer handles + zero-row cases without surfacing an exception.""" + data = dynamic_data_compiler( + start_time="2024/08/01 00:30:00", + end_time="2024/08/01 00:30:30", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + ) + assert data.empty + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCHPRICE"), ids=lambda c: c.id +) +def test_dispatch_price_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="REGIONID", + expected_entities=FIXTURED_REGIONS, + ) diff --git a/tests/end_to_end_table_tests/test_dispatch_region_sum.py b/tests/end_to_end_table_tests/test_dispatch_region_sum.py new file mode 100644 index 0000000..86bfb46 --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_region_sum.py @@ -0,0 +1,68 @@ +"""Tests for DISPATCHREGIONSUM. + +Per-region five-minute summary: total demand, cleared generation, FCAS +enablements, etc. Has INTERVENTION column like DISPATCHPRICE, so tests +filter INTERVENTION == 0 to pin the normal-run timeline. + +Boundary matrix (`test_dispatch_region_sum_boundary`) is auto-generated +from `spec.DYNAMIC_TABLES["DISPATCHREGIONSUM"]["eras"]` by +`_boundaries.py`. + +REGIONID ∈ {SA1, NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_REGIONS = {"SA1", "NSW1"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_gives_twelve_intervals_per_region(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + expected = [start + pd.Timedelta(minutes=5 * i) for i in range(1, 13)] + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCHREGIONSUM", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "INTERVENTION", "TOTALDEMAND"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + timestamps = sorted(data[data["REGIONID"] == region]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCHREGIONSUM"), ids=lambda c: c.id +) +def test_dispatch_region_sum_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCHREGIONSUM", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "INTERVENTION", "TOTALDEMAND"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="REGIONID", + expected_entities=FIXTURED_REGIONS, + ) diff --git a/tests/end_to_end_table_tests/test_dispatch_unit_scada.py b/tests/end_to_end_table_tests/test_dispatch_unit_scada.py new file mode 100644 index 0000000..e1c7cc1 --- /dev/null +++ b/tests/end_to_end_table_tests/test_dispatch_unit_scada.py @@ -0,0 +1,64 @@ +"""Tests for DISPATCH_UNIT_SCADA. + +Per-unit measured output (SCADAVALUE) at five-minute resolution. Unlike +DISPATCHLOAD this table has no INTERVENTION column, so every interval +produces exactly one row per DUID. + +Boundary matrix (`test_dispatch_unit_scada_boundary`) is auto-generated +from `spec.DYNAMIC_TABLES["DISPATCH_UNIT_SCADA"]["eras"]` by +`_boundaries.py`. + +Filtered to DUID ∈ {AGLHAL, HDWF2}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_DUIDS = {"AGLHAL", "HDWF2"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_gives_twelve_intervals_per_duid(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + expected = [start + pd.Timedelta(minutes=5 * i) for i in range(1, 13)] + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="DISPATCH_UNIT_SCADA", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "SCADAVALUE"], + ) + + assert set(data["DUID"]) == FIXTURED_DUIDS + for duid in FIXTURED_DUIDS: + timestamps = sorted(data[data["DUID"] == duid]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +@pytest.mark.parametrize( + "case", boundary_cases("DISPATCH_UNIT_SCADA"), ids=lambda c: c.id +) +def test_dispatch_unit_scada_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="DISPATCH_UNIT_SCADA", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "SCADAVALUE"], + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="DUID", + expected_entities=FIXTURED_DUIDS, + ) diff --git a/tests/end_to_end_table_tests/test_dudetail.py b/tests/end_to_end_table_tests/test_dudetail.py new file mode 100644 index 0000000..f02af4b --- /dev/null +++ b/tests/end_to_end_table_tests/test_dudetail.py @@ -0,0 +1,24 @@ +"""Tests for DUDETAIL. + +Effective-date config table: registration details per dispatchable unit +(connection point, registered capacity, station ID, etc.). +search_type="all". + +Fixtured eras: 2021-05; DUID ∈ {AGLHAL, HDWF2}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_dudetail_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="DUDETAIL", + raw_data_location=str(nemosis_fixture), + select_columns=["DUID", "EFFECTIVEDATE", "VERSIONNO", "CONNECTIONPOINTID", "REGISTEREDCAPACITY"], + ) + + assert not data.empty + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} diff --git a/tests/end_to_end_table_tests/test_dudetailsummary.py b/tests/end_to_end_table_tests/test_dudetailsummary.py new file mode 100644 index 0000000..c3b5f40 --- /dev/null +++ b/tests/end_to_end_table_tests/test_dudetailsummary.py @@ -0,0 +1,46 @@ +"""Tests for DUDETAILSUMMARY. + +Effective-date config table summarising DUDETAIL into start/end date +ranges per DUID (more compact than DUDETAIL for participant lookups). +search_type="end" — fetches archives only up to end_time. + +Fixtured eras: 2021-05; DUID ∈ {AGLHAL, HDWF2}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_dudetailsummary_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="DUDETAILSUMMARY", + raw_data_location=str(nemosis_fixture), + select_columns=["DUID", "START_DATE", "END_DATE", "DISPATCHTYPE", "REGIONID"], + ) + + assert not data.empty + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} + + +def test_narrow_window_one_row_per_duid(nemosis_fixture, monkeypatch): + """`filter_on_start_date` should collapse to one effective row per DUID + at any single instant in time. Grouping by PK minus START_DATE must + show no duplicates — otherwise two versions of the same DUID are + leaking through the effective-date filter.""" + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + pk = defaults.table_primary_keys["DUDETAILSUMMARY"] + group_cols = [c for c in pk if c != "START_DATE"] + + data = dynamic_data_compiler( + start_time="2021/05/20 23:00:00", + end_time="2021/05/20 23:05:00", + table_name="DUDETAILSUMMARY", + raw_data_location=str(nemosis_fixture), + select_columns=pk, + ) + + assert not data.empty + assert not data.duplicated(group_cols).any(), data[data.duplicated(group_cols, keep=False)] diff --git a/tests/end_to_end_table_tests/test_fformat_csv.py b/tests/end_to_end_table_tests/test_fformat_csv.py new file mode 100644 index 0000000..d99d21a --- /dev/null +++ b/tests/end_to_end_table_tests/test_fformat_csv.py @@ -0,0 +1,45 @@ +"""Coverage for `dynamic_data_compiler(fformat="csv")`. + +The CSV path is materially different from feather/parquet — it skips the +cache-read/cache-write branch entirely and re-parses the raw AEMO CSV on +every call. Feather/parquet coverage lives in `test_cache_compiler.py`; +this file locks in that the CSV branch doesn't silently break. + +`cache_compiler` only accepts feather/parquet, so CSV is exclusively a +`dynamic_data_compiler` concern. +""" +from nemosis import dynamic_data_compiler + + +def test_csv_fformat_round_trip(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 00:30:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + fformat="csv", + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + assert not data.empty + assert data["SETTLEMENTDATE"].dtype == "datetime64[ns]" + assert data["REGIONID"].dtype == "object" + assert not all(data.dtypes == "object") + + +def test_keep_csv_false_leaves_cache_empty(nemosis_fixture): + """With `fformat="csv"` and `keep_csv=False`, the raw CSV is fetched, + used to produce the return frame, and then deleted — the cache dir + should be empty afterwards. This is the opt-out path for users who + don't want NEMOSIS accumulating files on disk.""" + dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 00:30:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + fformat="csv", + keep_csv=False, + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + ) + assert list(nemosis_fixture.iterdir()) == [] diff --git a/tests/end_to_end_table_tests/test_gencondata.py b/tests/end_to_end_table_tests/test_gencondata.py new file mode 100644 index 0000000..6e064bb --- /dev/null +++ b/tests/end_to_end_table_tests/test_gencondata.py @@ -0,0 +1,23 @@ +"""Tests for GENCONDATA. + +Effective-date config table: master list of generic constraints +(constraint type, RHS, weight, description). search_type="all". + +Fixtured eras: 2021-05; GENCONID ∈ {#NSW1-QLD1_RAMP_I_F}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_gencondata_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="GENCONDATA", + raw_data_location=str(nemosis_fixture), + select_columns=["GENCONID", "EFFECTIVEDATE", "VERSIONNO", "CONSTRAINTTYPE", "CONSTRAINTVALUE"], + ) + + assert not data.empty + assert set(data["GENCONID"]) == {"#NSW1-QLD1_RAMP_I_F"} diff --git a/tests/end_to_end_table_tests/test_interconnector.py b/tests/end_to_end_table_tests/test_interconnector.py new file mode 100644 index 0000000..0d687b1 --- /dev/null +++ b/tests/end_to_end_table_tests/test_interconnector.py @@ -0,0 +1,28 @@ +"""Tests for INTERCONNECTOR. + +Effective-date config table: one row per interconnector, `LASTCHANGED` +records the last config change. NEMOSIS classifies it as search_type +"all" — by default it would iterate every month from July 2009 through +end_time. The test narrows that window by monkeypatching +`nem_data_model_start_time` so only a single fixture month is probed. + +Fixtured eras: 2021-05; INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +import pandas as pd + +from nemosis import defaults, dynamic_data_compiler + + +def test_interconnector_returns_fixtured_row(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="INTERCONNECTOR", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERCONNECTORID", "REGIONFROM", "REGIONTO", "LASTCHANGED"], + ) + + assert not data.empty + assert set(data["INTERCONNECTORID"]) == {"VIC1-NSW1"} diff --git a/tests/end_to_end_table_tests/test_interconnector_constraint.py b/tests/end_to_end_table_tests/test_interconnector_constraint.py new file mode 100644 index 0000000..ee26fff --- /dev/null +++ b/tests/end_to_end_table_tests/test_interconnector_constraint.py @@ -0,0 +1,23 @@ +"""Tests for INTERCONNECTORCONSTRAINT. + +Effective-date config table: per-interconnector physical limits and +loss parameters. search_type="all". + +Fixtured eras: 2021-05; INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_interconnector_constraint_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="INTERCONNECTORCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERCONNECTORID", "EFFECTIVEDATE", "VERSIONNO", "IMPORTLIMIT", "EXPORTLIMIT"], + ) + + assert not data.empty + assert set(data["INTERCONNECTORID"]) == {"VIC1-NSW1"} diff --git a/tests/end_to_end_table_tests/test_intermittent_gen_scada.py b/tests/end_to_end_table_tests/test_intermittent_gen_scada.py new file mode 100644 index 0000000..91179ac --- /dev/null +++ b/tests/end_to_end_table_tests/test_intermittent_gen_scada.py @@ -0,0 +1,58 @@ +"""Tests for INTERMITTENT_GEN_SCADA. + +Scrape-pattern table publishing SCADA output for semi-scheduled +generators in the next-day window. Keyed on RUN_DATETIME + DUID. + +Fixtured era: recent (scrape endpoint retention is short). +""" +from nemosis import dynamic_data_compiler + + +import pandas as pd + + +def test_recent_day_returns_rows_for_fixtured_duids(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 00:00:00", + end_time="2026/03/15 01:00:00", + table_name="INTERMITTENT_GEN_SCADA", + raw_data_location=str(nemosis_fixture), + select_columns=["RUN_DATETIME", "DUID", "SCADA_VALUE"], + ) + + assert not data.empty + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} + + +# Market-day stitch at 04:00 — same convention as the other scrape tables. +# AGLHAL is a peaking plant (scheduled, not intermittent) so only HDWF2 +# appears in this table's fixture. SCADA_TYPE is part of the PK — each +# (RUN_DATETIME, DUID) has two rows: ELAV (Element Availability) and +# LOCL (Local SCADA value). + +def test_end_market_day_returns_0400_row_from_previous_file(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 03:55:00", + end_time="2026/03/15 04:00:00", + table_name="INTERMITTENT_GEN_SCADA", + raw_data_location=str(nemosis_fixture), + select_columns=["RUN_DATETIME", "DUID", "SCADA_TYPE", "SCADA_VALUE"], + ) + assert set(data["RUN_DATETIME"].unique()) == {pd.Timestamp("2026-03-15 04:00:00")} + assert set(data["DUID"]) == {"HDWF2"} + assert set(data["SCADA_TYPE"]) == {"ELAV", "LOCL"} + assert not data.duplicated(["RUN_DATETIME", "DUID", "SCADA_TYPE"]).any() + + +def test_start_market_day_returns_0405_row_from_current_file(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 04:00:00", + end_time="2026/03/15 04:05:00", + table_name="INTERMITTENT_GEN_SCADA", + raw_data_location=str(nemosis_fixture), + select_columns=["RUN_DATETIME", "DUID", "SCADA_TYPE", "SCADA_VALUE"], + ) + assert set(data["RUN_DATETIME"].unique()) == {pd.Timestamp("2026-03-15 04:05:00")} + assert set(data["DUID"]) == {"HDWF2"} + assert set(data["SCADA_TYPE"]) == {"ELAV", "LOCL"} + assert not data.duplicated(["RUN_DATETIME", "DUID", "SCADA_TYPE"]).any() diff --git a/tests/end_to_end_table_tests/test_loss_factor_model.py b/tests/end_to_end_table_tests/test_loss_factor_model.py new file mode 100644 index 0000000..93bec0a --- /dev/null +++ b/tests/end_to_end_table_tests/test_loss_factor_model.py @@ -0,0 +1,23 @@ +"""Tests for LOSSFACTORMODEL. + +Effective-date config table: per-region loss factors for each +interconnector. search_type="all". + +Fixtured eras: 2021-05; INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_loss_factor_model_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="LOSSFACTORMODEL", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERCONNECTORID", "REGIONID", "EFFECTIVEDATE", "VERSIONNO", "DEMANDCOEFFICIENT"], + ) + + assert not data.empty + assert set(data["INTERCONNECTORID"]) == {"VIC1-NSW1"} diff --git a/tests/end_to_end_table_tests/test_loss_model.py b/tests/end_to_end_table_tests/test_loss_model.py new file mode 100644 index 0000000..0bc1496 --- /dev/null +++ b/tests/end_to_end_table_tests/test_loss_model.py @@ -0,0 +1,23 @@ +"""Tests for LOSSMODEL. + +Effective-date config table: piecewise loss model per interconnector, +broken into segments. search_type="all". + +Fixtured eras: 2021-05; INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_loss_model_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="LOSSMODEL", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERCONNECTORID", "EFFECTIVEDATE", "VERSIONNO", "LOSSSEGMENT", "MWBREAKPOINT"], + ) + + assert not data.empty + assert set(data["INTERCONNECTORID"]) == {"VIC1-NSW1"} diff --git a/tests/end_to_end_table_tests/test_market_price_thresholds.py b/tests/end_to_end_table_tests/test_market_price_thresholds.py new file mode 100644 index 0000000..731555a --- /dev/null +++ b/tests/end_to_end_table_tests/test_market_price_thresholds.py @@ -0,0 +1,23 @@ +"""Tests for MARKET_PRICE_THRESHOLDS. + +Effective-date config table: NEM market price floor and cap, plus +admin-price thresholds. Tiny — 12 rows total — so no row filter is +applied at fixture-build time. search_type="all". + +Fixtured eras: 2021-05. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_market_price_thresholds_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="MARKET_PRICE_THRESHOLDS", + raw_data_location=str(nemosis_fixture), + select_columns=["EFFECTIVEDATE", "VERSIONNO", "VOLL", "MARKETPRICEFLOOR"], + ) + + assert not data.empty diff --git a/tests/end_to_end_table_tests/test_mnsp_day_offer.py b/tests/end_to_end_table_tests/test_mnsp_day_offer.py new file mode 100644 index 0000000..be404fb --- /dev/null +++ b/tests/end_to_end_table_tests/test_mnsp_day_offer.py @@ -0,0 +1,37 @@ +"""Tests for MNSP_DAYOFFER. + +Daily bid offers from MNSP interconnectors (price bands, daily totals; +keyed on LINKID). MMS monthly archive at every era — the table kept +its name across the Aug-2024 archive rename, unlike MNSP_PEROFFER, so +coverage extends to 2024-09. + +Fixtured eras: 2018-05, 2021-05, 2024-09; LINKID ∈ {BLNKTAS, BLNKVIC}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", + "2024/09/01 00:00:00", +]) +def test_day_offers_returned_for_fixtured_links(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="MNSP_DAYOFFER", + raw_data_location=str(nemosis_fixture), + select_columns=[ + "SETTLEMENTDATE", "OFFERDATE", "VERSIONNO", + "PARTICIPANTID", "LINKID", "PRICEBAND1", + ], + ) + + assert not data.empty + assert set(data["LINKID"]) <= {"BLNKTAS", "BLNKVIC"} diff --git a/tests/end_to_end_table_tests/test_mnsp_interconnector.py b/tests/end_to_end_table_tests/test_mnsp_interconnector.py new file mode 100644 index 0000000..2b635c5 --- /dev/null +++ b/tests/end_to_end_table_tests/test_mnsp_interconnector.py @@ -0,0 +1,29 @@ +"""Tests for MNSP_INTERCONNECTOR. + +Effective-date config table: per-MNSP-link routing into a parent +"interconnector" abstraction (e.g. BLNKTAS → T-V-MNSP1). search_type="all" +— test narrows the scan window via monkeypatched `nem_data_model_start_time`. + +Filtered on LINKID rather than INTERCONNECTORID because the +INTERCONNECTORIDs in this table are the MNSP-side abstractions +(T-V-MNSP1, V-S-MNSP1, N-Q-MNSP1), not the regulated AC interconnectors +in our INTERCONNECTORS constant. + +Fixtured eras: 2021-05; LINKID ∈ {BLNKTAS, BLNKVIC}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_mnsp_interconnector_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="MNSP_INTERCONNECTOR", + raw_data_location=str(nemosis_fixture), + select_columns=["LINKID", "INTERCONNECTORID", "EFFECTIVEDATE", "VERSIONNO"], + ) + + assert not data.empty + assert set(data["LINKID"]) <= {"BLNKTAS", "BLNKVIC"} diff --git a/tests/end_to_end_table_tests/test_mnsp_per_offer.py b/tests/end_to_end_table_tests/test_mnsp_per_offer.py new file mode 100644 index 0000000..3a4f842 --- /dev/null +++ b/tests/end_to_end_table_tests/test_mnsp_per_offer.py @@ -0,0 +1,38 @@ +"""Tests for MNSP_PEROFFER. + +Per-period bid offers from MNSP interconnectors (keyed on LINKID +rather than DUID). MMS monthly archive at every era NEMOSIS can +currently read. After ~April 2021 AEMO began writing MNSP_BIDOFFERPERIOD +data (different column names) into the PUBLIC_DVD_MNSP_PEROFFER archives +and then at Aug-2024 renamed the archive stub outright — NEMOSIS doesn't +handle either change, so coverage stops at 2021-02. + +Fixtured eras: 2018-05, 2021-02; LINKID ∈ {BLNKTAS, BLNKVIC}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/02/01 00:00:00", +]) +def test_offers_returned_for_fixtured_links(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="MNSP_PEROFFER", + raw_data_location=str(nemosis_fixture), + select_columns=[ + "SETTLEMENTDATE", "OFFERDATE", "VERSIONNO", + "PARTICIPANTID", "LINKID", "PERIODID", "BANDAVAIL1", + ], + ) + + assert not data.empty + assert set(data["LINKID"]) <= {"BLNKTAS", "BLNKVIC"} diff --git a/tests/end_to_end_table_tests/test_next_day_dispatch_load.py b/tests/end_to_end_table_tests/test_next_day_dispatch_load.py new file mode 100644 index 0000000..a52b1e1 --- /dev/null +++ b/tests/end_to_end_table_tests/test_next_day_dispatch_load.py @@ -0,0 +1,54 @@ +"""Tests for NEXT_DAY_DISPATCHLOAD. + +Scrape-pattern version of DISPATCHLOAD published as part of the +next-day dispatch reports. Five-minute resolution, DUID-keyed. + +Fixtured era: recent (scrape endpoint keeps only a few months of data). +""" +from nemosis import dynamic_data_compiler + + +import pandas as pd + + +def test_recent_day_returns_rows_for_fixtured_duids(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 00:00:00", + end_time="2026/03/15 01:00:00", + table_name="NEXT_DAY_DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + ) + + assert not data.empty + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} + + +# Market-day stitch at 04:00. See test_daily_region_summary.py for the +# full explanation — NEXT_DAY_DISPATCH files share the same convention: +# `{date} 04:05 → {date+1} 04:00` per file, with 04:00 in the prior file. + +def test_end_market_day_returns_0400_row_from_previous_file(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 03:55:00", + end_time="2026/03/15 04:00:00", + table_name="NEXT_DAY_DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + ) + assert set(data["SETTLEMENTDATE"].unique()) == {pd.Timestamp("2026-03-15 04:00:00")} + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} + assert not data.duplicated(["SETTLEMENTDATE", "DUID"]).any() + + +def test_start_market_day_returns_0405_row_from_current_file(nemosis_fixture): + data = dynamic_data_compiler( + start_time="2026/03/15 04:00:00", + end_time="2026/03/15 04:05:00", + table_name="NEXT_DAY_DISPATCHLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + ) + assert set(data["SETTLEMENTDATE"].unique()) == {pd.Timestamp("2026-03-15 04:05:00")} + assert set(data["DUID"]) <= {"AGLHAL", "HDWF2"} + assert not data.duplicated(["SETTLEMENTDATE", "DUID"]).any() diff --git a/tests/end_to_end_table_tests/test_participant.py b/tests/end_to_end_table_tests/test_participant.py new file mode 100644 index 0000000..4c067c5 --- /dev/null +++ b/tests/end_to_end_table_tests/test_participant.py @@ -0,0 +1,69 @@ +"""Tests for PARTICIPANT. + +Effective-date config table: registered NEM participants. search_type="all". + +Fixtured eras: 2018-05, 2021-05; +PARTICIPANTID ∈ {AGLE, INFIGENH, ERMPOWER}. + +ERMPOWER is fixtured specifically to exercise `filter_on_last_changed` +across a wide window — its LASTCHANGED moves between 2018 and 2021 +archives, whereas AGLE and INFIGENH sit on 2013 LASTCHANGED values in +both archives and so each appear exactly once even in a multi-year query. +""" +from nemosis import defaults, dynamic_data_compiler + + +FIXTURED_PIDS = {"AGLE", "INFIGENH", "ERMPOWER"} + + +def test_participant_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="PARTICIPANT", + raw_data_location=str(nemosis_fixture), + select_columns=["PARTICIPANTID", "PARTICIPANTCLASSID", "NAME", "LASTCHANGED"], + ) + + assert not data.empty + assert set(data["PARTICIPANTID"]) <= FIXTURED_PIDS + + +def test_narrow_window_returns_one_row_per_participantid(nemosis_fixture, monkeypatch): + """5-minute window: `filter_on_last_changed` surfaces exactly the + currently-effective row for each participant — no history.""" + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 00:05:00", + table_name="PARTICIPANT", + raw_data_location=str(nemosis_fixture), + select_columns=["PARTICIPANTID", "NAME", "LASTCHANGED"], + ) + + counts = data.groupby("PARTICIPANTID").size() + assert (counts == 1).all(), counts.to_dict() + + +def test_wide_window_surfaces_lastchanged_history(nemosis_fixture, monkeypatch): + """Multi-year window: ERMPOWER has distinct LASTCHANGED values in the + 2018-05 and 2021-05 archives, so both rows survive `filter_on_last_changed`. + AGLE/INFIGENH never changed — still 1 row each. The contrast confirms + the filter is surfacing history, not collapsing to most-recent-per-PID.""" + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2018/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2018/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="PARTICIPANT", + raw_data_location=str(nemosis_fixture), + select_columns=["PARTICIPANTID", "NAME", "LASTCHANGED"], + ) + + counts = data.groupby("PARTICIPANTID").size().to_dict() + assert counts.get("ERMPOWER", 0) > 1, counts + assert counts.get("AGLE") == 1, counts + assert counts.get("INFIGENH") == 1, counts diff --git a/tests/end_to_end_table_tests/test_rooftop_pv_actual.py b/tests/end_to_end_table_tests/test_rooftop_pv_actual.py new file mode 100644 index 0000000..acdcb7e --- /dev/null +++ b/tests/end_to_end_table_tests/test_rooftop_pv_actual.py @@ -0,0 +1,69 @@ +"""Tests for ROOFTOP_PV_ACTUAL. + +Per-region rooftop PV generation at 30-min resolution. Introduced +around 2019. Has a TYPE column with both SATELLITE (regional estimate +updated through the day) and DAILY (next-day reconciled) values — +a given (REGIONID, INTERVAL_DATETIME) can have up to two rows. + +Boundary matrix (`test_rooftop_pv_actual_boundary`) filters TYPE=SATELLITE +so the helper sees one row per (REGIONID, interval) and its row-count +assertions hold. The matrix is auto-generated from +`spec.DYNAMIC_TABLES["ROOFTOP_PV_ACTUAL"]["eras"]` by `_boundaries.py` +— 30-min stride throughout. + +REGIONID ∈ {SA1, NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_REGIONS = {"SA1", "NSW1"} + + +@pytest.mark.parametrize("era_start", [ + "2021/05/01 00:00:00", + "2024/08/01 00:00:00", +]) +def test_one_hour_returns_two_half_hourly_rows_per_region_per_type(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="ROOFTOP_PV_ACTUAL", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERVAL_DATETIME", "REGIONID", "TYPE", "POWER"], + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + # Two half-hour intervals in one hour. Each region may appear under + # either SATELLITE or DAILY type (or both). + for region in FIXTURED_REGIONS: + by_type = data[data["REGIONID"] == region].groupby("TYPE").size() + assert (by_type == 2).all(), by_type + + +@pytest.mark.parametrize( + "case", boundary_cases("ROOFTOP_PV_ACTUAL"), ids=lambda c: c.id +) +def test_rooftop_pv_actual_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="ROOFTOP_PV_ACTUAL", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERVAL_DATETIME", "REGIONID", "TYPE", "POWER"], + filter_cols=["TYPE"], + filter_values=(["SATELLITE"],), + ) + + assert_boundary_shape( + data, case, + date_col="INTERVAL_DATETIME", + entities_col="REGIONID", + expected_entities=FIXTURED_REGIONS, + ) diff --git a/tests/end_to_end_table_tests/test_spd_connection_point_constraint.py b/tests/end_to_end_table_tests/test_spd_connection_point_constraint.py new file mode 100644 index 0000000..db99e52 --- /dev/null +++ b/tests/end_to_end_table_tests/test_spd_connection_point_constraint.py @@ -0,0 +1,28 @@ +"""Tests for SPDCONNECTIONPOINTCONSTRAINT. + +Effective-date config table: tells SPD which connection points feed +into which generic constraints, and with what factor. search_type="all". + +Filtered by GENCONID rather than CONNECTIONPOINTID since we don't have a +known connection-point shortlist; picked a low-volume constraint +("S>>MKRB_NIL_WEMWP4", ~20 rows in 2021-05) with an EFFECTIVEDATE before +the test window so NEMOSIS's `filter_on_effective_date` keeps it in. + +Fixtured eras: 2021-05; GENCONID ∈ {S>>MKRB_NIL_WEMWP4}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_spd_connection_point_constraint_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="SPDCONNECTIONPOINTCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["CONNECTIONPOINTID", "GENCONID", "EFFECTIVEDATE", "VERSIONNO", "BIDTYPE", "FACTOR"], + ) + + assert not data.empty + assert set(data["GENCONID"]) == {"S>>MKRB_NIL_WEMWP4"} diff --git a/tests/end_to_end_table_tests/test_spd_interconnector_constraint.py b/tests/end_to_end_table_tests/test_spd_interconnector_constraint.py new file mode 100644 index 0000000..a19590d --- /dev/null +++ b/tests/end_to_end_table_tests/test_spd_interconnector_constraint.py @@ -0,0 +1,23 @@ +"""Tests for SPDINTERCONNECTORCONSTRAINT. + +Effective-date config table: tells SPD which interconnectors feed into +which generic constraints, and with what factor. search_type="all". + +Fixtured eras: 2021-05; INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_spd_interconnector_constraint_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="SPDINTERCONNECTORCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["INTERCONNECTORID", "GENCONID", "EFFECTIVEDATE", "VERSIONNO", "FACTOR"], + ) + + assert not data.empty + assert set(data["INTERCONNECTORID"]) == {"VIC1-NSW1"} diff --git a/tests/end_to_end_table_tests/test_spd_region_constraint.py b/tests/end_to_end_table_tests/test_spd_region_constraint.py new file mode 100644 index 0000000..b602d01 --- /dev/null +++ b/tests/end_to_end_table_tests/test_spd_region_constraint.py @@ -0,0 +1,23 @@ +"""Tests for SPDREGIONCONSTRAINT. + +Effective-date config table: tells SPD which regions feed into which +generic constraints, and with what factor. search_type="all". + +Fixtured eras: 2021-05; REGIONID ∈ {SA1, NSW1}. +""" +from nemosis import defaults, dynamic_data_compiler + + +def test_spd_region_constraint_returns_fixtured_rows(nemosis_fixture, monkeypatch): + monkeypatch.setattr(defaults, "nem_data_model_start_time", "2021/05/01 00:00:00") + + data = dynamic_data_compiler( + start_time="2021/05/01 00:00:00", + end_time="2021/05/01 01:00:00", + table_name="SPDREGIONCONSTRAINT", + raw_data_location=str(nemosis_fixture), + select_columns=["REGIONID", "GENCONID", "EFFECTIVEDATE", "VERSIONNO", "FACTOR"], + ) + + assert not data.empty + assert set(data["REGIONID"]) <= {"SA1", "NSW1"} diff --git a/tests/end_to_end_table_tests/test_static_tables.py b/tests/end_to_end_table_tests/test_static_tables.py new file mode 100644 index 0000000..64dcb18 --- /dev/null +++ b/tests/end_to_end_table_tests/test_static_tables.py @@ -0,0 +1,88 @@ +"""Tests for AEMO's static (time-independent) tables. + +These four tables share one test file because they have no date dimension — +each is downloaded once as a snapshot and cached verbatim. The fixtures are +frozen copies of what AEMO was serving when build.py last ran. + + - ELEMENTS_FCAS_4_SECOND — CSV, scraped from a directory index + - VARIABLES_FCAS_4_SECOND — CSV, direct download + - Generators and Scheduled Loads — XLS workbook, direct download + - FCAS Providers — different tab of the same XLS workbook +""" +from nemosis import static_table + + +def test_elements_fcas_returns_non_empty_frame(nemosis_fixture): + data = static_table("ELEMENTS_FCAS_4_SECOND", str(nemosis_fixture)) + assert not data.empty + + +def test_variables_fcas_returns_non_empty_frame(nemosis_fixture): + data = static_table("VARIABLES_FCAS_4_SECOND", str(nemosis_fixture)) + assert not data.empty + + +def test_generators_and_scheduled_loads_returns_non_empty_frame(nemosis_fixture): + data = static_table("Generators and Scheduled Loads", str(nemosis_fixture)) + assert not data.empty + + +def test_fcas_providers_returns_non_empty_frame(nemosis_fixture): + # "FCAS Providers" reads a different sheet of the same XLS workbook as + # "Generators and Scheduled Loads". NEMOSIS's static_downloader_map has + # no entry for "FCAS Providers", so the first fetch errors on an empty + # cache — prime it by fetching the sibling table. + static_table("Generators and Scheduled Loads", str(nemosis_fixture)) + data = static_table("FCAS Providers", str(nemosis_fixture)) + assert not data.empty + + +# --------------------------------------------------------------------------- +# Filter exercises — confirm `filter_cols`/`filter_values` actually narrow +# the result. `static_table` requires `select_columns` whenever `filter_cols` +# is passed (the filter cols must be a subset of the selected cols). +# --------------------------------------------------------------------------- + +def test_elements_fcas_filter_narrows(nemosis_fixture): + cols = ["ELEMENTNUMBER", "EMSNAME", "ELEMENTTYPE"] + full = static_table("ELEMENTS_FCAS_4_SECOND", str(nemosis_fixture), select_columns=cols) + filtered = static_table( + "ELEMENTS_FCAS_4_SECOND", str(nemosis_fixture), + select_columns=cols, filter_cols=["ELEMENTTYPE"], filter_values=[["GEN"]], + ) + assert 0 < len(filtered) < len(full) + assert set(filtered["ELEMENTTYPE"]) == {"GEN"} + + +def test_variables_fcas_filter_narrows(nemosis_fixture): + cols = ["VARIABLENUMBER", "VARIABLETYPE"] + full = static_table("VARIABLES_FCAS_4_SECOND", str(nemosis_fixture), select_columns=cols) + filtered = static_table( + "VARIABLES_FCAS_4_SECOND", str(nemosis_fixture), + select_columns=cols, filter_cols=["VARIABLETYPE"], filter_values=[["MW"]], + ) + assert 0 < len(filtered) < len(full) + assert set(filtered["VARIABLETYPE"]) == {"MW"} + + +def test_generators_filter_narrows(nemosis_fixture): + cols = ["Participant", "Region", "DUID"] + full = static_table("Generators and Scheduled Loads", str(nemosis_fixture), select_columns=cols) + filtered = static_table( + "Generators and Scheduled Loads", str(nemosis_fixture), + select_columns=cols, filter_cols=["Region"], filter_values=[["SA1"]], + ) + assert 0 < len(filtered) < len(full) + assert set(filtered["Region"]) == {"SA1"} + + +def test_fcas_providers_filter_narrows(nemosis_fixture): + static_table("Generators and Scheduled Loads", str(nemosis_fixture)) + cols = ["Participant", "Region", "DUID", "Bid Type"] + full = static_table("FCAS Providers", str(nemosis_fixture), select_columns=cols) + filtered = static_table( + "FCAS Providers", str(nemosis_fixture), + select_columns=cols, filter_cols=["Region"], filter_values=[["SA1"]], + ) + assert 0 < len(filtered) < len(full) + assert set(filtered["Region"]) == {"SA1"} diff --git a/tests/end_to_end_table_tests/test_trading_interconnect.py b/tests/end_to_end_table_tests/test_trading_interconnect.py new file mode 100644 index 0000000..eb13923 --- /dev/null +++ b/tests/end_to_end_table_tests/test_trading_interconnect.py @@ -0,0 +1,85 @@ +"""Tests for TRADINGINTERCONNECT. + +Per-interconnector trading summary. Same 30-min → 5-min transition +at 2021-10-01 (5MS reform) as TRADINGPRICE. + +Boundary matrix (`test_trading_interconnect_boundary`) is auto-generated +from `spec.DYNAMIC_TABLES["TRADINGINTERCONNECT"]["eras"]` by +`_boundaries.py`. The helper picks the right stride per era automatically. + +INTERCONNECTORID ∈ {VIC1-NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_INTERCONNECTORS = {"VIC1-NSW1"} + + +@pytest.mark.parametrize("era_start,expected_per_hour", [ + ("2018/05/01 00:00:00", 2), # 30-min era + ("2021/05/01 00:00:00", 2), + ("2022/06/01 00:00:00", 12), # post-5MS (cutover was 2021-10-01) + ("2024/08/01 00:00:00", 12), +]) +def test_interval_granularity_matches_era(nemosis_fixture, era_start, expected_per_hour): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="TRADINGINTERCONNECT", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "INTERCONNECTORID", "METEREDMWFLOW"], + ) + + assert set(data["INTERCONNECTORID"]) == FIXTURED_INTERCONNECTORS + assert len(data) == expected_per_hour + + +def test_5ms_cutover_stride_transition(nemosis_fixture): + """Query a window straddling the 2021-10-01 5MS cutover and assert the + stride transition: two 30-min rows from the Sept file, then twelve 5-min + rows from the Oct file.""" + data = dynamic_data_compiler( + start_time="2021/09/30 23:00:00", + end_time="2021/10/01 01:00:00", + table_name="TRADINGINTERCONNECT", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "INTERCONNECTORID", "METEREDMWFLOW"], + ) + + expected = [ + pd.Timestamp("2021-09-30 23:30:00"), + pd.Timestamp("2021-10-01 00:00:00"), + ] + [pd.Timestamp("2021-10-01 00:00:00") + pd.Timedelta(minutes=5 * i) + for i in range(1, 13)] + + assert set(data["INTERCONNECTORID"]) == FIXTURED_INTERCONNECTORS + for link in FIXTURED_INTERCONNECTORS: + timestamps = sorted(data[data["INTERCONNECTORID"] == link]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +@pytest.mark.parametrize( + "case", boundary_cases("TRADINGINTERCONNECT"), ids=lambda c: c.id +) +def test_trading_interconnect_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="TRADINGINTERCONNECT", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "INTERCONNECTORID", "METEREDMWFLOW"], + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="INTERCONNECTORID", + expected_entities=FIXTURED_INTERCONNECTORS, + ) diff --git a/tests/end_to_end_table_tests/test_trading_load.py b/tests/end_to_end_table_tests/test_trading_load.py new file mode 100644 index 0000000..cc5c870 --- /dev/null +++ b/tests/end_to_end_table_tests/test_trading_load.py @@ -0,0 +1,62 @@ +"""Tests for TRADINGLOAD. + +Per-unit 30-minute trading summary (INITIALMW, TOTALCLEARED, etc.). +**Discontinued before 2022-01** — AEMO stopped publishing the monthly +archive for this table when trading merged with dispatch. Only +pre-2022 eras are fixtured. + +Boundary matrix (`test_trading_load_boundary`) is auto-generated from +`spec.DYNAMIC_TABLES["TRADINGLOAD"]["eras"]` by `_boundaries.py` — +30-min stride throughout. + +DUID ∈ {AGLHAL, HDWF2}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_DUIDS = {"AGLHAL", "HDWF2"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", +]) +def test_one_hour_gives_two_half_hourly_intervals_per_duid(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="TRADINGLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + ) + + assert set(data["DUID"]) == FIXTURED_DUIDS + for duid in FIXTURED_DUIDS: + assert len(data[data["DUID"] == duid]) == 2 + + +@pytest.mark.parametrize( + "case", boundary_cases("TRADINGLOAD"), ids=lambda c: c.id +) +def test_trading_load_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="TRADINGLOAD", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "DUID", "INITIALMW"], + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="DUID", + expected_entities=FIXTURED_DUIDS, + ) diff --git a/tests/end_to_end_table_tests/test_trading_price.py b/tests/end_to_end_table_tests/test_trading_price.py new file mode 100644 index 0000000..7da6fcf --- /dev/null +++ b/tests/end_to_end_table_tests/test_trading_price.py @@ -0,0 +1,91 @@ +"""Tests for TRADINGPRICE. + +Per-region trading price summary. Interval granularity changed on 2021-10-01 +(the 5MS reform cutover): before that date trading was 30-minute, after it +matches dispatch at 5-min. The tests make that transition explicit via era +parametrisation and a dedicated cutover test. + +Boundary matrix (`test_trading_price_boundary`) is auto-generated from +`spec.DYNAMIC_TABLES["TRADINGPRICE"]["eras"]` by `_boundaries.py`. The +helper picks the right stride per era automatically (30 → 5 at 2021-10). + +REGIONID ∈ {SA1, NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_REGIONS = {"SA1", "NSW1"} + + +@pytest.mark.parametrize("era_start,interval_minutes,expected_per_hour", [ + ("2018/05/01 00:00:00", 30, 2), # pre-5-min era — 2 half-hour intervals/hour + ("2021/05/01 00:00:00", 30, 2), + ("2022/06/01 00:00:00", 5, 12), # post-5MS (cutover was 2021-10-01) + ("2024/08/01 00:00:00", 5, 12), +]) +def test_interval_granularity_matches_era( + nemosis_fixture, era_start, interval_minutes, expected_per_hour, +): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="TRADINGPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + region_data = data[data["REGIONID"] == region].sort_values("SETTLEMENTDATE") + assert len(region_data) == expected_per_hour + + +def test_5ms_cutover_stride_transition(nemosis_fixture): + """Query a window straddling the 2021-10-01 5MS cutover and assert the + stride transition is reproduced exactly: two 30-min rows from the Sept + file, then twelve 5-min rows from the Oct file.""" + data = dynamic_data_compiler( + start_time="2021/09/30 23:00:00", + end_time="2021/10/01 01:00:00", + table_name="TRADINGPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + ) + + expected = [ + pd.Timestamp("2021-09-30 23:30:00"), + pd.Timestamp("2021-10-01 00:00:00"), + ] + [pd.Timestamp("2021-10-01 00:00:00") + pd.Timedelta(minutes=5 * i) + for i in range(1, 13)] + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + timestamps = sorted(data[data["REGIONID"] == region]["SETTLEMENTDATE"].tolist()) + assert timestamps == expected + + +@pytest.mark.parametrize( + "case", boundary_cases("TRADINGPRICE"), ids=lambda c: c.id +) +def test_trading_price_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="TRADINGPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="REGIONID", + expected_entities=FIXTURED_REGIONS, + ) diff --git a/tests/end_to_end_table_tests/test_trading_region_sum.py b/tests/end_to_end_table_tests/test_trading_region_sum.py new file mode 100644 index 0000000..637f48e --- /dev/null +++ b/tests/end_to_end_table_tests/test_trading_region_sum.py @@ -0,0 +1,60 @@ +"""Tests for TRADINGREGIONSUM. + +Per-region 30-minute trading summary. Discontinued in 2022 (same +reason as TRADINGLOAD), so only pre-2022 eras are fixtured. + +Boundary matrix (`test_trading_region_sum_boundary`) is auto-generated +from `spec.DYNAMIC_TABLES["TRADINGREGIONSUM"]["eras"]` by `_boundaries.py` +— 30-min stride throughout. + +REGIONID ∈ {SA1, NSW1}. +""" +import pandas as pd +import pytest + +from nemosis import dynamic_data_compiler + +from _boundaries import assert_boundary_shape, boundary_cases + +FIXTURED_REGIONS = {"SA1", "NSW1"} + + +@pytest.mark.parametrize("era_start", [ + "2018/05/01 00:00:00", + "2021/05/01 00:00:00", +]) +def test_one_hour_gives_two_half_hourly_intervals_per_region(nemosis_fixture, era_start): + start = pd.to_datetime(era_start, format="%Y/%m/%d %H:%M:%S") + end = start + pd.Timedelta(hours=1) + + data = dynamic_data_compiler( + start_time=start.strftime("%Y/%m/%d %H:%M:%S"), + end_time=end.strftime("%Y/%m/%d %H:%M:%S"), + table_name="TRADINGREGIONSUM", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "TOTALDEMAND"], + ) + + assert set(data["REGIONID"]) == FIXTURED_REGIONS + for region in FIXTURED_REGIONS: + assert len(data[data["REGIONID"] == region]) == 2 + + +@pytest.mark.parametrize( + "case", boundary_cases("TRADINGREGIONSUM"), ids=lambda c: c.id +) +def test_trading_region_sum_boundary(nemosis_fixture, case): + data = dynamic_data_compiler( + start_time=case.start_str, + end_time=case.end_str, + table_name="TRADINGREGIONSUM", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "TOTALDEMAND"], + ) + + assert_boundary_shape( + data, case, + date_col="SETTLEMENTDATE", + entities_col="REGIONID", + expected_entities=FIXTURED_REGIONS, + ) diff --git a/tests/fixtures/build.py b/tests/fixtures/build.py new file mode 100644 index 0000000..16a916b --- /dev/null +++ b/tests/fixtures/build.py @@ -0,0 +1,499 @@ +"""Download and filter real AEMO data into compact local test fixtures. + +Reads the spec in tests/fixtures/spec.py, downloads every (table, era) pair +from AEMO, filters rows down to the entities in the spec, and writes the +result under tests/fixtures/data/ in a directory tree that mirrors the URL +paths on nemweb.com.au and aemo.com.au. The dummy HTTP server serves that +tree verbatim, so every URL AEMO exposes has a 1:1 local counterpart. + +Usage: + python tests/fixtures/build.py # build missing fixtures only + python tests/fixtures/build.py --rebuild # re-download and overwrite all + +This is intended to be run rarely — whenever an era or table is added to the +spec — and the resulting files committed to the repo. +""" +from __future__ import annotations + +import argparse +import io +import logging +import sys +import zipfile +from datetime import date +from pathlib import Path + +import pandas as pd +import requests +from bs4 import BeautifulSoup + +REPO_ROOT = Path(__file__).resolve().parents[2] +FIXTURE_ROOT = Path(__file__).parent / "data" +sys.path.insert(0, str(REPO_ROOT / "src")) +sys.path.insert(0, str(Path(__file__).parent)) + +from nemosis import defaults # noqa: E402 +import spec # noqa: E402 + +USR_AGENT = {"User-Agent": "NEMOSIS-fixture-builder"} +MMS_ARCHIVE_CUTOVER = date(2024, 8, 1) # PUBLIC_DVD_ → PUBLIC_ARCHIVE# on this month + +# Temporal filter applied to every row whose table has a primary date column. +# Keeping only the first and last few days of the month shrinks fixtures ~6x +# while preserving the start-of-month / end-of-month / month-straddle boundaries +# the tests care about. +KEEP_FIRST_DAYS = 3 +KEEP_LAST_DAYS = 2 + +log = logging.getLogger("fixtures") + + +# --------------------------------------------------------------------------- +# Network / IO helpers +# --------------------------------------------------------------------------- + +def http_get(url: str) -> requests.Response: + log.info("GET %s", url) + r = requests.get(url.replace("#", "%23"), headers=USR_AGENT, timeout=180) + r.raise_for_status() + return r + + +def write_file(target: Path, contents: bytes) -> None: + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(contents) + log.info("wrote %s (%s bytes)", target.relative_to(FIXTURE_ROOT), len(contents)) + + +def read_single_csv_from_zip(zip_bytes: bytes) -> tuple[str, bytes]: + with zipfile.ZipFile(io.BytesIO(zip_bytes)) as z: + name = z.namelist()[0] + return name, z.read(name) + + +def pack_csv_as_zip(csv_name: str, csv_bytes: bytes) -> bytes: + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: + z.writestr(csv_name, csv_bytes) + return buf.getvalue() + + +# --------------------------------------------------------------------------- +# MMS CSV filtering +# --------------------------------------------------------------------------- +# AEMO MMS CSVs have the shape: +# row 0: C,NEMP.WORLD,... (summary — preserved as-is) +# row 1: I,TABLE,SUBCAT,VERSION,col,col (column header — preserved as-is) +# rows 2-N: D,TABLE,SUBCAT,VERSION,val,val (data rows — filtered) +# row N+1: C,"END OF REPORT",n (footer — preserved as-is) + +def filter_mms_csv( + csv_bytes: bytes, + filters: dict[str, list], + date_column: str | None, + era_date: date, +) -> bytes: + lines = csv_bytes.decode("utf-8").splitlines(keepends=True) + summary, footer = lines[0], lines[-1] + body = "".join(lines[1:-1]) + + df = pd.read_csv(io.StringIO(body), dtype=str, keep_default_na=False) + for col, allowed in filters.items(): + if col not in df.columns: + raise KeyError(f"filter column {col!r} not found in {list(df.columns)}") + df = df[df[col].isin([str(v) for v in allowed])] + + if date_column and date_column in df.columns: + df = _keep_first_and_last_days(df, date_column, era_date) + + out = io.StringIO() + out.write(summary) + df.to_csv(out, index=False, lineterminator="\n") + out.write(footer) + return out.getvalue().encode("utf-8") + + +def _keep_first_and_last_days(df: pd.DataFrame, col: str, era_date: date) -> pd.DataFrame: + """Keep rows from the first KEEP_FIRST_DAYS and last KEEP_LAST_DAYS of the + era's month. Also keep rows from adjacent months if they happen to be + present — MMS archives sometimes contain records from the trailing edge of + the neighbouring month, and tests exercising that behaviour want them.""" + parsed = pd.to_datetime(df[col], errors="coerce") + same_month = (parsed.dt.year == era_date.year) & (parsed.dt.month == era_date.month) + days_in_month = (parsed + pd.offsets.MonthEnd(0)).dt.day + early = same_month & (parsed.dt.day <= KEEP_FIRST_DAYS) + late = same_month & (parsed.dt.day > days_in_month - KEEP_LAST_DAYS) + other_month = ~same_month # preserve any cross-month rows verbatim + return df[early | late | other_month] + + +# --------------------------------------------------------------------------- +# URL & path construction (MMS dynamic tables) +# --------------------------------------------------------------------------- + +def mms_filename(table: str, era_date: date, chunk: int) -> str: + year, month = era_date.year, f"{era_date.month:02d}" + if era_date >= MMS_ARCHIVE_CUTOVER: + return f"PUBLIC_ARCHIVE#{table}#FILE{chunk:02d}#{year}{month}010000.zip" + return f"{defaults.names[table]}_{year}{month}010000.zip" + + +def mms_fixture_path(table: str, era_date: date, chunk: int) -> Path: + year, month = era_date.year, f"{era_date.month:02d}" + return ( + FIXTURE_ROOT + / "Data_Archive/Wholesale_Electricity/MMSDM" + / str(year) + / f"MMSDM_{year}_{month}" + / "MMSDM_Historical_Data_SQLLoader/DATA" + / mms_filename(table, era_date, chunk) + ) + + +def mms_source_url(table: str, era_date: date, chunk: int) -> str: + return defaults.aemo_mms_url.format( + era_date.year, + era_date.year, + f"{era_date.month:02d}", + mms_filename(table, era_date, chunk).removesuffix(".zip"), + ) + + +# --------------------------------------------------------------------------- +# Builders +# --------------------------------------------------------------------------- + +def build_mms_month(table: str, month: date, *, rebuild: bool) -> None: + """Download, filter, and write every chunk of one (table, month) MMS archive.""" + cfg = spec.DYNAMIC_TABLES[table] + filters = cfg["filter"] + # Effective-date config tables publish sparsely — the default first-3 / + # last-2 days trim would often leave the fixture empty. They opt out with + # `keep_full_month: True`, meaning we only apply the row (column-value) + # filter, never the time filter. + date_column = None if cfg.get("keep_full_month") else defaults.primary_date_columns.get(table) + chunked = month >= MMS_ARCHIVE_CUTOVER + + chunk = 1 + while True: + target = mms_fixture_path(table, month, chunk) + if target.exists() and not rebuild: + log.info("skip (cached) %s", target.relative_to(FIXTURE_ROOT)) + else: + url = mms_source_url(table, month, chunk) + try: + zip_bytes = http_get(url).content + except requests.HTTPError: + if chunk == 1: + raise + log.info("no chunk %d for %s %s — stopping", chunk, table, month) + return + csv_name, csv_bytes = read_single_csv_from_zip(zip_bytes) + filtered = filter_mms_csv(csv_bytes, filters, date_column, month) + write_file(target, pack_csv_as_zip(csv_name, filtered)) + + if not chunked: + return + chunk += 1 + + +def previous_month(month: date) -> date: + if month.month == 1: + return date(month.year - 1, 12, 1) + return date(month.year, month.month - 1, 1) + + +def build_static_variables_fcas(*, rebuild: bool) -> None: + """Direct CSV download from aemo.com.au — no scraping, no filtering.""" + url = defaults.static_table_url["VARIABLES_FCAS_4_SECOND"] + target = FIXTURE_ROOT / url.split("://", 1)[1].split("/", 1)[1] + if target.exists() and not rebuild: + log.info("skip (cached) %s", target.relative_to(FIXTURE_ROOT)) + return + write_file(target, http_get(url).content) + + +def build_static_generators_xls(*, rebuild: bool) -> None: + """Direct XLS download from aemo.com.au — covers both 'Generators and + Scheduled Loads' and 'FCAS Providers', which share the same workbook.""" + url = defaults.static_table_url["Generators and Scheduled Loads"] + target = FIXTURE_ROOT / url.split("://", 1)[1].split("/", 1)[1] + if target.exists() and not rebuild: + log.info("skip (cached) %s", target.relative_to(FIXTURE_ROOT)) + return + write_file(target, http_get(url).content) + + +def build_static_elements_fcas(*, rebuild: bool) -> None: + """Scrape-pattern: download the newest Elements CSV and write a fake + directory index alongside it so the live scraper logic still works.""" + index_url = defaults.static_table_url["ELEMENTS_FCAS_4_SECOND"] + fixture_dir = FIXTURE_ROOT / index_url.split("://", 1)[1].split("/", 1)[1] + + soup = BeautifulSoup(http_get(index_url).text, "html.parser") + links = [a.text for a in soup.find_all("a") if a.text.endswith(".csv")] + if not links: + raise RuntimeError("no Elements_FCAS CSV links found on AEMO index page") + filename = links[-1] # mirror downloader.download_elements_file's choice + + target = fixture_dir / filename + if target.exists() and not rebuild: + log.info("skip (cached) %s", target.relative_to(FIXTURE_ROOT)) + else: + write_file(target, http_get(index_url + filename).content) + + elements_path = index_url.split("://", 1)[1].split("/", 1)[1] + write_file(fixture_dir / "index.html", + make_index_html([filename], elements_path).encode("utf-8")) + + +def make_index_html(filenames: list[str], url_prefix: str) -> str: + """Minimal HTML directory listing. Real AEMO pages are messy tables of + server-generated links with absolute-path hrefs like /Reports/Current/X/Y.zip; + NEMOSIS concatenates those onto the domain URL, so our fake index must use + the same absolute-path form (not ./relative) for the resulting URL to point + at the right file on our server.""" + anchors = "\n".join(f'{n}' for n in filenames) + return f"\n{anchors}\n" + + +# --------------------------------------------------------------------------- +# Scrape-pattern (BIDMOVE_COMPLETE, PUBLIC_DAILY, PUBLIC_NEXT_DAY_*) +# --------------------------------------------------------------------------- +# These tables live on AEMO's rolling "current data" index pages — each page +# lists daily zips for the last few months. NEMOSIS finds them by scraping +# the page for a link whose href contains a date-stamped filename stub. +# Build-time complication: dates that age out of the window stop being served, +# so we pick one date that's currently on the page when build runs (ERAS.recent) +# and also fetch the day before, which NEMOSIS's date generator buffers for. + +# Each AEMO scrape file may contain several logical tables in one CSV, +# delimited by 'I' header rows. NEMOSIS only reads specific sections — we +# filter those by column-value and strip the D-rows of everything else, +# which preserves the I-row structure NEMOSIS's section-counter relies on. +# sections_to_keep: section-index → filter dict +SCRAPE_FILES = { + "Reports/Current/Daily_Reports/": { + "stub_template": "PUBLIC_DAILY_{ymd}", + "sections_to_keep": {1: {"REGIONID": spec.REGIONS}}, + }, + "Reports/Current/NEXT_DAY_DISPATCH/": { + "stub_template": "PUBLIC_NEXT_DAY_DISPATCH_{ymd}", + "sections_to_keep": {0: {"DUID": spec.DUIDS}}, + }, + "Reports/Current/Next_Day_Intermittent_Gen_Scada/": { + "stub_template": "PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_{ymd}", + "sections_to_keep": {0: {"DUID": spec.DUIDS}}, + }, +} + +# Which NEMOSIS table maps to which scrape file. +TABLE_SCRAPE_FILE = { + "DAILY_REGION_SUMMARY": "Reports/Current/Daily_Reports/", + "NEXT_DAY_DISPATCHLOAD": "Reports/Current/NEXT_DAY_DISPATCH/", + "INTERMITTENT_GEN_SCADA": "Reports/Current/Next_Day_Intermittent_Gen_Scada/", +} + + +def build_scrape_day(table: str, day: date, *, rebuild: bool) -> None: + """Download, filter, and write one day's scrape-pattern fixture. + + Also rewrites the directory's fake index.html to list every fixture + file currently present (so a later build for a different day adds a + link rather than replacing the page). + """ + path = TABLE_SCRAPE_FILE[table] + cfg = SCRAPE_FILES[path] + ymd = f"{day.year}{day.month:02d}{day.day:02d}" + stub = cfg["stub_template"].format(ymd=ymd) + fixture_dir = FIXTURE_ROOT / path + + existing_match = next( + (p for p in fixture_dir.glob("*") if stub in p.name and p.suffix != ".html"), + None, + ) + if existing_match and not rebuild: + log.info("skip (cached) %s", existing_match.relative_to(FIXTURE_ROOT)) + else: + href, zip_bytes = _fetch_matching_link(path, stub) + filename = href.split("/")[-1] + filtered_zip = _filter_multisection_zip(zip_bytes, cfg["sections_to_keep"]) + write_file(fixture_dir / filename, filtered_zip) + + _rewrite_index(fixture_dir) + + +def _fetch_matching_link(path: str, stub: str) -> tuple[str, bytes]: + """Mirror NEMOSIS's scraper: hit the index, find an anchor whose href + contains the stub, download it. Returns (href, zip-bytes).""" + index_url = defaults.nem_web_domain_url.rstrip("/") + "/" + path + soup = BeautifulSoup(http_get(index_url).text, "html.parser") + domain = defaults.nem_web_domain_url.rstrip("/") + for link in soup.find_all("a"): + href = link.get("href") or "" + if stub not in href: + continue + if href.startswith("http"): + zip_url = href + elif href.startswith("/"): + zip_url = domain + href # absolute path on the domain + else: + zip_url = index_url.rstrip("/") + "/" + href.lstrip("./") + return href, http_get(zip_url).content + raise RuntimeError(f"no link matching {stub!r} on {index_url}") + + +def _rewrite_index(directory: Path) -> None: + directory.mkdir(parents=True, exist_ok=True) + filenames = sorted( + p.name for p in directory.iterdir() if p.is_file() and p.suffix != ".html" + ) + url_prefix = directory.relative_to(FIXTURE_ROOT).as_posix() + write_file(directory / "index.html", + make_index_html(filenames, url_prefix).encode("utf-8")) + + +# --------------------------------------------------------------------------- +# Multi-section AEMO CSV parsing (for BIDMOVE_COMPLETE, PUBLIC_DAILY, etc.) +# --------------------------------------------------------------------------- +# Several AEMO zip archives contain a single CSV holding multiple logical +# tables — each delimited by an 'I' header row, ending with a 'C' footer. +# We split at I-rows, filter each section independently (so BIDDAYOFFER_D +# gets filtered by DUID, and so does BIDPEROFFER_D alongside it). + +def _filter_multisection_zip(zip_bytes: bytes, sections_to_keep: dict[int, dict]) -> bytes: + """Filter sections listed in `sections_to_keep` by their column-values, + and drop all D rows from every other section. I rows stay untouched so + NEMOSIS's section-counting (_find_start_row_nth_table) still matches up. + """ + csv_name, csv_bytes = read_single_csv_from_zip(zip_bytes) + text = csv_bytes.decode("utf-8") + lines = text.splitlines(keepends=True) + summary = lines[0] + footer = lines[-1] if lines[-1].startswith("C,") else "" + body = lines[1:-1] if footer else lines[1:] + + sections = _split_mms_sections(body) + for idx, section in enumerate(sections): + if idx in sections_to_keep: + section.update(_filter_one_section(section, sections_to_keep[idx])) + else: + section["data"] = [] # strip — NEMOSIS never reads this section + + rebuilt = summary + for section in sections: + rebuilt += section["header"] + rebuilt += "".join(section["data"]) + rebuilt += footer + return pack_csv_as_zip(csv_name, rebuilt.encode("utf-8")) + + +def _split_mms_sections(body_lines: list[str]) -> list[dict]: + sections: list[dict] = [] + current: dict | None = None + for line in body_lines: + if line.startswith("I,"): + if current is not None: + sections.append(current) + current = {"header": line, "data": []} + elif line.startswith("D,") and current is not None: + current["data"].append(line) + if current is not None: + sections.append(current) + return sections + + +def _filter_one_section(section: dict, filters: dict[str, list]) -> dict: + if not section["data"]: + return section + df = pd.read_csv( + io.StringIO(section["header"] + "".join(section["data"])), + dtype=str, keep_default_na=False, + ) + for col, allowed in filters.items(): + if col in df.columns: + df = df[df[col].isin([str(v) for v in allowed])] + out = io.StringIO() + df.to_csv(out, index=False, lineterminator="\n") + lines = out.getvalue().splitlines(keepends=True) + return {"header": lines[0], "data": lines[1:]} + + +def previous_day(day: date) -> date: + from datetime import timedelta + return day - timedelta(days=1) + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--rebuild", action="store_true", + help="re-download and overwrite existing fixtures") + parser.add_argument("-v", "--verbose", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(levelname)-5s %(message)s", + ) + + failed: list[tuple[str, str, str]] = [] + + for table, cfg in spec.DYNAMIC_TABLES.items(): + # `extra_eras` are built but not picked up by `boundary_cases` — used + # for standalone tests whose windows don't fit the matrix's shape + # (e.g. straddling a stride-transition month like 2021-10). + for era_key in cfg["eras"] + cfg.get("extra_eras", []): + era_date = spec.ERAS[era_key] + try: + if _is_scrape_only(table, era_date): + build_scrape_day(table, era_date, rebuild=args.rebuild) + # NEMOSIS buffers one day back for scrape tables. + try: + build_scrape_day(table, previous_day(era_date), rebuild=args.rebuild) + except (requests.HTTPError, RuntimeError) as e: + log.info("prev-day buffer unavailable for %s @ %s (%s)", + table, era_key, e) + else: + build_mms_month(table, era_date, rebuild=args.rebuild) + try: + build_mms_month(table, previous_month(era_date), rebuild=args.rebuild) + except requests.HTTPError as e: + log.info("prev-month buffer unavailable for %s @ %s (%s)", + table, era_key, e) + except (requests.HTTPError, RuntimeError) as e: + failed.append((table, era_key, str(e))) + log.error("FAILED %s @ %s — %s", table, era_key, e) + + for name in spec.STATIC_TABLES: + builder = { + "ELEMENTS_FCAS_4_SECOND": build_static_elements_fcas, + "VARIABLES_FCAS_4_SECOND": build_static_variables_fcas, + "Generators and Scheduled Loads": build_static_generators_xls, + }[name] + try: + builder(rebuild=args.rebuild) + except requests.HTTPError as e: + failed.append((name, "static", str(e))) + log.error("FAILED %s — %s", name, e) + + if failed: + log.error("%d fixture(s) failed — fix the spec or retry:", len(failed)) + for table, era, err in failed: + log.error(" %s @ %s: %s", table, era, err) + sys.exit(1) + + +def _is_scrape_only(table: str, era_date: date) -> bool: + """Some AEMO tables are only published on rolling current-data pages, not + in the MMS monthly archive. (BIDPEROFFER_D and BIDDAYOFFER_D use MMS — + NEMOSIS's dead `BIDDING` code path would go through the Bidmove_Complete + scrape, but no table is actually wired to it.)""" + return table in ("DAILY_REGION_SUMMARY", "NEXT_DAY_DISPATCHLOAD", "INTERMITTENT_GEN_SCADA") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/data/-/media/files/electricity/nem/Participant_Information/NEM-Registration-and-Exemption-List.xlsx b/tests/fixtures/data/-/media/files/electricity/nem/Participant_Information/NEM-Registration-and-Exemption-List.xlsx new file mode 100644 index 0000000..bc14646 Binary files /dev/null and b/tests/fixtures/data/-/media/files/electricity/nem/Participant_Information/NEM-Registration-and-Exemption-List.xlsx differ diff --git a/tests/fixtures/data/-/media/files/electricity/nem/settlements_and_payments/settlements/auction-reports/archive/ancillary-services-market-causer-pays-variables-file.csv b/tests/fixtures/data/-/media/files/electricity/nem/settlements_and_payments/settlements/auction-reports/archive/ancillary-services-market-causer-pays-variables-file.csv new file mode 100644 index 0000000..1f86e73 --- /dev/null +++ b/tests/fixtures/data/-/media/files/electricity/nem/settlements_and_payments/settlements/auction-reports/archive/ancillary-services-market-causer-pays-variables-file.csv @@ -0,0 +1,19 @@ +1,"MW" +2,"Gen_MW" +3,"GenSPD_MW" +4,"GenRPF_%" +5,"GenRegComp_MW" +6,"Spare06" +7,"Spare07" +8,"Spare08" +9,"ON" +10,"OFF" +11,"ACE" +12,"ACEFIL" +13,"HZ" +14,"HZNOM" +15,"SEC" +16,"ACEINT" +17,"HZOFFSET" +18,"HZDEV" +19,"SPD_MWB" diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201804010000.zip new file mode 100644 index 0000000..c66488a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201804010000.zip new file mode 100644 index 0000000..6c8b1c1 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201804010000.zip new file mode 100644 index 0000000..c17809c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201804010000.zip new file mode 100644 index 0000000..be9c5bf Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201804010000.zip new file mode 100644 index 0000000..48d4f6b Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201804010000.zip new file mode 100644 index 0000000..c9a0a2a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201804010000.zip new file mode 100644 index 0000000..acf2b18 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201804010000.zip new file mode 100644 index 0000000..7220e2c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201804010000.zip new file mode 100644 index 0000000..9a3425c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201804010000.zip new file mode 100644 index 0000000..dbffdc6 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201804010000.zip new file mode 100644 index 0000000..2fdc63a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201804010000.zip new file mode 100644 index 0000000..a23dbcc Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201804010000.zip new file mode 100644 index 0000000..e8a270f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201804010000.zip new file mode 100644 index 0000000..55d5987 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201804010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201804010000.zip new file mode 100644 index 0000000..ef6396f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201804010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201805010000.zip new file mode 100644 index 0000000..8225284 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201805010000.zip new file mode 100644 index 0000000..1cf80ec Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201805010000.zip new file mode 100644 index 0000000..caec0e9 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201805010000.zip new file mode 100644 index 0000000..fb5e45b Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201805010000.zip new file mode 100644 index 0000000..f577f3c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201805010000.zip new file mode 100644 index 0000000..7868422 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201805010000.zip new file mode 100644 index 0000000..9abe38a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201805010000.zip new file mode 100644 index 0000000..b8eee6d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201805010000.zip new file mode 100644 index 0000000..5766bfb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201805010000.zip new file mode 100644 index 0000000..739f409 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201805010000.zip new file mode 100644 index 0000000..1c222fe Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201805010000.zip new file mode 100644 index 0000000..db6aefe Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201805010000.zip new file mode 100644 index 0000000..a000235 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201805010000.zip new file mode 100644 index 0000000..8746ffb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201805010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201805010000.zip new file mode 100644 index 0000000..10f863c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2018/MMSDM_2018_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201805010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201912010000.zip new file mode 100644 index 0000000..fdce5b3 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201912010000.zip new file mode 100644 index 0000000..97bb46a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201912010000.zip new file mode 100644 index 0000000..1c8ae80 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201912010000.zip new file mode 100644 index 0000000..e3f1bc6 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201912010000.zip new file mode 100644 index 0000000..8cc508d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201912010000.zip new file mode 100644 index 0000000..e9609f5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201912010000.zip new file mode 100644 index 0000000..fe0895a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201912010000.zip new file mode 100644 index 0000000..c4b6730 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201912010000.zip new file mode 100644 index 0000000..67d7341 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201912010000.zip new file mode 100644 index 0000000..b022e45 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_201912010000.zip new file mode 100644 index 0000000..1ab82c3 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201912010000.zip new file mode 100644 index 0000000..44aff28 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201912010000.zip new file mode 100644 index 0000000..4e71715 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201912010000.zip new file mode 100644 index 0000000..4f87743 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201912010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201912010000.zip new file mode 100644 index 0000000..96b8bcb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2019/MMSDM_2019_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_201912010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202001010000.zip new file mode 100644 index 0000000..09ab124 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202001010000.zip new file mode 100644 index 0000000..dbe2a44 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202001010000.zip new file mode 100644 index 0000000..c03fc8f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202001010000.zip new file mode 100644 index 0000000..a90b6e0 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202001010000.zip new file mode 100644 index 0000000..cb9e35d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202001010000.zip new file mode 100644 index 0000000..3980962 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202001010000.zip new file mode 100644 index 0000000..9f8e3d5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202001010000.zip new file mode 100644 index 0000000..7a8d527 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202001010000.zip new file mode 100644 index 0000000..fd95118 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202001010000.zip new file mode 100644 index 0000000..fdf923a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202001010000.zip new file mode 100644 index 0000000..28fefef Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202001010000.zip new file mode 100644 index 0000000..cc3aaea Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202001010000.zip new file mode 100644 index 0000000..cde5200 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202001010000.zip new file mode 100644 index 0000000..5ecaa97 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202001010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202001010000.zip new file mode 100644 index 0000000..277b54f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2020/MMSDM_2020_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202001010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202101010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202101010000.zip new file mode 100644 index 0000000..f2a3538 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202101010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202101010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202101010000.zip new file mode 100644 index 0000000..6860154 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202101010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202101010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202101010000.zip new file mode 100644 index 0000000..bd4b64f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202101010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202102010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202102010000.zip new file mode 100644 index 0000000..e7ee2a5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDDAYOFFER_D_202102010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202102010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202102010000.zip new file mode 100644 index 0000000..adfeb75 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_BIDPEROFFER_D_202102010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202102010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202102010000.zip new file mode 100644 index 0000000..5aa11aa Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_02/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_PEROFFER_202102010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202104010000.zip new file mode 100644 index 0000000..2ef134e Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202104010000.zip new file mode 100644 index 0000000..3dacc86 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202104010000.zip new file mode 100644 index 0000000..da1f970 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202104010000.zip new file mode 100644 index 0000000..b698be7 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202104010000.zip new file mode 100644 index 0000000..48776f1 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202104010000.zip new file mode 100644 index 0000000..61f48bc Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202104010000.zip new file mode 100644 index 0000000..0968969 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202104010000.zip new file mode 100644 index 0000000..3e8845a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202104010000.zip new file mode 100644 index 0000000..eb846b8 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202104010000.zip new file mode 100644 index 0000000..0f48460 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202104010000.zip new file mode 100644 index 0000000..e058e49 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202104010000.zip new file mode 100644 index 0000000..b337872 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202104010000.zip new file mode 100644 index 0000000..5631880 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202104010000.zip new file mode 100644 index 0000000..1625ee1 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202104010000.zip new file mode 100644 index 0000000..c4fee46 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202104010000.zip new file mode 100644 index 0000000..1d981b5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202104010000.zip new file mode 100644 index 0000000..67c9a15 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202104010000.zip new file mode 100644 index 0000000..aaea616 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202104010000.zip new file mode 100644 index 0000000..862c560 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202104010000.zip new file mode 100644 index 0000000..afdbfab Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202104010000.zip new file mode 100644 index 0000000..ba41d9d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202104010000.zip new file mode 100644 index 0000000..da7a97f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202104010000.zip new file mode 100644 index 0000000..dd70a0c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202104010000.zip new file mode 100644 index 0000000..a8c98a7 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202104010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202104010000.zip new file mode 100644 index 0000000..c1b16e4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_04/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202104010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202105010000.zip new file mode 100644 index 0000000..1c08ed9 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202105010000.zip new file mode 100644 index 0000000..b6518ed Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202105010000.zip new file mode 100644 index 0000000..ddddc85 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202105010000.zip new file mode 100644 index 0000000..dad377f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202105010000.zip new file mode 100644 index 0000000..fdc5031 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202105010000.zip new file mode 100644 index 0000000..640df17 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202105010000.zip new file mode 100644 index 0000000..23d77d9 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAILSUMMARY_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202105010000.zip new file mode 100644 index 0000000..efe20ef Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DUDETAIL_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202105010000.zip new file mode 100644 index 0000000..a31a8d8 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_GENCONDATA_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202105010000.zip new file mode 100644 index 0000000..4a656c4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTORCONSTRAINT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202105010000.zip new file mode 100644 index 0000000..6795083 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_INTERCONNECTOR_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202105010000.zip new file mode 100644 index 0000000..bb31d31 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSFACTORMODEL_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202105010000.zip new file mode 100644 index 0000000..79fdd80 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_LOSSMODEL_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202105010000.zip new file mode 100644 index 0000000..46ee23e Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MARKET_PRICE_THRESHOLDS_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202105010000.zip new file mode 100644 index 0000000..e5ce5d7 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_DAYOFFER_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202105010000.zip new file mode 100644 index 0000000..fd68872 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_MNSP_INTERCONNECTOR_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202105010000.zip new file mode 100644 index 0000000..f25a855 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_PARTICIPANT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202105010000.zip new file mode 100644 index 0000000..abc52d3 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202105010000.zip new file mode 100644 index 0000000..1c0bde5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDCONNECTIONPOINTCONSTRAINT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202105010000.zip new file mode 100644 index 0000000..4bf1f6a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDINTERCONNECTORCONSTRAINT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202105010000.zip new file mode 100644 index 0000000..e1cd6e9 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_SPDREGIONCONSTRAINT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202105010000.zip new file mode 100644 index 0000000..302fc8c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202105010000.zip new file mode 100644 index 0000000..6c36008 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGLOAD_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202105010000.zip new file mode 100644 index 0000000..eb183e3 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202105010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202105010000.zip new file mode 100644 index 0000000..b14d7cc Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGREGIONSUM_202105010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202109010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202109010000.zip new file mode 100644 index 0000000..4eed940 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202109010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202109010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202109010000.zip new file mode 100644 index 0000000..6ed2124 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202109010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202110010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202110010000.zip new file mode 100644 index 0000000..e08b6a4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202110010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202110010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202110010000.zip new file mode 100644 index 0000000..999af24 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_10/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202110010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202112010000.zip new file mode 100644 index 0000000..ea1efa4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202112010000.zip new file mode 100644 index 0000000..914e028 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202112010000.zip new file mode 100644 index 0000000..3752bc6 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202112010000.zip new file mode 100644 index 0000000..f3f30b3 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202112010000.zip new file mode 100644 index 0000000..b3dae2d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202112010000.zip new file mode 100644 index 0000000..c64af77 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202112010000.zip new file mode 100644 index 0000000..e3945c8 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202112010000.zip new file mode 100644 index 0000000..9053f02 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202112010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202112010000.zip new file mode 100644 index 0000000..f258f6d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2021/MMSDM_2021_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202112010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202201010000.zip new file mode 100644 index 0000000..69da75b Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202201010000.zip new file mode 100644 index 0000000..36ebd21 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202201010000.zip new file mode 100644 index 0000000..28c88f8 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202201010000.zip new file mode 100644 index 0000000..f789453 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202201010000.zip new file mode 100644 index 0000000..104b13e Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202201010000.zip new file mode 100644 index 0000000..4c9263f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202201010000.zip new file mode 100644 index 0000000..93d76de Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202201010000.zip new file mode 100644 index 0000000..be1c772 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202201010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202201010000.zip new file mode 100644 index 0000000..331d868 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202201010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202205010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202205010000.zip new file mode 100644 index 0000000..9dce3b5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202205010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202205010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202205010000.zip new file mode 100644 index 0000000..32f0408 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_05/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202205010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202206010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202206010000.zip new file mode 100644 index 0000000..abd0df4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202206010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202206010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202206010000.zip new file mode 100644 index 0000000..8ddeda2 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2022/MMSDM_2022_06/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202206010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202407010000.zip new file mode 100644 index 0000000..eab9a6d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHCONSTRAINT_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202407010000.zip new file mode 100644 index 0000000..bb27691 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHINTERCONNECTORRES_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202407010000.zip new file mode 100644 index 0000000..1f648dc Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHLOAD_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202407010000.zip new file mode 100644 index 0000000..8e13100 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHPRICE_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202407010000.zip new file mode 100644 index 0000000..0249acd Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCHREGIONSUM_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202407010000.zip new file mode 100644 index 0000000..8f8b26f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_DISPATCH_UNIT_SCADA_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202407010000.zip new file mode 100644 index 0000000..7385667 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_ROOFTOP_PV_ACTUAL_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202407010000.zip new file mode 100644 index 0000000..95dda01 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGINTERCONNECT_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202407010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202407010000.zip new file mode 100644 index 0000000..8bf809c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_07/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_TRADINGPRICE_202407010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202408010000.zip new file mode 100644 index 0000000..08fbf6c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202408010000.zip new file mode 100644 index 0000000..81d35c2 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202408010000.zip new file mode 100644 index 0000000..75f4e00 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202408010000.zip new file mode 100644 index 0000000..b6b7cbb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202408010000.zip new file mode 100644 index 0000000..84bd753 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202408010000.zip new file mode 100644 index 0000000..0f8b5df Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202408010000.zip new file mode 100644 index 0000000..b06677b Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202408010000.zip new file mode 100644 index 0000000..388059c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202408010000.zip new file mode 100644 index 0000000..da01595 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202408010000.zip new file mode 100644 index 0000000..95ce871 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202408010000.zip new file mode 100644 index 0000000..811b166 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202408010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202408010000.zip new file mode 100644 index 0000000..01cfda6 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_08/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202408010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202409010000.zip new file mode 100644 index 0000000..47ccba8 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202409010000.zip new file mode 100644 index 0000000..4f98c6d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202409010000.zip new file mode 100644 index 0000000..c1c746d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202409010000.zip new file mode 100644 index 0000000..9aa680f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202409010000.zip new file mode 100644 index 0000000..c8aad8a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202409010000.zip new file mode 100644 index 0000000..23790f2 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202409010000.zip new file mode 100644 index 0000000..7a7cac4 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202409010000.zip new file mode 100644 index 0000000..272d5b5 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202409010000.zip new file mode 100644 index 0000000..3083e13 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202409010000.zip new file mode 100644 index 0000000..13dd2fb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202409010000.zip new file mode 100644 index 0000000..1d20691 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202409010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202409010000.zip new file mode 100644 index 0000000..216c055 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_09/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202409010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202412010000.zip new file mode 100644 index 0000000..4313e2a Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202412010000.zip new file mode 100644 index 0000000..88da9fd Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202412010000.zip new file mode 100644 index 0000000..5dade2d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202412010000.zip new file mode 100644 index 0000000..7e46ae2 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202412010000.zip new file mode 100644 index 0000000..5d8795c Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202412010000.zip new file mode 100644 index 0000000..597c6fc Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202412010000.zip new file mode 100644 index 0000000..3db3c37 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202412010000.zip new file mode 100644 index 0000000..b324440 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202412010000.zip new file mode 100644 index 0000000..f927bd6 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202412010000.zip new file mode 100644 index 0000000..1e8d354 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202412010000.zip new file mode 100644 index 0000000..222a5bb Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202412010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202412010000.zip new file mode 100644 index 0000000..57a5e7d Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2024/MMSDM_2024_12/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202412010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202501010000.zip new file mode 100644 index 0000000..6e2a0a2 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDDAYOFFER_D#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202501010000.zip new file mode 100644 index 0000000..a9dad68 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#BIDPEROFFER_D#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202501010000.zip new file mode 100644 index 0000000..5aed03e Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHCONSTRAINT#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202501010000.zip new file mode 100644 index 0000000..3b8c17e Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHINTERCONNECTORRES#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202501010000.zip new file mode 100644 index 0000000..f886ca0 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHLOAD#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202501010000.zip new file mode 100644 index 0000000..ffd5979 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHPRICE#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202501010000.zip new file mode 100644 index 0000000..d7cdd61 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCHREGIONSUM#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202501010000.zip new file mode 100644 index 0000000..e2d5415 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#DISPATCH_UNIT_SCADA#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202501010000.zip new file mode 100644 index 0000000..b35d863 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#MNSP_DAYOFFER#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202501010000.zip new file mode 100644 index 0000000..a0d14c7 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#ROOFTOP_PV_ACTUAL#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202501010000.zip new file mode 100644 index 0000000..c650031 Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGINTERCONNECT#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202501010000.zip b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202501010000.zip new file mode 100644 index 0000000..180e39f Binary files /dev/null and b/tests/fixtures/data/Data_Archive/Wholesale_Electricity/MMSDM/2025/MMSDM_2025_01/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_ARCHIVE#TRADINGPRICE#FILE01#202501010000.zip differ diff --git a/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/Elements_FCAS_202504151310.csv b/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/Elements_FCAS_202504151310.csv new file mode 100644 index 0000000..74e542b --- /dev/null +++ b/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/Elements_FCAS_202504151310.csv @@ -0,0 +1,517 @@ +1,"SUBSTN.LYPA.GEN.A1GEN ","GEN","*MMS MarketName*" +2,"SUBSTN.LYPA.GEN.A2GEN ","GEN","*MMS MarketName*" +3,"SUBSTN.LYPA.GEN.A3GEN ","GEN","*MMS MarketName*" +4,"SUBSTN.LYPA.GEN.A4GEN ","GEN","*MMS MarketName*" +5,"SUBSTN.LYPB.GEN.B1GEN ","GEN","*MMS MarketName*" +6,"SUBSTN.LYPB.GEN.B2GEN ","GEN","*MMS MarketName*" +7,"SUBSTN.YPS.GEN.W1GEN ","GEN","*MMS MarketName*" +8,"SUBSTN.YPS.GEN.W2GEN ","GEN","*MMS MarketName*" +9,"SUBSTN.YPS.GEN.W3GEN ","GEN","*MMS MarketName*" +10,"SUBSTN.YPS.GEN.W4GEN ","GEN","*MMS MarketName*" +11,"SUBSTN.NPSD.GEN.GEN ","GEN","*MMS MarketName*" +24,"SUBSTN.JLGSA.GEN.A1GEN ","GEN","*MMS MarketName*" +25,"SUBSTN.JLGSA.GEN.A2GEN ","GEN","*MMS MarketName*" +26,"SUBSTN.JLGSA.GEN.A3GEN ","GEN","*MMS MarketName*" +27,"SUBSTN.JLGSA.GEN.A4GEN ","GEN","*MMS MarketName*" +28,"SUBSTN.JLGSB.GEN.B1GEN ","GEN","*MMS MarketName*" +29,"SUBSTN.JLGSB.GEN.B2GEN ","GEN","*MMS MarketName*" +30,"SUBSTN.JLGSB.GEN.B3GEN ","GEN","*MMS MarketName*" +31,"SUBSTN.DPS.GEN.GEN ","GEN","*MMS MarketName*" +32,"SUBSTN.EPS.GEN.1GEN ","GEN","*MMS MarketName*" +33,"SUBSTN.EPS.GEN.2GEN ","GEN","*MMS MarketName*" +36,"SUBSTN.WKPS.GEN.GEN_1&2 ","GEN","*MMS MarketName*" +37,"SUBSTN.WKPS.GEN.GEN_3&4 ","GEN","*MMS MarketName*" +41,"SUBSTN.TUMUT1_2.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +42,"SUBSTN.TUMUT3.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +43,"SUBSTN.MURRAY.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +44,"SUBSTN.GUTHEGA.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +45,"SUBSTN.JINDABYN.UNIT.1 ","GEN","*MMS MarketName*" +46,"SUBSTN.JINDABYN.UNIT.2 ","GEN","*MMS MarketName*" +47,"SUBSTN.BAYSWATR.UNIT.1 ","GEN","*MMS MarketName*" +48,"SUBSTN.BAYSWATR.UNIT.2 ","GEN","*MMS MarketName*" +49,"SUBSTN.BAYSWATR.UNIT.3 ","GEN","*MMS MarketName*" +50,"SUBSTN.BAYSWATR.UNIT.4 ","GEN","*MMS MarketName*" +51,"SUBSTN.ERARING.UNIT.1 ","GEN","*MMS MarketName*" +52,"SUBSTN.ERARING.UNIT.2 ","GEN","*MMS MarketName*" +53,"SUBSTN.ERARING.UNIT.3 ","GEN","*MMS MarketName*" +54,"SUBSTN.ERARING.UNIT.4 ","GEN","*MMS MarketName*" +55,"SUBSTN.MT_PIPER.UNIT.1 ","GEN","*MMS MarketName*" +56,"SUBSTN.MT_PIPER.UNIT.2 ","GEN","*MMS MarketName*" +57,"SUBSTN.VALES_PT.UNIT.5 ","GEN","*MMS MarketName*" +58,"SUBSTN.VALES_PT.UNIT.6 ","GEN","*MMS MarketName*" +59,"SUBSTN.LIDDELL.UNIT.1 ","GEN","*MMS MarketName*" +60,"SUBSTN.LIDDELL.UNIT.2 ","GEN","*MMS MarketName*" +61,"SUBSTN.LIDDELL.UNIT.3 ","GEN","*MMS MarketName*" +62,"SUBSTN.LIDDELL.UNIT.4 ","GEN","*MMS MarketName*" +69,"SUBSTN.BRKNHILL.UNIT.GT1 ","GEN","*MMS MarketName*" +70,"SUBSTN.BRKNHILL.UNIT.GT2 ","GEN","*MMS MarketName*" +76,"SUBSTN.HUME.UNIT.HUMENSW ","GEN","*MMS MarketName*" +77,"SUBSTN.HUME.UNIT.HUMEVIC ","GEN","*MMS MarketName*" +79,"SUBSTN.BLOWERG.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +82,"SUBSTN.TIPS_A.GEN.TA_1 ","GEN","*MMS MarketName*" +84,"SUBSTN.TIPS_A.GEN.TA_3 ","GEN","*MMS MarketName*" +86,"SUBSTN.TIPS_B.GEN.TB_1 ","GEN","*MMS MarketName*" +87,"SUBSTN.TIPS_B.GEN.TB_2 ","GEN","*MMS MarketName*" +88,"SUBSTN.TIPS_B.GEN.TB_3 ","GEN","*MMS MarketName*" +89,"SUBSTN.TIPS_B.GEN.TB_4 ","GEN","*MMS MarketName*" +90,"SUBSTN.PEL_PS.GEN.PP_1 ","GEN","*MMS MarketName*" +92,"SUBSTN.DRY_CK.GEN.DC_1 ","GEN","*MMS MarketName*" +93,"SUBSTN.DRY_CK.GEN.DC_2 ","GEN","*MMS MarketName*" +94,"SUBSTN.DRY_CK.GEN.DC_3 ","GEN","*MMS MarketName*" +95,"SUBSTN.MINTAR.GEN.MN_1 ","GEN","*MMS MarketName*" +96,"SUBSTN.SNUGGY.GEN.SN_1_2_3 ","GEN","*MMS MarketName*" +97,"SUBSTN.OCPL.GEN.OC_1_2 ","GEN","*MMS MarketName*" +98,"SUBSTN.LADB_G.GEN.LG_1 ","GEN","*MMS MarketName*" +99,"SUBSTN.LADB_G.GEN.LG_2 ","GEN","*MMS MarketName*" +100,"SUBSTN.PT_L_T.GEN.PL_1_2 ","GEN","*MMS MarketName*" +101,"SUBSTN.TRNG_PS.GEN.1_TRNG_PS ","GEN","*MMS MarketName*" +102,"SUBSTN.TRNG_PS.GEN.2_TRNG_PS ","GEN","*MMS MarketName*" +103,"SUBSTN.TRNG_PS.GEN.3_TRNG_PS ","GEN","*MMS MarketName*" +104,"SUBSTN.TRNG_PS.GEN.4_TRNG_PS ","GEN","*MMS MarketName*" +105,"SUBSTN.TRNG_PS.GEN.GT_TRNG_PS ","GEN","*MMS MarketName*" +106,"SUBSTN.STAN_PS.GEN.1_STAN_PS ","GEN","*MMS MarketName*" +107,"SUBSTN.STAN_PS.GEN.2_STAN_PS ","GEN","*MMS MarketName*" +108,"SUBSTN.STAN_PS.GEN.3_STAN_PS ","GEN","*MMS MarketName*" +109,"SUBSTN.STAN_PS.GEN.4_STAN_PS ","GEN","*MMS MarketName*" +114,"SUBSTN.CAL_B_PS.GEN.1_CAL_B_PS ","GEN","*MMS MarketName*" +115,"SUBSTN.CAL_B_PS.GEN.2_CAL_B_PS ","GEN","*MMS MarketName*" +116,"SUBSTN.CAL_C_PS.GEN.3_CAL_C_PS ","GEN","*MMS MarketName*" +117,"SUBSTN.GLAD_PS.GEN.1_GLAD_PS ","GEN","*MMS MarketName*" +118,"SUBSTN.GLAD_PS.GEN.2_GLAD_PS ","GEN","*MMS MarketName*" +119,"SUBSTN.GLAD_PS.GEN.3_GLAD_PS ","GEN","*MMS MarketName*" +120,"SUBSTN.GLAD_PS.GEN.4_GLAD_PS ","GEN","*MMS MarketName*" +121,"SUBSTN.GLAD_PS.GEN.5_GLAD_PS ","GEN","*MMS MarketName*" +122,"SUBSTN.GLAD_PS.GEN.6_GLAD_PS ","GEN","*MMS MarketName*" +128,"SUBSTN.T36_INVA.GEN.1_T36_INVA ","GEN","*MMS MarketName*" +141,"SUBSTN.MT_S_PS.GEN.1_MT_S_PS ","GEN","*MMS MarketName*" +142,"SUBSTN.MT_S_PS.GEN.2_MT_S_PS ","GEN","*MMS MarketName*" +143,"SUBSTN.TVLLE_PS.GEN.GT_TVLLE_PS ","GEN","*MMS MarketName*" +144,"SUBSTN.OAKEY_PS.GEN.1_OAK_PS ","GEN","*MMS MarketName*" +145,"SUBSTN.OAKEY_PS.GEN.2_OAK_PS ","GEN","*MMS MarketName*" +146,"SUBSTN.ROMA_PS.GEN.7_ROMA_PS ","GEN","*MMS MarketName*" +147,"SUBSTN.ROMA_PS.GEN.8_ROMA_PS ","GEN","*MMS MarketName*" +149,"SUBSTN.BCDE_PS.GEN.GT_BCDE_PS ","GEN","*MMS MarketName*" +151,"SUBSTN.WIV_PS.GEN.1_WIV_PS ","GEN","*MMS MarketName*" +152,"SUBSTN.WIV_PS.GEN.2_WIV_PS ","GEN","*MMS MarketName*" +153,"SUBSTN.KAR_H_PS.GEN.1_KAR_H_PS ","GEN","*MMS MarketName*" +154,"SUBSTN.KAR_H_PS.GEN.2_KAR_H_PS ","GEN","*MMS MarketName*" +155,"SUBSTN.KAR_H_PS.GEN.3_KAR_H_PS ","GEN","*MMS MarketName*" +156,"SUBSTN.KAR_H_PS.GEN.4_KAR_H_PS ","GEN","*MMS MarketName*" +157,"SUBSTN.KAR_H_PS.GEN.5_KAR_H_PS ","GEN","*MMS MarketName*" +158,"SUBSTN.T54_BG_H.GEN.1_B_G_H_PS ","GEN","*MMS MarketName*" +159,"SUBSTN.T54_BG_H.GEN.2_B_G_H_PS ","GEN","*MMS MarketName*" +160,"SUBSTN.BDL_PS.GEN.1GEN ","GEN","*MMS MarketName*" +161,"SUBSTN.TUMUT3.LOAD.SUMM_PUMPS ","GEN","*MMS MarketName*" +162,"SUBSTN.SITHE.UNIT.SITHE01 ","GEN","*MMS MarketName*" +165,"SUBSTN.SHOALHAV.UNIT.BENDEELA_KAN ","GEN","*MMS MarketName*" +166,"SUBSTN.SHOALHAV.SUMM.PUMPS ","GEN","*MMS MarketName*" +167,"SUBSTN.WIV_PS.LOAD.1PUMP ","GEN","*MMS MarketName*" +168,"SUBSTN.WIV_PS.LOAD.2PUMP ","GEN","*MMS MarketName*" +169,"SUBSTN.CAL_C_PS.GEN.4_CAL_C_PS ","GEN","*MMS MarketName*" +170,"SUBSTN.QPS.GEN.QP_1 ","GEN","*MMS MarketName*" +171,"SUBSTN.QPS.GEN.QP_2 ","GEN","*MMS MarketName*" +172,"SUBSTN.QPS.GEN.QP_3 ","GEN","*MMS MarketName*" +173,"SUBSTN.QPS.GEN.QP_4 ","GEN","*MMS MarketName*" +180,"SUBSTN.HALLET.GEN.HA_1 ","GEN","*MMS MarketName*" +181,"SUBSTN.BDL_PS.GEN.2GEN ","GEN","*MMS MarketName*" +182,"SUBSTN.SPS.GEN.GEN_1_2_3_4 ","GEN","*MMS MarketName*" +183,"SUBSTN.SBK_E_PS.GEN.1_SBK_E_PS ","GEN","*MMS MarketName*" +184,"SUBSTN.MILLM_PS.GEN.1_MILLM_PS ","GEN","*MMS MarketName*" +185,"SUBSTN.MILLM_PS.GEN.2_MILLM_PS ","GEN","*MMS MarketName*" +186,"SUBSTN.TRNGN_PS.GEN.1_TRNGN_PS ","GEN","*MMS MarketName*" +189,"SUBSTN.TVLLE_PS.GEN.2_TVLLE_PS ","GEN","*MMS MarketName*" +190,"SUBSTN.TUNGATIN.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +191,"SUBSTN.TRIBUTE.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +192,"SUBSTN.TREVALYN.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +193,"SUBSTN.TARRALEA.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +194,"SUBSTN.REECE.UNIT.AGGREGATE_1 ","GEN","*MMS MarketName*" +195,"SUBSTN.REECE.UNIT.AGGREGATE_2 ","GEN","*MMS MarketName*" +196,"SUBSTN.POATINA.UNIT.AGGREGATE_1 ","GEN","*MMS MarketName*" +197,"SUBSTN.POATINA.UNIT.AGGREGATE_2 ","GEN","*MMS MarketName*" +198,"SUBSTN.MDWBANK.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +199,"SUBSTN.MAKNTOSH.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +200,"SUBSTN.L_ECHO.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +201,"SUBSTN.LI_WY_CA.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +202,"SUBSTN.LEM_WIL.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +203,"SUBSTN.JBUTTERS.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +204,"SUBSTN.GORDON.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +205,"SUBSTN.FISHER.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +206,"SUBSTN.DEVILS_G.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +207,"SUBSTN.CETHANA.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +208,"SUBSTN.BASTYAN.UNIT.AGGREGATE ","GEN","*MMS MarketName*" +212,"SUBSTN.BRAE_PS.GEN.1_BRAE_PS ","GEN","*MMS MarketName*" +213,"SUBSTN.BRAE_PS.GEN.2_BRAE_PS ","GEN","*MMS MarketName*" +214,"SUBSTN.BRAE_PS.GEN.3_BRAE_PS ","GEN","*MMS MarketName*" +215,"SUBSTN.TAMAR_PS.UNIT.G101 ","GEN","*MMS MarketName*" +216,"SUBSTN.TAMAR_PS.UNIT.G102 ","GEN","*MMS MarketName*" +217,"SUBSTN.TAMAR_PS.UNIT.G103 ","GEN","*MMS MarketName*" +219,"SUBSTN.MAYURA.GEN.LAKE_BONY2 ","GEN","*MMS MarketName*" +220,"SUBSTN.WATT_P.GEN.WP_1 ","GEN","*MMS MarketName*" +221,"SUBSTN.MT_MIL.TF.T_1 ","GEN","*MMS MarketName*" +222,"SUBSTN.BUTLERS.UNIT.M/C ","GEN","*MMS MarketName*" +223,"SUBSTN.CLUNY.UNIT.M/C ","GEN","*MMS MarketName*" +224,"SUBSTN.PALOONA.UNIT.M/C ","GEN","*MMS MarketName*" +225,"SUBSTN.MAYURA.LINE.MAYLBY_1 ","GEN","*MMS MarketName*" +226,"SUBSTN.CATH_R.GEN.CR_1 ","GEN","*MMS MarketName*" +227,"SUBSTN.TAS1.SUMM.WOOLNORTH ","GEN","*MMS MarketName*" +228,"SUBSTN.YWF.GEN.YWF ","GEN","*MMS MarketName*" +229,"SUBSTN.KOGAN_PS.GEN.1_KOGAN_PS ","GEN","*MMS MarketName*" +230,"SUBSTN.HAL_WF.GEN.HAL1 ","GEN","*MMS MarketName*" +231,"SUBSTN.SNW_WF.GEN.ST_1 ","GEN","*MMS MarketName*" +232,"SUBSTN.ERARING.UNIT.GT1 ","GEN","*MMS MarketName*" +233,"SUBSTN.QPS.GEN.QP_5 ","GEN","*MMS MarketName*" +234,"SUBSTN.TALAWARA.UNIT.1 ","GEN","*MMS MarketName*" +235,"SUBSTN.URANQNTY.UNIT.U11 ","GEN","*MMS MarketName*" +236,"SUBSTN.URANQNTY.UNIT.U12 ","GEN","*MMS MarketName*" +237,"SUBSTN.URANQNTY.UNIT.U13 ","GEN","*MMS MarketName*" +238,"SUBSTN.URANQNTY.UNIT.U14 ","GEN","*MMS MarketName*" +239,"SUBSTN.T200CMPS.GEN.STN_CMPS ","GEN","*MMS MarketName*" +240,"SUBSTN.WBTS.GEN.GEN ","GEN","*MMS MarketName*" +241,"SUBSTN.H48_B2PS.GEN.1_B2PS ","GEN","*MMS MarketName*" +242,"SUBSTN.H48_B2PS.GEN.2_B2PS ","GEN","*MMS MarketName*" +243,"SUBSTN.H48_B2PS.GEN.3_B2PS ","GEN","*MMS MarketName*" +244,"SUBSTN.COLONGRA.UNIT.1 ","GEN","*MMS MarketName*" +245,"SUBSTN.COLONGRA.UNIT.2 ","GEN","*MMS MarketName*" +246,"SUBSTN.COLONGRA.UNIT.3 ","GEN","*MMS MarketName*" +247,"SUBSTN.COLONGRA.UNIT.4 ","GEN","*MMS MarketName*" +248,"SUBSTN.TAMAR_PS.UNIT.G104 ","GEN","*MMS MarketName*" +249,"SUBSTN.CULLERIN.UNIT.1 ","GEN","*MMS MarketName*" +250,"SUBSTN.CAPITAL.UNIT.C1 ","GEN","*MMS MarketName*" +251,"SUBSTN.CLEM_G.GEN.CG_1 ","GEN","*MMS MarketName*" +252,"SUBSTN.HALHIL.GEN.HLH_1 ","GEN","*MMS MarketName*" +253,"SUBSTN.TAMAR_PS.UNIT.CC ","GEN","*MMS MarketName*" +254,"SUBSTN.MKPS.GEN.MCKAY1 ","GEN","*MMS MarketName*" +255,"SUBSTN.MT_S_PS.GEN.3_MT_S_PS ","GEN","*MMS MarketName*" +256,"SUBSTN.H71_DDPS.GEN.STN_DDPS ","GEN","*MMS MarketName*" +257,"SUBSTN.YARWUN.GEN.1_YARWUN ","GEN","*MMS MarketName*" +258,"SUBSTN.NO_B_H.GEN.NBH_1 ","GEN","*MMS MarketName*" +259,"SUBSTN.MAYURA.GEN.LAKE_BONY3 ","GEN","*MMS MarketName*" +260,"SUBSTN.PT_L_T.GEN.PL_3 ","GEN","*MMS MarketName*" +261,"SUBSTN.WAT_WF.GEN.WW_1 ","GEN","*MMS MarketName*" +262,"SUBSTN.GUNNING.TRANS.TCS1 ","GEN","*MMS MarketName*" +263,"SUBSTN.WOODLAWN.UNIT.1 ","GEN","*MMS MarketName*" +264,"SUBSTN.MOPS.GEN.GEN11 ","GEN","*MMS MarketName*" +265,"SUBSTN.MOPS.GEN.GEN12 ","GEN","*MMS MarketName*" +266,"SUBSTN.BLUFF_WF.GEN.PPR_1 ","GEN","*MMS MarketName*" +267,"SUBSTN.OWF.GEN.OWF ","GEN","*MMS MarketName*" +268,"SUBSTN.MWF.GEN.MACARTH ","GEN","*MMS MarketName*" +270,"SUBSTN.MLWF.TRANS.TRANS1 ","GEN","*MMS MarketName*" +271,"SUBSTN.MUSSELRO.GEN.WTG_ABCD ","GEN","*MMS MarketName*" +272,"SUBSTN.SNW_NTH.GEN.SNOWNTH1 ","GEN","*MMS MarketName*" +273,"SUBSTN.SNW_STH.GEN.SNOWSTH1 ","GEN","*MMS MarketName*" +274,"SUBSTN.GRWF.GEN.GULLRWF1 ","GEN","*MMS MarketName*" +275,"SUBSTN.MEWF.GEN.MERCER01 ","GEN","*MMS MarketName*" +276,"SUBSTN.BOCOROCK.UNIT.BOCORWF1 ","GEN","*MMS MarketName*" +277,"SUBSTN.NYNGN_SF.UNIT.NYNGAN1 ","GEN","*MMS MarketName*" +278,"SUBSTN.TARALGA.UNIT.TARALGA1 ","GEN","*MMS MarketName*" +279,"SUBSTN.BHWF.GEN.BALDHWF1 ","GEN","*MMS MarketName*" +281,"SUBSTN.BRKNHILL.UNIT.GTGEN_SUMM ","GEN","*MMS MarketName*" +282,"SUBSTN.GERM_CRK.GEN.GEN_AGGR ","GEN","*MMS MarketName*" +283,"SUBSTN.MORANBAH.GEN.MBH_NTH ","GEN","*MMS MarketName*" +284,"SUBSTN.REPULSE.UNIT.M/C ","GEN","*MMS MarketName*" +285,"SUBSTN.SSRPT_PS.GEN.1_SSRPT_PS ","GEN","*MMS MarketName*" +286,"SUBSTN.CAPE_J.SUMM.STAR_HILL ","GEN","*MMS MarketName*" +287,"SUBSTN.LNGS.UNIT.1 ","GEN","*MMS MarketName*" +288,"SUBSTN.LNGS.UNIT.2 ","GEN","*MMS MarketName*" +289,"SUBSTN.VPGS.UNIT.1 ","GEN","*MMS MarketName*" +290,"SUBSTN.VPGS.UNIT.2 ","GEN","*MMS MarketName*" +291,"SUBSTN.VPGS.UNIT.3 ","GEN","*MMS MarketName*" +292,"SUBSTN.VPGS.UNIT.4 ","GEN","*MMS MarketName*" +293,"SUBSTN.VPGS.UNIT.5 ","GEN","*MMS MarketName*" +294,"SUBSTN.VPGS.UNIT.6 ","GEN","*MMS MarketName*" +295,"SUBSTN.BRKNH_SF.UNIT.BROKENH1 ","GEN","*MMS MarketName*" +296,"SUBSTN.SNUGGY.SUMM.CANUNDA ","GEN","*MMS MarketName*" +297,"SUBSTN.ROWALLAN.UNIT.M/C ","GEN","*MMS MarketName*" +298,"SUBSTN.JOUNAMA.UNIT.1 ","GEN","*MMS MarketName*" +299,"SUBSTN.MOREE_SF.UNIT.MOREESF1 ","GEN","*MMS MarketName*" +300,"SUBSTN.PT_STA.GEN.PS_1 ","GEN","*MMS MarketName*" +301,"SUBSTN.PT_STA.GEN.LONSDALE ","GEN","*MMS MarketName*" +309,"SUBSTN.ROYALLA.GEN.ROYALLA ","GEN","*MMS MarketName*" +311,"SUBSTN.ANGAST.GEN.ANGAS ","GEN","*MMS MarketName*" +312,"SUBSTN.HRN_WF.GEN.HDWF1 ","GEN","*MMS MarketName*" +313,"SUBSTN.ARWF.GEN.ARWF1 ","GEN","*MMS MarketName*" +314,"SUBSTN.MUGGA_SF.GEN.MUGGALANESF ","GEN","*MMS MarketName*" +315,"SUBSTN.BASOL_SF.GEN.BASOL_SF ","GEN","*MMS MarketName*" +316,"SUBSTN.HRN_WF.GEN.HDWF2 ","GEN","*MMS MarketName*" +317,"SUBSTN.WRWF.GEN.WRWF1 ","GEN","*MMS MarketName*" +318,"SUBSTN.GRWF.GEN.GULLRSF1 ","GEN","*MMS MarketName*" +319,"SUBSTN.HRN_WF.GEN.HDWF3 ","GEN","*MMS MarketName*" +320,"SUBSTN.PARK_SF.GEN.PSF1 ","GEN","*MMS MarketName*" +321,"SUBSTN.GRIFF_SF.GEN.GRIFSF1 ","GEN","*MMS MarketName*" +322,"SUBSTN.TG_STH.GEN.SATGS1 ","GEN","*MMS MarketName*" +324,"SUBSTN.KWF.GEN.KIATAWF ","GEN","*MMS MarketName*" +327,"SUBSTN.SAPWF.GEN.SAPWF01 ","GEN","*MMS MarketName*" +328,"SUBSTN.T239_KSF.GEN.KSP1 ","GEN","*MMS MarketName*" +329,"SUBSTN.YSW.GEN.YSWF1 ","GEN","*MMS MarketName*" +332,"SUBSTN.CLPS.SUMM.HYDRO ","GEN","*MMS MarketName*" +333,"SUBSTN.MANLD_SF.GEN.MANSLR1 ","GEN","*MMS MarketName*" +334,"SUBSTN.HUGHN_SF.GEN.HUGSF1 ","GEN","*MMS MarketName*" +335,"SUBSTN.GSF.GEN.GANNSF1 ","GEN","*MMS MarketName*" +336,"SUBSTN.SILVERWF.GEN.STWF1 ","GEN","*MMS MarketName*" +337,"SUBSTN.T240_CSF.GEN.CLARESF ","GEN","*MMS MarketName*" +338,"SUBSTN.SCWF.GEN.SALTCRK1 ","GEN","*MMS MarketName*" +339,"SUBSTN.LRSF.GEN.LRSF1 ","GEN","*MMS MarketName*" +342,"SUBSTN.WILLOG.GEN.WGWF1 ","GEN","*MMS MarketName*" +343,"SUBSTN.BUNGAL.GEN.BNGSF1 ","GEN","*MMS MarketName*" +344,"SUBSTN.Z3_HAMSF.GEN.HAMISF1 ","GEN","*MMS MarketName*" +345,"SUBSTN.Z2_WHSF.GEN.WHITSF1 ","GEN","*MMS MarketName*" +346,"SUBSTN.MGW.GEN.MTGELWF1 ","GEN","*MMS MarketName*" +347,"SUBSTN.CROOKWF.GEN.CROOKWF2 ","GEN","*MMS MarketName*" +348,"SUBSTN.BODANGWF.GEN.BODWF1 ","GEN","*MMS MarketName*" +349,"SUBSTN.WRWF.GEN.WRSF1 ","GEN","*MMS MarketName*" +350,"SUBSTN.BSP.GEN.BANN1 ","GEN","*MMS MarketName*" +351,"SUBSTN.Z7_RRSF.GEN.RRSF1 ","GEN","*MMS MarketName*" +352,"SUBSTN.H86_DDSF.GEN.DDSF1 ","GEN","*MMS MarketName*" +353,"SUBSTN.TVLZ_SF.GEN.SMCSF1 ","GEN","*MMS MarketName*" +354,"SUBSTN.COLMB_SF.GEN.COLEASF1 ","GEN","*MMS MarketName*" +355,"SUBSTN.BUNGAL.GEN.BNGSF2 ","GEN","*MMS MarketName*" +356,"SUBSTN.H84_MTEW.GEN.MEWF1 ","GEN","*MMS MarketName*" +357,"SUBSTN.COLL_SF.GEN.CSPVPS1 ","GEN","*MMS MarketName*" +358,"SUBSTN.EMRLD_SF.GEN.EMERASF1 ","GEN","*MMS MarketName*" +359,"SUBSTN.WSF.GEN.WEMENSF1 ","GEN","*MMS MarketName*" +360,"SUBSTN.KSF.GEN.KARSF1 ","GEN","*MMS MarketName*" +361,"SUBSTN.Z5_HAYSF.GEN.HAYMSF1 ","GEN","*MMS MarketName*" +362,"SUBSTN.Z6_DAYSF.GEN.DAYDSF1 ","GEN","*MMS MarketName*" +367,"SUBSTN.LIN_GP.GEN.LGAPWF1 ","GEN","*MMS MarketName*" +368,"SUBSTN.BGR.SUMM.CHALLICUM ","GEN","*MMS MarketName*" +369,"SUBSTN.CWWF.GEN.CROWLWF1 ","GEN","*MMS MarketName*" +370,"SUBSTN.CLRMT_SF.GEN.CLERMSF1 ","GEN","*MMS MarketName*" +371,"SUBSTN.T255_RRS.GEN.RGBYRSF1 ","GEN","*MMS MarketName*" +372,"SUBSTN.Z8_LV1SF.GEN.LILYSF1 ","GEN","*MMS MarketName*" +373,"SUBSTN.OAKY1_SF.GEN.OAKEY1SF ","GEN","*MMS MarketName*" +374,"SUBSTN.Z4_CGWF.GEN.COOPGWF1 ","GEN","*MMS MarketName*" +375,"SUBSTN.SSNRV_SF.GEN.SRSF1 ","GEN","*MMS MarketName*" +376,"SUBSTN.CHLDR_SF.GEN.CHILDSF1 ","GEN","*MMS MarketName*" +377,"SUBSTN.COOR_S.GEN.TBSF1 ","GEN","*MMS MarketName*" +378,"SUBSTN.BGWF.GEN.BULGANA1 ","GEN","*MMS MarketName*" +380,"SUBSTN.NSF.GEN.NUMURSF1 ","GEN","*MMS MarketName*" +381,"SUBSTN.BERYL_SF.GEN.BERYLSF1 ","GEN","*MMS MarketName*" +382,"SUBSTN.YDW.GEN.YENDWF1 ","GEN","*MMS MarketName*" +383,"SUBSTN.OAKY2_SF.GEN.OAKEY2SF ","GEN","*MMS MarketName*" +384,"SUBSTN.H96HN2SF.GEN.HAUGHT1 ","GEN","*MMS MarketName*" +387,"SUBSTN.MOORAWF.GEN.MOORAWF1 ","GEN","*MMS MarketName*" +388,"SUBSTN.LIMN2_SF.GEN.LIMOSF21 ","GEN","*MMS MarketName*" +389,"SUBSTN.FINLY_SF.GEN.FINLYSF1 ","GEN","*MMS MarketName*" +390,"SUBSTN.YARRN_SF.GEN.YARANSF1 ","GEN","*MMS MarketName*" +391,"SUBSTN.KEPG_SF.GEN.KEPSF1 ","GEN","*MMS MarketName*" +392,"SUBSTN.KEPG_WF.GEN.KEPWF1 ","GEN","*MMS MarketName*" +393,"SUBSTN.YSF.GEN.YATSF1 ","GEN","*MMS MarketName*" +394,"SUBSTN.CATHILL.GEN.CTHLWF1 ","GEN","*MMS MarketName*" +395,"SUBSTN.BARK_I.GEN.BARKIPS1 ","GEN","*MMS MarketName*" +396,"SUBSTN.NEVRT_SF.GEN.NEVERSF1 ","GEN","*MMS MarketName*" +397,"SUBSTN.SUNRY_SF.GEN.SUNRSF1 ","GEN","*MMS MarketName*" +398,"SUBSTN.MARYR_SF.GEN.MARYRSF1 ","GEN","*MMS MarketName*" +399,"SUBSTN.GRANWF.GEN.GRANWF1 ","GEN","*MMS MarketName*" +400,"SUBSTN.SHWF.GEN.STOCKYD1 ","GEN","*MMS MarketName*" +401,"SUBSTN.LIMN1_SF.GEN.LIMOSF11 ","GEN","*MMS MarketName*" +402,"SUBSTN.DARLP_SF.GEN.DARLSF1 ","GEN","*MMS MarketName*" +403,"SUBSTN.KIAMALSF.GEN.KIAMSF1 ","GEN","*MMS MarketName*" +404,"SUBSTN.ELWF.GEN.ELAINWF1 ","GEN","*MMS MarketName*" +405,"SUBSTN.CTWF.GEN.CHYTWF1 ","GEN","*MMS MarketName*" +406,"SUBSTN.CSF.GEN.COHUNSF1 ","GEN","*MMS MarketName*" +407,"SUBSTN.DDWF.GEN.DUNDWF1 ","GEN","*MMS MarketName*" +408,"SUBSTN.DDWF.GEN.DUNDWF2 ","GEN","*MMS MarketName*" +409,"SUBSTN.DDWF.GEN.DUNDWF3 ","GEN","*MMS MarketName*" +410,"SUBSTN.BOMEN_SF.GEN.BOMENSF1 ","GEN","*MMS MarketName*" +411,"SUBSTN.WRCK1_SF.GEN.WARWSF1 ","GEN","*MMS MarketName*" +412,"SUBSTN.WRCK2_SF.GEN.WARWSF2 ","GEN","*MMS MarketName*" +413,"SUBSTN.MMNT_SF.GEN.MIDDLSF1 ","GEN","*MMS MarketName*" +414,"SUBSTN.GOON_SF.GEN.GOONSF1 ","GEN","*MMS MarketName*" +415,"SUBSTN.GRWF2.GEN.GULLRWF2 ","GEN","*MMS MarketName*" +416,"SUBSTN.BRYBNKWF.GEN.BRYB1WF1 ","GEN","*MMS MarketName*" +417,"SUBSTN.WELLNGTN.GEN.WELLSF1 ","GEN","*MMS MarketName*" +418,"SUBSTN.MOLNG_SF.GEN.MOLNGSF1 ","GEN","*MMS MarketName*" +419,"SUBSTN.COLLECWF.GEN.COLWF01 ","GEN","*MMS MarketName*" +420,"SUBSTN.BANGOWF.GEN.BANGOWF1 ","GEN","*MMS MarketName*" +421,"SUBSTN.BANGOWF.GEN.BANGOWF2 ","GEN","*MMS MarketName*" +422,"SUBSTN.GWSF.GEN.GLRWNSF1 ","GEN","*MMS MarketName*" +423,"SUBSTN.WISF.GEN.WINTSF1 ","GEN","*MMS MarketName*" +424,"SUBSTN.MW_3.GEN.MWPS3PV1 ","GEN","*MMS MarketName*" +425,"SUBSTN.MW_4.GEN.MWPS4PV1 ","GEN","*MMS MarketName*" +426,"SUBSTN.CRUDNEWF.GEN.CRURWF1 ","GEN","*MMS MarketName*" +427,"SUBSTN.JEMAL_SF.GEN.JEMALNG1 ","GEN","*MMS MarketName*" +428,"SUBSTN.MW_1.GEN.MWPS1PV1 ","GEN","*MMS MarketName*" +429,"SUBSTN.MW_2.GEN.MWPS2PV1 ","GEN","*MMS MarketName*" +430,"SUBSTN.MA_2.GEN.MAPS2PV1 ","GEN","*MMS MarketName*" +431,"SUBSTN.MA_3.GEN.MAPS3PV1 ","GEN","*MMS MarketName*" +432,"SUBSTN.CORWA_SF.GEN.CRWASF1 ","GEN","*MMS MarketName*" +433,"SUBSTN.TAB_MILL.TRANS.TG1H66","GEN","*MMS MarketName*" +434,"SUBSTN.T273_GGS.GEN.GANGARR1 ","GEN","*MMS MarketName*" +435,"SUBSTN.H104_WDS.GEN.WDGPH1 ","GEN","*MMS MarketName*" +436,"SUBSTN.ADP.GEN.ADPPV1 ","GEN","*MMS MarketName*" +439,"SUBSTN.WAGGN_SF.GEN.WAGGNSF1 ","GEN","*MMS MarketName*" +440,"SUBSTN.JUNEE_SF.GEN.JUNEESF1 ","GEN","*MMS MarketName*" +441,"SUBSTN.GUNDH_SF.GEN.GNNDHSF1 ","GEN","*MMS MarketName*" +442,"SUBSTN.MBH_2.GEN.MBPS2PV1 ","GEN","*MMS MarketName*" +449,"SUBSTN.T272_CLB.GEN.COLUMSF1 ","GEN","*MMS MarketName*" +450,"SUBSTN.SUNTP_SF.GEN.SUNTPSF1 ","GEN","*MMS MarketName*" +451,"SUBSTN.HILST_SF.GEN.HILLSTN1 ","GEN","*MMS MarketName*" +452,"SUBSTN.MRW1.GEN.MUWAWF1 ","GEN","*MMS MarketName*" +453,"SUBSTN.MRW2.GEN.MUWAWF2 ","GEN","*MMS MarketName*" +454,"SUBSTN.SEBAS_SF.GEN.SEBSF1 ","GEN","*MMS MarketName*" +455,"SUBSTN.LIN_GP.GEN.LGAPWF2 ","GEN","*MMS MarketName*" +456,"SUBSTN.BOLIVAR.GEN.BOWWPV1 ","GEN","*MMS MarketName*" +461,"SUBSTN.METZ_SF.GEN.METZSF1 ","GEN","*MMS MarketName*" +462,"SUBSTN.Z34_BGSF.GEN.BGSF1 ","GEN","*MMS MarketName*" +463,"SUBSTN.WLGA_SF.GEN.WLGASF1 ","GEN","*MMS MarketName*" +464,"SUBSTN.P_PA_N.GEN.PAREPW1 ","GEN","*MMS MarketName*" +465,"SUBSTN.P_PA_N.GEN.PAREPS1 ","GEN","*MMS MarketName*" +466,"SUBSTN.SNAP_P.GEN.SNAPPER1 ","GEN","*MMS MarketName*" +467,"SUBSTN.VIC1.NSGEN.DIAPURWF ","GEN","*MMS MarketName*" +468,"SUBSTN.ST_LENRD.LOAD.SLDCBLK1 ","GEN","*MMS MarketName*" +473,"SUBSTN.SAW_HV.GEN.HVWWPV1 ","GEN","*MMS MarketName*" +474,"SUBSTN.MLSW.GEN.MRTLSWF1 ","GEN","*MMS MarketName*" +475,"SUBSTN.BRYBNKWF.GEN.BRYB2WF2 ","GEN","*MMS MarketName*" +476,"SUBSTN.H116_KWF.GEN.KABWF1 ","GEN","*MMS MarketName*" +477,"SUBSTN.EDVL_SF.GEN.EDVLSF1 ","GEN","*MMS MarketName*" +478,"SUBSTN.MOUR_SF.GEN.MOURSF1 ","GEN","*MMS MarketName*" +481,"SUBSTN.WWYAL_SF.GEN.WSTWYSF1 ","GEN","*MMS MarketName*" +482,"SUBSTN.NEWEN_SF.GEN.NEWENSF1 ","GEN","*MMS MarketName*" +483,"SUBSTN.NEWEN_SF.GEN.NEWENSF2 ","GEN","*MMS MarketName*" +488,"SUBSTN.BLVR_PS.GEN.BOLIVPS1 ","GEN","*MMS MarketName*" +491,"SUBSTN.WAND_SF.GEN.WANDSF1 ","GEN","*MMS MarketName*" +492,"SUBSTN.AVL_SF.GEN.AVLSF1 ","GEN","*MMS MarketName*" +501,"SUBSTN.COOR_2.GEN.TB2SF1 ","GEN","*MMS MarketName*" +502,"SUBSTN.WYA_SF.GEN.WYASF1 ","GEN","*MMS MarketName*" +503,"SUBSTN.T287_DWF.GEN.DULAWF1 ","GEN","*MMS MarketName*" +508,"SUBSTN.RYE_P_WF.GEN.RYEPARK1 ","GEN","*MMS MarketName*" +509,"SUBSTN.T48_TLLY.GEN.TLLY_S_MILL ","GEN","*MMS MarketName*" +510,"SUBSTN.GNSF.GEN.GLENSF1 ","GEN","*MMS MarketName*" +511,"SUBSTN.FLYCR_WF.GEN.FLYCRKWF ","GEN","*MMS MarketName*" +512,"SUBSTN.GYDR_WF.GEN.GSWF1A ","GEN","*MMS MarketName*" +515,"SUBSTN.MANN_R.GEN.MANNSF2 ","GEN","*MMS MarketName*" +516,"SUBSTN.GYDR_WF.GEN.GSWF1B1 ","GEN","*MMS MarketName*" +517,"SUBSTN.TALAWARA.UNIT.2 ","GEN","*MMS MarketName*" +518,"SUBSTN.KURRI.GEN.HEZ1 ","GEN","*MMS MarketName*" +519,"SUBSTN.WEL_N_SF.GEN.WELNTH1 ","GEN","*MMS MarketName*" +522,"SUBSTN.NSW1.NSGEN.SHOAL1 ","GEN","*MMS MarketName*" +523,"SUBSTN.CROOKWF.GEN.CROOKWF3 ","GEN","*MMS MarketName*" +524,"SUBSTN.KING_SF.GEN.KINGASF1 ","GEN","*MMS MarketName*" +525,"SUBSTN.MACNT_WF.GEN.MCINTYR1 ","GEN","*MMS MarketName*" +526,"SUBSTN.H102_CKC.GEN.CLRKCWF1 ","GEN","*MMS MarketName*" +527,"SUBSTN.H100_CKN.GEN.CLRKCWF2 ","GEN","*MMS MarketName*" +528,"SUBSTN.WALA_SF.GEN.WLWLSF1 ","GEN","*MMS MarketName*" +529,"SUBSTN.WALA_SF.GEN.WLWLSF2 ","GEN","*MMS MarketName*" +530,"SUBSTN.RCWF.GEN.RYANCWF1 ","GEN","*MMS MarketName*" +531,"SUBSTN.GGS.GEN.GIRGSF ","GEN","*MMS MarketName*" +532,"SUBSTN.WNS.GEN.WUNUSF1 ","GEN","*MMS MarketName*" +535,"SUBSTN.H121_WDB.GEN.WDBESSG ","GEN","*MMS MarketName*" +536,"SUBSTN.WALGBESS.GEN.WALGGEN ","GEN","*MMS MarketName*" +537,"SUBSTN.BRK_BESS.GEN.BRKBESSGEN ","GEN","*MMS MarketName*" +538,"SUBSTN.CBESS.GEN.CBESSGEN ","GEN","*MMS MarketName*" +539,"SUBSTN.DPESS.GEN.DPESSGEN ","GEN","*MMS MarketName*" +540,"SUBSTN.QBYNBESS.GEN.QBYNGEN ","GEN","*MMS MarketName*" +541,"SUBSTN.RESS1.GEN.RESS1GEN ","GEN","*MMS MarketName*" +542,"SUBSTN.RESS2.GEN.RESS2GEN ","GEN","*MMS MarketName*" +543,"SUBSTN.Z13_BCMB.GEN.BCMBESSG ","GEN","*MMS MarketName*" +544,"SUBSTN.T271_WSB.GEN.WANDBG ","GEN","*MMS MarketName*" +545,"SUBSTN.Z35_CCB.GEN.CHINBESSG ","GEN","*MMS MarketName*" +546,"SUBSTN.ADP.GEN.ADPGEN ","GEN","*MMS MarketName*" +547,"SUBSTN.BOLIVAR.GEN.BOLGEN ","GEN","*MMS MarketName*" +548,"SUBSTN.CHRI_B.GEN.CHBGEN ","GEN","*MMS MarketName*" +549,"SUBSTN.SAW_HV.GEN.HVYGEN ","GEN","*MMS MarketName*" +550,"SUBSTN.COOR_2.GEN.TB2BGEN ","GEN","*MMS MarketName*" +551,"SUBSTN.DALR_N.GEN.DPRGEN ","GEN","*MMS MarketName*" +552,"SUBSTN.HRN_WF.GEN.HPRGEN ","GEN","*MMS MarketName*" +553,"SUBSTN.LBBESS.GEN.LPRGEN ","GEN","*MMS MarketName*" +554,"SUBSTN.TI_BESS.GEN.TIBGEN ","GEN","*MMS MarketName*" +555,"SUBSTN.HWBS.GEN.HBESSGEN ","GEN","*MMS MarketName*" +556,"SUBSTN.VBB.GEN.VBBGEN ","GEN","*MMS MarketName*" +557,"SUBSTN.BESS.GEN.BALBGEN ","GEN","*MMS MarketName*" +558,"SUBSTN.BGWF.GEN.BGWFBESS ","GEN","*MMS MarketName*" +559,"SUBSTN.GESS.GEN.GANNBESS ","GEN","*MMS MarketName*" +560,"SUBSTN.PHIBESS.GEN.PICESBGEN ","GEN","*MMS MarketName*" +561,"SUBSTN.HDWF.GEN.HD1WF1 ","GEN","*MMS MarketName*" +562,"SUBSTN.HASTINGS.GEN.HASTING1 ","GEN","*MMS MarketName*" +563,"SUBSTN.HASTINGS.GEN.HASTING2 ","GEN","*MMS MarketName*" +564,"SUBSTN.HASTINGS.GEN.HASTING3 ","GEN","*MMS MarketName*" +565,"SUBSTN.WOLLAR_W.GEN.WOLARSF1 ","GEN","*MMS MarketName*" +566,"SUBSTN.STUBO_SF.GEN.STUBSF1 ","GEN","*MMS MarketName*" +567,"SUBSTN.STUBO_SF.GEN.STUBSF2 ","GEN","*MMS MarketName*" +568,"SUBSTN.GPEC.GEN.GPWFEST1 ","GEN","*MMS MarketName*" +569,"SUBSTN.GPEC.GEN.GPWFEST2 ","GEN","*MMS MarketName*" +570,"SUBSTN.GPEC.GEN.GPWFEST3 ","GEN","*MMS MarketName*" +571,"SUBSTN.RBES.GEN.RANGEB1 ","GEN","*MMS MarketName*" +572,"SUBSTN.BLYBSS.GEN.BLYTHB1 ","GEN","*MMS MarketName*" +573,"SUBSTN.WAR_BESS.GEN.WTAHB1 ","GEN","*MMS MarketName*" +574,"SUBSTN.Z36_GBB.GEN.GREENB1 ","GEN","*MMS MarketName*" +575,"SUBSTN.ERA_BESS.GEN.ERB01 ","GEN","*MMS MarketName*" +576,"SUBSTN.MKSF.GEN.MOKOSF1 ","GEN","*MMS MarketName*" +577,"SUBSTN.KSP.GEN.KERNGSP1 ","GEN","*MMS MarketName*" +578,"SUBSTN.LVES.GEN.LVES1 ","GEN","*MMS MarketName*" +579,"SUBSTN.KRES.GEN.KESSB1 ","GEN","*MMS MarketName*" +580,"SUBSTN.H138_TRB.GEN.TARBESS1 ","GEN","*MMS MarketName*" +581,"SUBSTN.H98_WBWF.GEN.WAMBOWF1 ","GEN","*MMS MarketName*" +582,"SUBSTN.CAPRAL.GEN.HUNTER1 ","GEN","*MMS MarketName*" +583,"SUBSTN.CAPRAL.GEN.HUNTER2 ","GEN","*MMS MarketName*" +584,"SUBSTN.H113_ALD.GEN.ALDGASF1 ","GEN","*MMS MarketName*" +585,"SUBSTN.H157_ULB.GEN.ULPBESS1 ","GEN","*MMS MarketName*" +586,"SUBSTN.CULC_SF.GEN.CUSF1 ","GEN","*MMS MarketName*" +587,"SUBSTN.WNSF.GEN.WNSF1 ","GEN","*MMS MarketName*" +588,"SUBSTN.MREH.GEN.MREHA1 ","GEN","*MMS MarketName*" +589,"SUBSTN.MREH.GEN.MREHA2 ","GEN","*MMS MarketName*" +590,"SUBSTN.MREH.GEN.MREHA3 ","GEN","*MMS MarketName*" +591,"SUBSTN.MNM_BESS.GEN.MANNUMB1 ","GEN","*MMS MarketName*" +592,"SUBSTN.TP_BESS.GEN.TEMPB1 ","GEN","*MMS MarketName*" +30001,"AGC Mode NEM SOUTH ","AGC"," * No MarketName * " +30002,"AGC Mode NEM NORTH ","AGC"," * No MarketName * " +31001,"ACE NEM SOUTH ","AGC"," * No MarketName * " +31002,"ACEFIL NEM SOUTH ","AGC"," * No MarketName * " +31003,"ACEINT NEM SOUTH ","AGC"," * No MarketName * " +31004,"TIME_ERR NEM SOUTH ","AGC"," * No MarketName * " +31005,"ACE NEM NORTH ","AGC"," * No MarketName * " +31006,"ACEFIL NEM NORTH ","AGC"," * No MarketName * " +31007,"ACEINT NEM NORTH ","AGC"," * No MarketName * " +31008,"TIME_ERR NEM NORTH ","AGC"," * No MarketName * " +32001,"FREQ NEM SOUTH ","SYSTEM"," * No MarketName * " +32002,"FREQ_NOM NEM SOUTH ","SYSTEM"," * No MarketName * " +32003,"FREQ_DEV NEM SOUTH ","SYSTEM"," * No MarketName * " +32004,"FREQ_OFFSET NEM SOUTH ","SYSTEM"," * No MarketName * " +32005,"FREQ NEM NORTH ","SYSTEM"," * No MarketName * " +32006,"FREQ_NOM NEM NORTH ","SYSTEM"," * No MarketName * " +32007,"FREQ_DEV NEM NORTH ","SYSTEM"," * No MarketName * " +32008,"FREQ_OFFSET NEM NORTH ","SYSTEM"," * No MarketName * " +40001,"AGC Mode TASNEM SOUTH ","AGC"," * No MarketName * " +40002,"AGC Mode TASNEM NORTH ","AGC"," * No MarketName * " +41001,"ACE TASNEM SOUTH ","AGC"," * No MarketName * " +41002,"ACEFIL TASNEM SOUTH ","AGC"," * No MarketName * " +41003,"ACEINT TASNEM SOUTH ","AGC"," * No MarketName * " +41004,"TIME_ERR TASNEM SOUTH ","AGC"," * No MarketName * " +41005,"ACE TASNEM NORTH ","AGC"," * No MarketName * " +41006,"ACEFIL TASNEM NORTH ","AGC"," * No MarketName * " +41007,"ACEINT TASNEM NORTH ","AGC"," * No MarketName * " +41008,"TIME_ERR TASNEM NORTH ","AGC"," * No MarketName * " +42001,"FREQ TASNEM SOUTH ","SYSTEM"," * No MarketName * " +42002,"FREQ_NOM TASNEM SOUTH ","SYSTEM"," * No MarketName * " +42003,"FREQ_DEV TASNEM SOUTH ","SYSTEM"," * No MarketName * " +42004,"FREQ_OFFSET TASNEM SOUTH ","SYSTEM"," * No MarketName * " +42005,"FREQ TASNEM NORTH ","SYSTEM"," * No MarketName * " +42006,"FREQ_NOM TASNEM NORTH ","SYSTEM"," * No MarketName * " +42007,"FREQ_DEV TASNEM NORTH ","SYSTEM"," * No MarketName * " +42008,"FREQ_OFFSET TASNEM NORTH ","SYSTEM"," * No MarketName * " +33001,"SPD SECONDS","SPD"," * No MarketName * " +20001,"SA-VIC HYTS-SESS","INTERCONNECTOR","*MarketName*" +20002,"VIC-SNOWY DDTS-MSS","INTERCONNECTOR","*MarketName*" +20003,"VIC-SNOWY RCTS-Buronga","INTERCONNECTOR","*MarketName*" +20004,"VIC-SNOWY WOTS-JIND","INTERCONNECTOR","*MarketName*" +20005,"SNOWY-NSW WOTS-JIND","INTERCONNECTOR","*MarketName*" +20006,"SNOWY-NSW RCTS-Buronga","INTERCONNECTOR","*MarketName*" +20007,"SNOWY-NSW T1&2_CBA","INTERCONNECTOR","*MarketName*" +20008,"SNOWY-NSW T1&2_YASS","INTERCONNECTOR","*MarketName*" +20009,"SNOWY-NSW T3_YASS","INTERCONNECTOR","*MarketName*" +20010,"SNOWY-NSW T3_CBA","INTERCONNECTOR","*MarketName*" +20011,"SNOWY-NSW T3_Wagga","INTERCONNECTOR","*MarketName*" +20012,"SNOWY-NSW GGA_Munyang","INTERCONNECTOR","*MarketName*" +20013,"NSW-QLD Directlink","INTERCONNECTOR","*MarketName*" +20014,"NSW-QLD QNI","INTERCONNECTOR","*MarketName*" +20015,"SPD VIC-SA ","INTERCONNECTOR","*MarketName*" +20016,"SPD VIC-SNOWY ","INTERCONNECTOR","*MarketName*" +20017,"SPD NSW-SNOWY ","INTERCONNECTOR","*MarketName*" +20018,"SPD NSW-Directlink ","INTERCONNECTOR","*MarketName*" +20019,"SPD NSW-QNI ","INTERCONNECTOR","*MarketName*" +20020,"SPD SA-Murraylink ","INTERCONNECTOR","*MarketName*" +20021,"SPD VIC-NSW ","INTERCONNECTOR","*MarketName*" +20022,"SPD Basslink ","INTERCONNECTOR","*MarketName*" +10001,"APD SUMM POTLINES","LOAD","*MarketName*" +10006,"TOMAGO SUMM POTLINES","LOAD","*MarketName*" +10013,"H8_BOYNE POTLINES SUMM","LOAD","*MarketName*" diff --git a/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/index.html b/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/index.html new file mode 100644 index 0000000..3827663 --- /dev/null +++ b/tests/fixtures/data/Reports/Current/Causer_Pays_Elements/index.html @@ -0,0 +1,3 @@ + +Elements_FCAS_202504151310.csv + \ No newline at end of file diff --git a/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603140000_20260315040503.zip b/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603140000_20260315040503.zip new file mode 100644 index 0000000..4ef62be Binary files /dev/null and b/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603140000_20260315040503.zip differ diff --git a/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603150000_20260316040504.zip b/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603150000_20260316040504.zip new file mode 100644 index 0000000..4522146 Binary files /dev/null and b/tests/fixtures/data/Reports/Current/Daily_Reports/PUBLIC_DAILY_202603150000_20260316040504.zip differ diff --git a/tests/fixtures/data/Reports/Current/Daily_Reports/index.html b/tests/fixtures/data/Reports/Current/Daily_Reports/index.html new file mode 100644 index 0000000..73c53b3 --- /dev/null +++ b/tests/fixtures/data/Reports/Current/Daily_Reports/index.html @@ -0,0 +1,4 @@ + +PUBLIC_DAILY_202603140000_20260315040503.zip +PUBLIC_DAILY_202603150000_20260316040504.zip + \ No newline at end of file diff --git a/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260314_0000000507927925.zip b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260314_0000000507927925.zip new file mode 100644 index 0000000..c302d57 Binary files /dev/null and b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260314_0000000507927925.zip differ diff --git a/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260315_0000000508079749.zip b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260315_0000000508079749.zip new file mode 100644 index 0000000..dec3fef Binary files /dev/null and b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/PUBLIC_NEXT_DAY_DISPATCH_20260315_0000000508079749.zip differ diff --git a/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/index.html b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/index.html new file mode 100644 index 0000000..cd113f4 --- /dev/null +++ b/tests/fixtures/data/Reports/Current/NEXT_DAY_DISPATCH/index.html @@ -0,0 +1,4 @@ + +PUBLIC_NEXT_DAY_DISPATCH_20260314_0000000507927925.zip +PUBLIC_NEXT_DAY_DISPATCH_20260315_0000000508079749.zip + \ No newline at end of file diff --git a/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260314_0000000507927930.zip b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260314_0000000507927930.zip new file mode 100644 index 0000000..f8c6b80 Binary files /dev/null and b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260314_0000000507927930.zip differ diff --git a/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260315_0000000508079754.zip b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260315_0000000508079754.zip new file mode 100644 index 0000000..c902895 Binary files /dev/null and b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260315_0000000508079754.zip differ diff --git a/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/index.html b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/index.html new file mode 100644 index 0000000..9c56b6d --- /dev/null +++ b/tests/fixtures/data/Reports/Current/Next_Day_Intermittent_Gen_Scada/index.html @@ -0,0 +1,4 @@ + +PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260314_0000000507927930.zip +PUBLIC_NEXT_DAY_INTERMITTENT_GEN_SCADA_20260315_0000000508079754.zip + \ No newline at end of file diff --git a/tests/fixtures/spec.py b/tests/fixtures/spec.py new file mode 100644 index 0000000..ad86269 --- /dev/null +++ b/tests/fixtures/spec.py @@ -0,0 +1,160 @@ +"""Declarative spec of what the NEMOSIS test fixtures cover. + +The fixture build script reads this module and is otherwise self-contained — +if you want to add an era or a table to the test matrix, edit here and re-run +the build script. + +Adding a new era: + 1. Add a dated entry to ERAS. + 2. Append the era key to each table's `eras` list in DYNAMIC_TABLES. + 3. Run `python tests/fixtures/build.py` to download, filter, and write. + 4. Commit the new files under tests/fixtures/data/. +""" +from datetime import date + + +# Each era is a single calendar month pinned by its first day. +# Dates are chosen to straddle known AEMO schema / format transitions so +# the tests exercise every historical variant NEMOSIS needs to handle. +ERAS = { + "2018-05": date(2018, 5, 1), # pre-5-min-trading baseline + "2020-01": date(2020, 1, 1), # year boundary, pre-5-min, monthly bidding format + "2021-02": date(2021, 2, 1), # last month the MMS archive still publishes bidding + "2021-05": date(2021, 5, 1), # first month of daily BIDMOVE_COMPLETE layout + "2021-10": date(2021, 10, 1), # 5MS cutover for TRADINGPRICE/TRADINGINTERCONNECT (extra_eras only) + "2022-01": date(2022, 1, 1), # year boundary, 5-min dispatch, 30-min trading, bidding gap + "2022-06": date(2022, 6, 1), # arbitrary post-5MS month for trading tables (5MS itself cut over 2021-10) + "2024-08": date(2024, 8, 1), # PUBLIC_DVD_ → PUBLIC_ARCHIVE# cutover; `into` straddles the format change + "2024-09": date(2024, 9, 1), # first safely-past-cutover month; used by bidding tables (see note) + "2025-01": date(2025, 1, 1), # year boundary, PUBLIC_ARCHIVE# format, post-bidding-gap + "recent": date(2026, 3, 15), # inside AEMO's current-data scrape window (see note) +} +# Year-boundary eras (2020-01, 2022-01, 2025-01) exist for the boundary test +# matrix in tests/end_to_end_table_tests/_boundaries.py — they exercise +# NEMOSIS's Dec→Jan stitch and the date generator's `month == 1` buffer-wrap +# branch. Each is added only to the dynamic time-series tables that have +# AEMO data in that month (no bidding tables for 2022-01, no MNSP_PEROFFER +# past 2021-02 due to issue #68, etc.). +# Note on "recent": AEMO's current-data pages (bidmove_complete, daily reports, +# etc.) only retain ~a few months. Any date we pick here must be recent enough +# to still be served at fixture-build time. When the commit ages out, update +# this date and rerun `build.py --rebuild` for the scrape-pattern tables. + + +# Entities kept when filtering rows. Diverse enough to exercise table +# behaviour, small enough that fixture files stay under a few hundred KB each. +DUIDS = ["AGLHAL", "HDWF2"] # OCGT + wind farm +REGIONS = ["SA1", "NSW1"] +INTERCONNECTORS = ["VIC1-NSW1"] +CONSTRAINTS = ["DATASNAP_DFS_Q_CLST"] # CONSTRAINTID, used by DISPATCHCONSTRAINT +GENCON_IDS = ["#NSW1-QLD1_RAMP_I_F"] # GENCONID, used by GENCONDATA / SPD* tables +SPDCPC_GENCON_IDS = ["S>>MKRB_NIL_WEMWP4"] # smaller-volume GENCONID for SPDCPC, EFFECTIVEDATE pre-May-2021 +PARTICIPANTS = ["AGLE", "INFIGENH", "ERMPOWER"] # AGL SA + Infigen (Hornsdale) + ERM Power (LASTCHANGED moves between 2018/2021) +MNSP_LINKS = ["BLNKTAS", "BLNKVIC"] # Basslink, both directions + + +# Dynamic tables. Each entry says which eras to fixture and how to filter +# rows down. The build script figures out the URL / download path from the +# table name and era date — the spec itself stays path-agnostic. +# +# Starting with a representative cross-section covering the main patterns: +# - DISPATCHPRICE: MMS monthly archive, region filter +# - DISPATCHLOAD: MMS monthly archive, DUID filter +# - BIDPEROFFER_D: MMS before 2021-04, BIDMOVE_COMPLETE scrape after +# - DAILY_REGION_SUMMARY: scrape-pattern, current-data page only +# More tables get added as each one lands end-to-end. +DYNAMIC_TABLES = { + # Five-minute dispatch tables — the workhorse set. + "DISPATCHPRICE": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"REGIONID": REGIONS}}, + "DISPATCHLOAD": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"DUID": DUIDS}}, + "DISPATCH_UNIT_SCADA": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"DUID": DUIDS}}, + "DISPATCHREGIONSUM": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"REGIONID": REGIONS}}, + "DISPATCHINTERCONNECTORRES": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}}, + "DISPATCHCONSTRAINT": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"CONSTRAINTID": CONSTRAINTS}}, + + # Trading tables. TRADINGPRICE/TRADINGINTERCONNECT switched from 30-min to + # 5-min at 2021-10-01 (the 5MS reform cutover); TRADINGLOAD/TRADINGREGIONSUM + # were discontinued at that point and only have pre-2022 eras. + # + # The `extra_eras` entry for 2021-10 is a build-only era used by the + # dedicated stride-transition test in test_trading_price.py. It doesn't + # go into the boundary matrix: the matrix's `into` flavour would query a + # window straddling the cutover with rows at two different strides, which + # the boundary helper can't express as a single regular grid. + "TRADINGPRICE": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2022-06", "2024-08", "2025-01"], "extra_eras": ["2021-10"], "filter": {"REGIONID": REGIONS}}, + "TRADINGINTERCONNECT": {"eras": ["2018-05", "2020-01", "2021-05", "2022-01", "2022-06", "2024-08", "2025-01"], "extra_eras": ["2021-10"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}}, + # TRADINGLOAD/TRADINGREGIONSUM were discontinued before 2022 — 2022-01 returns 404. + "TRADINGLOAD": {"eras": ["2018-05", "2020-01", "2021-05"], "filter": {"DUID": DUIDS}}, + "TRADINGREGIONSUM": {"eras": ["2018-05", "2020-01", "2021-05"], "filter": {"REGIONID": REGIONS}}, + + # Bidding — MMS archive all the way through. AEMO stopped publishing these + # monthly files between March 2021 and July 2024 (see README), so 2022-01 + # is unavailable and post-2021-02 coverage jumps to 2024-09. + # + # Non-bidding tables use 2024-08 to straddle the PUBLIC_DVD_ → PUBLIC_ARCHIVE# + # filename cutover. Bidding tables stay on 2024-09: 2024-07's bid archive + # sits inside the publishing gap and a 2024-08-era prev-month buffer would + # 404 at test time. + "BIDPEROFFER_D": {"eras": ["2018-05", "2020-01", "2021-02", "2024-09", "2025-01"], "filter": {"DUID": DUIDS}}, + "BIDDAYOFFER_D": {"eras": ["2018-05", "2020-01", "2021-02", "2024-09", "2025-01"], "filter": {"DUID": DUIDS}}, + + # MNSP bidding — same shape as BIDPEROFFER_D / BIDDAYOFFER_D but keyed on + # LINKID (MNSP interconnectors bid as directional links). + # + # MNSP_PEROFFER has coverage gaps NEMOSIS can't currently read past: + # - From ~April 2021 AEMO began writing MNSP_BIDOFFERPERIOD data into + # the PUBLIC_DVD_MNSP_PEROFFER_* archives with different column names + # (TRADINGDATE / OFFERDATETIME instead of SETTLEMENTDATE / OFFERDATE). + # - At the Aug-2024 archive format switch AEMO also renamed the file + # stem to MNSP_BIDOFFERPERIOD outright. + # NEMOSIS's defaults.names knows neither of these transitions, so we cap + # MNSP_PEROFFER coverage at 2021-02 (the last clean MMS month) — same + # story as BIDPEROFFER_D but without the 2024-09 recovery era. + # + # MNSP_DAYOFFER kept its column names across both boundaries, so the + # standard bidding era set applies — same bidding-gap shape as BIDPEROFFER_D + # / BIDDAYOFFER_D above (no 2022-01, recovery at 2024-09). + "MNSP_PEROFFER": {"eras": ["2018-05", "2020-01", "2021-02"], "filter": {"LINKID": MNSP_LINKS}}, + "MNSP_DAYOFFER": {"eras": ["2018-05", "2020-01", "2021-05", "2024-09", "2025-01"], "filter": {"LINKID": MNSP_LINKS}}, + + # Scrape-only tables — all live on AEMO's rolling current-data pages. + "DAILY_REGION_SUMMARY": {"eras": ["recent"], "filter": {"REGIONID": REGIONS}}, + "NEXT_DAY_DISPATCHLOAD": {"eras": ["recent"], "filter": {"DUID": DUIDS}}, + "INTERMITTENT_GEN_SCADA": {"eras": ["recent"], "filter": {"DUID": DUIDS}}, + + # Rooftop PV — introduced mid-2019. + "ROOFTOP_PV_ACTUAL": {"eras": ["2020-01", "2021-05", "2022-01", "2024-08", "2025-01"], "filter": {"REGIONID": REGIONS}}, + + # Effective-date config tables. These publish sparsely — records are + # configuration changes, not time-series observations — and NEMOSIS's + # "all" search_type would normally iterate every month from July 2009 + # onward. Tests narrow the scan by monkeypatching + # `defaults.nem_data_model_start_time` so only a single era month is + # probed. `keep_full_month: True` tells build.py to skip the usual + # first-3 / last-2 day time trim, since a sparse table can easily end + # up with no rows in that window. All eras pinned to 2021-05 — these + # tables don't have format transitions that warrant multi-era coverage. + "INTERCONNECTOR": {"eras": ["2021-05"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}, "keep_full_month": True}, + "MNSP_INTERCONNECTOR": {"eras": ["2021-05"], "filter": {"LINKID": MNSP_LINKS}, "keep_full_month": True}, + "INTERCONNECTORCONSTRAINT": {"eras": ["2021-05"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}, "keep_full_month": True}, + "LOSSMODEL": {"eras": ["2021-05"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}, "keep_full_month": True}, + "LOSSFACTORMODEL": {"eras": ["2021-05"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}, "keep_full_month": True}, + "DUDETAIL": {"eras": ["2021-05"], "filter": {"DUID": DUIDS}, "keep_full_month": True}, + "DUDETAILSUMMARY": {"eras": ["2021-05"], "filter": {"DUID": DUIDS}, "keep_full_month": True}, + "PARTICIPANT": {"eras": ["2018-05", "2021-05"], "filter": {"PARTICIPANTID": PARTICIPANTS}, "keep_full_month": True}, # 2018 pair gives a wide LASTCHANGED span + "GENCONDATA": {"eras": ["2021-05"], "filter": {"GENCONID": GENCON_IDS}, "keep_full_month": True}, + "SPDREGIONCONSTRAINT": {"eras": ["2021-05"], "filter": {"REGIONID": REGIONS}, "keep_full_month": True}, + "SPDINTERCONNECTORCONSTRAINT": {"eras": ["2021-05"], "filter": {"INTERCONNECTORID": INTERCONNECTORS}, "keep_full_month": True}, + "SPDCONNECTIONPOINTCONSTRAINT": {"eras": ["2021-05"], "filter": {"GENCONID": SPDCPC_GENCON_IDS}, "keep_full_month": True}, + # MARKET_PRICE_THRESHOLDS has no group column (only 12 rows total) so no row filter is applied. + "MARKET_PRICE_THRESHOLDS": {"eras": ["2021-05"], "filter": {}, "keep_full_month": True}, +} + + +# Static tables have no time dimension; each is downloaded once as current state. +# The build script snapshots them to the fixture tree. +STATIC_TABLES = [ + "ELEMENTS_FCAS_4_SECOND", + "VARIABLES_FCAS_4_SECOND", + "Generators and Scheduled Loads", +] diff --git a/tests/test_data_fetch_methods.py b/tests/test_data_fetch_methods.py deleted file mode 100644 index b5c6278..0000000 --- a/tests/test_data_fetch_methods.py +++ /dev/null @@ -1,1084 +0,0 @@ -import unittest -import os -from datetime import datetime, timedelta, date -from dateutil.relativedelta import relativedelta -import calendar -import pandas as pd -from nemosis import custom_tables, defaults, filters, data_fetch_methods -from pandas.testing import assert_frame_equal -from parameterized import parameterized - -recent_test_month = datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - relativedelta(months=3) - -previous_month = datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - relativedelta(months=1) - -# Discontinued dates are not precise but should work with current testing dates. -discontinued_after = { - "TRADINGLOAD": "2022/01/01 00:00:00", - "TRADINGREGIONSUM": "2022/01/01 00:00:00", -} - -test_iterations = [ - [recent_test_month], - [datetime(year=2018, month=5, day=1)] -] - -def get_previous_month(month): - return month - relativedelta(months=1) - -def get_next_month(month): - return month + relativedelta(months=1) - -def last_day_of_month(year, month): - day_number = date(year, month, calendar.monthrange(year, month)[1]).day - return str(day_number).zfill(2) - -class TestDynamicDataCompilerWithSettlementDateFiltering(unittest.TestCase): - def setUp(self): - self.table_names = [ - "BIDDAYOFFER_D", - "BIDPEROFFER_D", - "DISPATCHLOAD", - "DISPATCHCONSTRAINT", - "DISPATCH_UNIT_SCADA", - "DISPATCHPRICE", - "DISPATCHINTERCONNECTORRES", - "DISPATCHREGIONSUM", - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - "ROOFTOP_PV_ACTUAL", - ] - - self.table_types = { - "DISPATCHLOAD": "DUID", - "DISPATCHCONSTRAINT": "CONSTRAINTID", - "DISPATCH_UNIT_SCADA": "DUIDONLY", - "DISPATCHPRICE": "REGIONID", - "DISPATCHINTERCONNECTORRES": "INTERCONNECTORID", - "DISPATCHREGIONSUM": "REGIONID", - "BIDPEROFFER_D": "DUID-BIDTYPE", - "BIDDAYOFFER_D": "DUID-BIDTYPE", - "TRADINGLOAD": "DUIDONLY", - "TRADINGPRICE": "REGIONIDONLY", - "TRADINGREGIONSUM": "REGIONIDONLY", - "TRADINGINTERCONNECT": "INTERCONNECTORIDONLY", - "ROOFTOP_PV_ACTUAL": "REGIONID-PVVALUETYPE", - } - - self.table_filters = { - "DISPATCHLOAD": ["DUID", "INTERVENTION"], - "DISPATCHCONSTRAINT": ["CONSTRAINTID", "INTERVENTION"], - "DISPATCH_UNIT_SCADA": ["DUID"], - "DISPATCHPRICE": ["REGIONID", "INTERVENTION"], - "DISPATCHINTERCONNECTORRES": ["INTERCONNECTORID", "INTERVENTION"], - "DISPATCHREGIONSUM": ["REGIONID", "INTERVENTION"], - "BIDPEROFFER_D": ["DUID", "BIDTYPE"], - "BIDDAYOFFER_D": ["DUID", "BIDTYPE"], - "TRADINGLOAD": ["DUID"], - "TRADINGPRICE": ["REGIONID"], - "TRADINGREGIONSUM": ["REGIONID"], - "TRADINGINTERCONNECT": ["INTERCONNECTORID"], - "ROOFTOP_PV_ACTUAL": ["REGIONID", "TYPE"], - } - - self.filter_values = { - "DUID": (["AGLHAL"], [0]), - "DUIDONLY": (["AGLHAL"],), - "REGIONID": (["SA1"], [0]), - "REGIONIDONLY": (["SA1"],), - "INTERCONNECTORID": (["VIC1-NSW1"], [0]), - "INTERCONNECTORIDONLY": (["VIC1-NSW1"],), - "CONSTRAINTID": (["DATASNAP_DFS_Q_CLST"], [0]), - "DUID-BIDTYPE": (["AGLHAL"], ["ENERGY"]), - "REGIONID-TYPE": (['NSW1'], ['DAILY']), - "REGIONID-PVVALUETYPE": (['NSW1'], ['SATELLITE', 'DAILY']), - } - self.half_hourly_tables = [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - "ROOFTOP_PV_ACTUAL" - ] - self.change_to_five_min_table = [ - "TRADINGPRICE", - "TRADINGINTERCONNECT", - ] - self.change_to_five_min_year = 2022 - - def _pv_extra_clean_up(self, data): - pv_types = data['TYPE'].unique() - if 'DAILY' in pv_types and 'SATELLITE' in pv_types: - data = data[data['TYPE'] == 'SATELLITE'].copy() - return data - - @parameterized.expand(test_iterations) - def test_dispatch_tables_start_of_month(self, test_month): - start_time = f"{test_month.year}/{test_month.month}/01 00:00:00" - end_time = f"{test_month.year}/{test_month.month}/01 05:15:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print(f"Testing {table} returning values at start of month.") - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 10 - expected_first_time = f"{test_month.year}/{test_month.month}/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = f"{test_month.year}/{test_month.month}/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 2 - expected_last_time = f"{test_month.year}/{test_month.month}/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - previous_month = get_previous_month(test_month) - day = last_day_of_month(previous_month.year, previous_month.month) - expected_first_time = f"{previous_month.year}/{previous_month.month}/{day} 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - print(start_time) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - @parameterized.expand(test_iterations) - def test_dispatch_tables_end_of_month(self, test_month): - day = last_day_of_month(test_month.year, test_month.month) - start_time = f"{test_month.year}/{test_month.month}/{day} 21:00:00" - following_month = get_next_month(test_month) - end_time = f"{following_month.year}/{following_month.month}/01 00:00:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print("Testing {} returing values at end of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 36 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 6 - expected_first_time = f"{test_month.year}/{test_month.month}/{day} 21:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 1 - expected_last_time = expected_first_time.replace(hour=0, minute=0) - expected_first_time = expected_first_time.replace(hour=0, minute=0) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.sort_values(dat_col) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - @parameterized.expand(test_iterations) - def test_dispatch_tables_straddle_2_months(self, test_month): - day = last_day_of_month(test_month.year, test_month.month) - start_time = f"{test_month.year}/{test_month.month}/{day} 21:00:00" - following_month = get_next_month(test_month) - end_time = f"{following_month.year}/{following_month.month}/01 21:00:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print(f"Testing {table} returing values from adjacent months.") - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 288 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 48 - expected_first_time = f"{test_month.year}/{test_month.month}/{day} 21:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 2 - expected_last_time = expected_last_time.replace(hour=0, minute=0) - expected_first_time = expected_first_time.replace(hour=0, minute=0) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.sort_values(dat_col) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - @parameterized.expand(test_iterations) - def test_dispatch_tables_start_of_year(self, test_month): - start_time = f"{test_month.year}/01/01 00:00:00" - following_month = get_next_month(test_month) - end_time = f"{following_month.year}/01/01 01:00:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print("Testing {} returing values at start of year.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 12 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 2 - expected_first_time = f"{test_month.year}/01/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 1 - expected_last_time = expected_last_time.replace( - hour=0, minute=0 - ) - timedelta(days=1) - expected_first_time = expected_first_time.replace( - hour=0, minute=0 - ) - timedelta(days=1) - - if table in ["BIDDAYOFFER_D", "BIDPEROFFER_D"]: - """Skip because this test is currently in bidding data gap.""" - continue - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.sort_values(dat_col) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - @parameterized.expand(test_iterations) - def test_dispatch_tables_end_of_year(self, test_month): - start_time = f"{test_month.year - 1}/12/31 23:00:00" - following_month = get_next_month(test_month) - end_time = f"{following_month.year}/01/01 00:00:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print("Testing {} returing values at end of year.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 12 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 2 - expected_first_time = f"{test_month.year - 1}/12/31 23:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 1 - expected_first_time = expected_first_time.replace(hour=0, minute=0) - expected_last_time = expected_first_time - - if table in ["BIDDAYOFFER_D", "BIDPEROFFER_D"]: - """Skip because this test is currently in bidding data gap.""" - continue - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.sort_values(dat_col) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - @parameterized.expand(test_iterations) - def test_dispatch_tables_straddle_years(self, test_month): - start_time = f"{test_month.year - 1}/12/31 23:00:00" - following_month = get_next_month(test_month) - end_time = f"{following_month.year}/01/01 01:00:00" - for table in self.table_names: - if table in discontinued_after and discontinued_after[table] < start_time: - continue - print(f"Testing {table} returning values from adjacent years.") - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - filter_cols = self.table_filters[table] - cols = [dat_col, *filter_cols] - expected_length = 24 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if (table in self.half_hourly_tables and - not (table in self.change_to_five_min_table and test_month.year > self.change_to_five_min_year)): - expected_length = 4 - expected_first_time = f"{test_month.year - 1}/12/31 23:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - if "ONLY" not in table_type: - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - expected_length = 1 - expected_first_time = expected_first_time.replace(hour=0, minute=0) - expected_last_time = expected_first_time - if table in ["BIDDAYOFFER_D", "BIDPEROFFER_D"]: - """Skip because this test is currently in bidding data gap.""" - continue - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - ) - if table == 'ROOFTOP_PV_ACTUAL': - data = self._pv_extra_clean_up(data) - data = data.sort_values(dat_col) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - -class TestDynamicDataCompilerWithSettlementDateFilteringNextDayTables( - unittest.TestCase -): - - def setUp(self): - self.table_names = [ - "DAILY_REGION_SUMMARY", - "NEXT_DAY_DISPATCHLOAD", - "INTERMITTENT_GEN_SCADA" - ] - - self.table_filters = { - "DAILY_REGION_SUMMARY": ["REGIONID"], - "NEXT_DAY_DISPATCHLOAD": ["DUID"], - "INTERMITTENT_GEN_SCADA": ["DUID", "SCADA_TYPE"] - } - - # Filter for bids at the start of the 2021-06-01 file and the end of the 2021-05-31, to make sure that we aren't - # skipping any of the data file rows. - self.filter_values = { - "DAILY_REGION_SUMMARY": ( - ["NSW1"], - ), - "NEXT_DAY_DISPATCHLOAD": ( - ['AGLHAL'], - ), - "INTERMITTENT_GEN_SCADA": ( - ['ADPPV1'], - ['ELAV'] - ) - } - - def test_dispatch_tables_start_of_month(self): - test_month = previous_month - start_time = f"{test_month.year}/{test_month.month}/01 00:00:00" - end_time = f"{test_month.year}/{test_month.month}/01 05:15:00" - for table in self.table_names: - print(f"Testing {table} returning values at start of month one.") - dat_col = defaults.primary_date_columns[table] - expected_length = 63 * 1 - expected_number_of_columns = len(defaults.table_columns[table]) - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=True, - filter_cols=self.table_filters[table], - filter_values=self.filter_values[table] - ) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - def test_dispatch_tables_middle_of_month_and_day(self): - test_month = previous_month - start_time = f"{test_month.year}/{test_month.month}/05 12:00:00" - end_time = f"{test_month.year}/{test_month.month}/05 17:15:00" - for table in self.table_names: - print(f"Testing {table} returning values at start of month one.") - dat_col = defaults.primary_date_columns[table] - expected_length = 63 * 1 - expected_number_of_columns = len(defaults.table_columns[table]) - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=True, - filter_cols=self.table_filters[table], - filter_values=self.filter_values[table] - ) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - def test_dispatch_tables_start_market_day(self): - test_month = previous_month - start_time = f"{test_month.year}/{test_month.month}/05 04:00:00" - end_time = f"{test_month.year}/{test_month.month}/05 04:05:00" - for table in self.table_names: - print(f"Testing {table} returning values at start of month one.") - dat_col = defaults.primary_date_columns[table] - expected_length = 1 - expected_number_of_columns = len(defaults.table_columns[table]) - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=True, - filter_cols=self.table_filters[table], - filter_values=self.filter_values[table] - ) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - def test_dispatch_tables_end_market_day(self): - test_month = previous_month - start_time = f"{test_month.year}/{test_month.month}/05 03:55:00" - end_time = f"{test_month.year}/{test_month.month}/05 04:00:00" - for table in self.table_names: - print(f"Testing {table} returning values at start of month one.") - dat_col = defaults.primary_date_columns[table] - expected_length = 1 - expected_number_of_columns = len(defaults.table_columns[table]) - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=True, - filter_cols=self.table_filters[table], - filter_values=self.filter_values[table] - ) - data = data.reset_index(drop=True) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - -class TestDynamicDataCompilerWithEffectiveDateFiltering(unittest.TestCase): - def setUp(self): - self.table_names = [ - "GENCONDATA", - "SPDREGIONCONSTRAINT", - "SPDCONNECTIONPOINTCONSTRAINT", - "SPDINTERCONNECTORCONSTRAINT", - ] - - @parameterized.expand([ - [recent_test_month], - [datetime(year=2018, month=5, day=1)] - ]) - def test_filtering_for_one_interval_returns(self, test_month): - start_time = f"{test_month.year}/{test_month.month}/20 23:00:00" - end_time = f"{test_month.year}/{test_month.month}/20 23:05:00" - for table in self.table_names: - print("Testing {} returing values for 1 interval.".format(table)) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=False, - select_columns=defaults.table_primary_keys[table], - ) - group_cols = [ - col - for col in defaults.table_primary_keys[table] - if col != "EFFECTIVEDATE" - ] - contains_duplicates = data.duplicated(group_cols).any() - self.assertEqual(False, contains_duplicates) - not_empty = data.shape[0] > 0 - self.assertEqual(True, not_empty) - print("Passed") - - -class TestCacheCompiler(unittest.TestCase): - def setUp(self): - self.table_names = [ - "BIDDAYOFFER_D", - "BIDPEROFFER_D", - "DISPATCHLOAD", - "DISPATCHCONSTRAINT", - "DISPATCH_UNIT_SCADA", - "DISPATCHPRICE", - "DISPATCHINTERCONNECTORRES", - "DISPATCHREGIONSUM", - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ] - self.id_cols = { - "DISPATCHLOAD": "DUID", - "DISPATCHCONSTRAINT": "CONSTRAINTID", - "DISPATCH_UNIT_SCADA": "DUID", - "DISPATCHPRICE": "REGIONID", - "DISPATCHINTERCONNECTORRES": "INTERCONNECTORID", - "DISPATCHREGIONSUM": "REGIONID", - "BIDPEROFFER_D": "DUID", - "BIDDAYOFFER_D": "DUID", - "TRADINGLOAD": "DUID", - "TRADINGPRICE": "REGIONID", - "TRADINGREGIONSUM": "REGIONID", - "TRADINGINTERCONNECT": "INTERCONNECTORID", - } - - def test_caching_and_typing_works_feather(self): - start_time = "2018/02/20 23:00:00" - end_time = "2018/02/20 23:30:00" - for table in self.table_names: - dat_col = defaults.primary_date_columns[table] - id_col = self.id_cols[table] - print(f"Testing {table} returing values for 1 interval.") - data_fetch_methods.cache_compiler( - start_time, end_time, table, defaults.raw_data_cache, fformat="feather" - ) - data = data_fetch_methods.dynamic_data_compiler( - start_time, end_time, table, defaults.raw_data_cache, fformat="feather" - ) - dat_col_type = data[dat_col].dtype - id_col_type = data[id_col].dtype - not_empty = data.shape[0] > 0 - not_typed = all(data.dtypes == "object") - self.assertTrue(not_empty) - self.assertFalse(not_typed) - self.assertEqual(dat_col_type, " 0 - not_typed = all(data.dtypes == "object") - self.assertTrue(not_empty) - self.assertFalse(not_typed) - self.assertEqual(dat_col_type, " 0 - self.assertEqual(True, not_empty) - print("Passed") - - -class TestDynamicDataCompilerWithLastChangedFiltering(unittest.TestCase): - @parameterized.expand([ - [recent_test_month], - [datetime(year=2018, month=5, day=1)] - ]) - def test_that_a_narrow_time_range_returns_one_entry_per_participantid(self, test_month): - start_time = f"{test_month.year}/{test_month.month}/01 00:00:00" - end_time = f"{test_month.year}/{test_month.month}/01 00:05:00" - table = "PARTICIPANT" - cols = ["PARTICIPANTID", "NAME", "LASTCHANGED"] - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - ) - data = data.groupby(['PARTICIPANTID'], as_index=False).count() - assert (data['NAME'] == 1).all() - print("Passed") - - def test_that_a_wide_time_range_returns_more_than_one_entry_for_at_least_some_participantids(self): - table = "PARTICIPANT" - data = data_fetch_methods.dynamic_data_compiler( - "2013/01/01 00:00:00", - "2023/01/01 00:00:00", - table, - defaults.raw_data_cache, - ) - data = data.groupby(['PARTICIPANTID'], as_index=False).count() - assert (data['NAME'] > 1).any() - print("Passed") - - def test_filter_of_raw_data(self): - table = "PARTICIPANT" - start_time_str = defaults.nem_data_model_start_time - end_time_str = "2023/01/01 00:00:00" - start_search = defaults.nem_data_model_start_time - start_time = datetime.strptime(start_time_str, "%Y/%m/%d %H:%M:%S") - end_time = datetime.strptime(end_time_str, "%Y/%m/%d %H:%M:%S") - start_search = datetime.strptime(start_search, "%Y/%m/%d %H:%M:%S") - data = data_fetch_methods.dynamic_data_compiler( - start_time_str, - end_time_str, - table, - defaults.raw_data_cache, - ) - raw_data = data_fetch_methods._dynamic_data_fetch_loop( - start_search, - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=['PARTICIPANTID', 'NAME', 'PARTICIPANTCLASSID', 'LASTCHANGED'], - date_filter=filters.filter_on_last_changed, - fformat='feather', - keep_csv=True, - rebuild=False) - raw_data = pd.concat(raw_data) - raw_data = raw_data.groupby(['PARTICIPANTID', 'LASTCHANGED'], as_index=False).count().\ - groupby(['PARTICIPANTID'], as_index=False).count().sort_values('PARTICIPANTID').reset_index(drop=True) - data = data.groupby(['PARTICIPANTID', 'LASTCHANGED'], as_index=False).count().\ - groupby(['PARTICIPANTID'], as_index=False).count().sort_values('PARTICIPANTID').reset_index(drop=True) - assert_frame_equal(raw_data, data) - - -class TestFCAS4SecondData(unittest.TestCase): - def setUp(self): - self.start_day = (datetime.now() - timedelta(30)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) - self.start_month = self.start_day.replace( - day=1, hour=0, minute=0, second=0, microsecond=0 - ) - self.start_year = self.start_day.replace( - month=1, day=1, hour=0, minute=0, second=0, microsecond=0 - ) - pass - - def test_dispatch_tables_start_of_month(self): - table = "FCAS_4_SECOND" - start_time = self.start_day - minute_offset = 5 - end_time = start_time + timedelta(minutes=minute_offset) - start_str = start_time.strftime("%Y/%m/%d %H:%M:%S") - end_str = end_time.strftime("%Y/%m/%d %H:%M:%S") - - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - cols = [dat_col, "ELEMENTNUMBER", "VARIABLENUMBER"] - # expected length assumes first data point is at 00:00:00 - expected_length = 15 * minute_offset - length_check = False - expected_number_of_columns = 3 - data = data_fetch_methods.dynamic_data_compiler( - start_str, - end_str, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=False, - select_columns=cols, - ) - length_array = data[dat_col].drop_duplicates() - if length_array.shape[0] == expected_length: - length_check = True - # case if first data point is 00:00:01/02/03 - elif length_array.shape[0] == expected_length - 1: - length_check = True - self.assertTrue(length_check) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - def test_fcas_tables_end_of_month(self): - table = "FCAS_4_SECOND" - minute_offset = 5 - start_time = self.start_month - timedelta(minutes=minute_offset) - end_time = start_time + timedelta(minutes=minute_offset * 2) - start_str = start_time.strftime("%Y/%m/%d %H:%M:%S") - end_str = end_time.strftime("%Y/%m/%d %H:%M:%S") - - print("Testing {} returing values at end of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - cols = [dat_col, "ELEMENTNUMBER", "VARIABLENUMBER"] - # expected length assumes first data point is at 00:00:00 - expected_length = 15 * (minute_offset * 2) - length_check = False - expected_number_of_columns = 3 - data = data_fetch_methods.dynamic_data_compiler( - start_str, - end_str, - table, - defaults.raw_data_cache, - fformat="feather", - keep_csv=False, - select_columns=cols, - ) - length_array = data[dat_col].drop_duplicates() - if length_array.shape[0] == expected_length: - length_check = True - # case if first data point is 00:00:01/02/03 - elif length_array.shape[0] == expected_length - 1: - length_check = True - print(length_array.shape[0]) - self.assertTrue(length_check) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - @unittest.skipIf( - (datetime.now() - datetime(year=datetime.now().year, month=1, day=1)).days > 60, - "start of year data not available: > 60 days ago", - ) - def test_fcas_tables_end_of_year(self): - table = "FCAS_4_SECOND" - minute_offset = 5 - start_time = self.start_year - timedelta(minutes=minute_offset) - end_time = start_time + timedelta(minutes=minute_offset * 2) - start_str = start_time.strftime("%Y/%m/%d %H:%M:%S") - end_str = end_time.strftime("%Y/%m/%d %H:%M:%S") - - print("Testing {} returing values at end of year.".format(table)) - dat_col = defaults.primary_date_columns[table] - cols = [dat_col, "ELEMENTNUMBER", "VARIABLENUMBER"] - expected_length = 15 * (minute_offset * 2) - length_check = False - expected_number_of_columns = 3 - data = data_fetch_methods.dynamic_data_compiler( - start_str, - end_str, - table, - defaults.raw_data_cache, - select_columns=cols, - fformat="feather", - keep_csv=False, - ) - length_array = data[dat_col].drop_duplicates() - if length_array.shape[0] == expected_length: - length_check = True - # case if first data point is 00:00:01/02/03 - elif length_array.shape[0] == expected_length - 1: - length_check = True - self.assertTrue(length_check) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - -class TestStaticTables(unittest.TestCase): - def setUp(self): - pass - - def test_fcas_elements_table(self): - start_time = "2017/12/31 23:55:04" - end_time = "2018/01/01 00:05:00" - table = "ELEMENTS_FCAS_4_SECOND" - cols = ["ELEMENTNUMBER", "EMSNAME"] - filter_cols = ("ELEMENTNUMBER",) - func = data_fetch_methods._static_table_wrapper_for_gui - data = func( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=(["1"],), - ) - expected_length = 1 - expected_number_of_columns = 2 - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - def test_fcas_variable_table(self): - start_time = "2018/12/31 23:55:04" - end_time = "2018/01/01 00:05:00" - table = "VARIABLES_FCAS_4_SECOND" - cols = ["VARIABLENUMBER", "VARIABLETYPE"] - filter_cols = ("VARIABLENUMBER",) - func = data_fetch_methods._static_table_wrapper_for_gui - data = func( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=(["2"],), - ) - expected_length = 1 - expected_number_of_columns = 2 - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - def test_registration_list(self): - table = "Generators and Scheduled Loads" - cols = ["DUID", "Technology Type - Primary"] - filter_cols = ("DUID",) - data = data_fetch_methods.static_table_xl( - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=(["AGLHAL"],), - ) - expected_length = 1 - expected_number_of_columns = 2 - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - print("Passed") - - -class TestCustomTables(unittest.TestCase): - def setUp(self): - pass - - def test_works_on_recent_data(self): - table = "FCAS_4_SECOND" - minute_offset = 5 - start_time = datetime( - year=datetime.now().year, - month=datetime.now().month, - day=1 - ) - timedelta(days=40) - end_time = start_time + timedelta(minutes=minute_offset * 2) - start_time = start_time.strftime("%Y/%m/%d %H:%M:%S") - end_time = end_time.strftime("%Y/%m/%d %H:%M:%S") - print("Testing custom table {}.".format(table)) - data = custom_tables.fcas4s_scada_match( - start_time, end_time, table, defaults.raw_data_cache - ) - data = data.reset_index(drop=True) - contains_duplicates = data.duplicated(["MARKETNAME"]).any() - self.assertEqual(False, contains_duplicates) - contains_duplicates = data.duplicated(["ELEMENTNUMBER"]).any() - self.assertEqual(False, contains_duplicates) - not_empty = data.shape[0] > 0 - self.assertEqual(True, not_empty) - print("Passed") diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 0000000..f2b6bdb --- /dev/null +++ b/tests/test_errors.py @@ -0,0 +1,176 @@ +"""Offline tests for NEMOSIS error paths and cache idempotency. + +Covers argument-validation errors (`UserInputError`) and network/data- +availability errors (`NoDataToReturn`) for `dynamic_data_compiler`, +`cache_compiler`, and `static_table`. Also verifies that after a +successful fetch populates the cache, a second call with the same +arguments reads from the cache rather than re-fetching. + +All tests run against the `nemosis_fixture` dummy server — no network. +The NoDataToReturn cases are triggered either by querying a date range +the fixture doesn't cover (dummy server 404s) or by patching the +static-table URL to an invalid path. +""" +import pytest + +from nemosis import cache_compiler, defaults, dynamic_data_compiler, static_table +from nemosis.custom_errors import NoDataToReturn, UserInputError + + +# --------------------------------------------------------------------------- +# Argument-validation: dynamic_data_compiler +# --------------------------------------------------------------------------- + +def test_dynamic_bad_table_name(nemosis_fixture): + with pytest.raises(UserInputError, match="not a dynamic table"): + dynamic_data_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "NOTATABLE", str(nemosis_fixture), + ) + + +def test_dynamic_bad_fformat(nemosis_fixture): + with pytest.raises(UserInputError, match="fformat must be"): + dynamic_data_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + fformat="db", + ) + + +def test_dynamic_filter_col_not_in_select_columns(nemosis_fixture): + with pytest.raises(UserInputError, match="Filter columns not valid"): + dynamic_data_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + + +def test_dynamic_filter_col_not_a_real_column(nemosis_fixture): + """When `select_columns` is None the filter is validated against the + table's default columns — a nonexistent column should still fail.""" + with pytest.raises(UserInputError, match="Filter columns not valid"): + dynamic_data_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + filter_cols=["NOTACOLUMN"], + filter_values=(["0"],), + ) + + +def test_dynamic_select_columns_all_requires_csv(nemosis_fixture): + with pytest.raises(UserInputError, match="select_columns='all'"): + dynamic_data_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + select_columns="all", + fformat="feather", + ) + + +# --------------------------------------------------------------------------- +# Argument-validation: cache_compiler +# --------------------------------------------------------------------------- + +def test_cache_bad_table_name(nemosis_fixture): + with pytest.raises(UserInputError, match="not a dynamic table"): + cache_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "NOTATABLE", str(nemosis_fixture), + ) + + +def test_cache_bad_fformat(nemosis_fixture): + """`cache_compiler` rejects 'csv' too — only feather/parquet.""" + with pytest.raises(UserInputError, match="fformat must be"): + cache_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + fformat="csv", + ) + + +def test_cache_select_columns_requires_rebuild(nemosis_fixture): + with pytest.raises(UserInputError, match="rebuild=True"): + cache_compiler( + "2018/05/01 00:00:00", "2018/05/01 01:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "RRP"], + ) + + +# --------------------------------------------------------------------------- +# Argument-validation: static_table +# --------------------------------------------------------------------------- + +def test_static_bad_table_name(nemosis_fixture): + with pytest.raises(UserInputError, match="not a static table"): + static_table("NOTATABLE", str(nemosis_fixture)) + + +def test_static_filter_col_not_in_select_columns(nemosis_fixture): + with pytest.raises(UserInputError, match="Filter columns not valid"): + static_table( + "VARIABLES_FCAS_4_SECOND", str(nemosis_fixture), + select_columns=["VARIABLENUMBER"], + filter_cols=["VARIABLETYPE"], + filter_values=(["MW"],), + ) + + +# --------------------------------------------------------------------------- +# Network / availability: NoDataToReturn +# --------------------------------------------------------------------------- + +def test_dynamic_no_data_raises(nemosis_fixture): + """Query a date range the fixture doesn't cover — the dummy server + 404s every month, NEMOSIS gives up and raises NoDataToReturn.""" + with pytest.raises(NoDataToReturn, match="Compiling data for table DISPATCHPRICE failed"): + dynamic_data_compiler( + "2000/01/01 00:00:00", "2000/02/01 00:00:00", + "DISPATCHPRICE", str(nemosis_fixture), + ) + + +def test_static_no_data_raises(nemosis_fixture, monkeypatch): + """Redirect a static-table URL to a path the dummy server doesn't + serve — NEMOSIS should raise NoDataToReturn, not crash cryptically.""" + monkeypatch.setitem( + defaults.static_table_url, + "VARIABLES_FCAS_4_SECOND", + "http://127.0.0.1:1/does-not-exist.csv", + ) + with pytest.raises(NoDataToReturn, match="Compiling data for table VARIABLES_FCAS_4_SECOND failed"): + static_table("VARIABLES_FCAS_4_SECOND", str(nemosis_fixture)) + + +# --------------------------------------------------------------------------- +# Cache idempotency +# --------------------------------------------------------------------------- + +def test_cache_is_reused_on_second_call(nemosis_fixture, monkeypatch): + """After the first call populates the cache with a feather file, a + second call with the same arguments must read from that cache rather + than re-fetching. We prove it by breaking the HTTP server mid-run: + the first call succeeds (fetches and caches), then we redirect the + AEMO URL to an invalid path, and the second call still succeeds.""" + kwargs = dict( + start_time="2018/05/01 00:00:00", + end_time="2018/05/01 01:00:00", + table_name="DISPATCHPRICE", + raw_data_location=str(nemosis_fixture), + select_columns=["SETTLEMENTDATE", "REGIONID", "RRP", "INTERVENTION"], + filter_cols=["INTERVENTION"], + filter_values=([0],), + ) + first = dynamic_data_compiler(**kwargs) + assert not first.empty + + # Cache is now on disk. Break the network: any new fetch would 404. + monkeypatch.setattr(defaults, "aemo_mms_url", "http://127.0.0.1:1/nowhere") + + second = dynamic_data_compiler(**kwargs) + assert first.equals(second) diff --git a/tests/test_errors_and_warnings.py b/tests/test_errors_and_warnings.py deleted file mode 100644 index 562e404..0000000 --- a/tests/test_errors_and_warnings.py +++ /dev/null @@ -1,330 +0,0 @@ -import unittest -from nemosis import dynamic_data_compiler, cache_compiler, static_table -from nemosis import defaults -import os -from unittest.mock import patch -from io import StringIO - - -class TestDynamicDataCompilerRaisesExpectedErrors(unittest.TestCase): - def test_raise_error_for_incorrect_table_name(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2000/01/01 00:00:00", - "2000/02/01 00:00:00", - "NOTATABLE", - defaults.raw_data_cache, - ) - self.assertTrue( - "Table name provided is not a dynamic table." in str(context.exception) - ) - - def test_raise_error_for_no_data_returned(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2000/01/01 00:00:00", - "2000/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - ) - self.assertTrue( - ( - f"Compiling data for table DISPATCHPRICE failed. " - + "This probably because none of the requested data " - + "could be download from AEMO. Check your internet " - + "connection and that the requested data is archived on: " - + "https://nemweb.com.au see nemosis.defaults for table specific urls." - ) - in str(context.exception) - ) - - def test_raise_error_for_filter_column_not_in_select_columns(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - select_columns=["REGIONID", "SETTLEMENTDATE", "RRP"], - filter_cols=["INTERVENTION"], - filter_values=(["0"],), - ) - self.assertTrue( - ( - "Filter columns not valid. They must be a part of " - + "select_columns or the table defaults." - ) - in str(context.exception) - ) - - def test_raise_error_for_filter_column_not_in_default_columns(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - select_columns=["REGIONID", "SETTLEMENTDATE", "RRP"], - filter_cols=["NOTACOLUMN"], - filter_values=(["0"],), - ) - self.assertTrue( - ( - "Filter columns not valid. They must be a part of " - + "select_columns or the table defaults." - ) - in str(context.exception) - ) - - def test_raise_error_if_fformat_not_in_expected_set(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - fformat="db", - ) - self.assertTrue( - "Argument fformat must be 'csv', 'feather' or 'parquet'" - in str(context.exception) - ) - - def test_raise_error_if_select_columns_not_in_data(self): - with self.assertRaises(Exception) as context: - dynamic_data_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - select_columns=["NOTACOLUMN"], - ) - self.assertIn( - ( - f"None of columns ['NOTACOLUMN'] are in D:\\nemosis_test_cache\\PUBLIC_DVD_DISPATCHPRICE_201812010000.feather. " - "This may be caused by user input if the 'select_columns' " - "argument is being used, or by changed AEMO data formats. " - "This error can be avoided by using the argument select_columns='all'." - ), - str(context.exception) - ) - - def test_using_select_columns_all_does_not_raise_error(self): - price_data = dynamic_data_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - select_columns="all", - fformat="csv", - ) - expected_columns = [ - "I", - "DISPATCH", - "PRICE", - "1", - "SETTLEMENTDATE", - "RUNNO", - "REGIONID", - "DISPATCHINTERVAL", - "INTERVENTION", - "RRP", - "EEP", - "ROP", - "APCFLAG", - "MARKETSUSPENDEDFLAG", - "LASTCHANGED", - "RAISE6SECRRP", - "RAISE6SECROP", - "RAISE6SECAPCFLAG", - "RAISE60SECRRP", - "RAISE60SECROP", - "RAISE60SECAPCFLAG", - "RAISE5MINRRP", - "RAISE5MINROP", - "RAISE5MINAPCFLAG", - "RAISEREGRRP", - "RAISEREGROP", - "RAISEREGAPCFLAG", - "LOWER6SECRRP", - "LOWER6SECROP", - "LOWER6SECAPCFLAG", - "LOWER60SECRRP", - "LOWER60SECROP", - "LOWER60SECAPCFLAG", - "LOWER5MINRRP", - "LOWER5MINROP", - "LOWER5MINAPCFLAG", - "LOWERREGRRP", - "LOWERREGROP", - "LOWERREGAPCFLAG", - "PRICE_STATUS", - "PRE_AP_ENERGY_PRICE", - "PRE_AP_RAISE6_PRICE", - "PRE_AP_RAISE60_PRICE", - "PRE_AP_RAISE5MIN_PRICE", - "PRE_AP_RAISEREG_PRICE", - "PRE_AP_LOWER6_PRICE", - "PRE_AP_LOWER60_PRICE", - "PRE_AP_LOWER5MIN_PRICE", - "PRE_AP_LOWERREG_PRICE", - "CUMUL_PRE_AP_ENERGY_PRICE", - "CUMUL_PRE_AP_RAISE6_PRICE", - "CUMUL_PRE_AP_RAISE60_PRICE", - "CUMUL_PRE_AP_RAISE5MIN_PRICE", - "CUMUL_PRE_AP_RAISEREG_PRICE", - "CUMUL_PRE_AP_LOWER6_PRICE", - "CUMUL_PRE_AP_LOWER60_PRICE", - "CUMUL_PRE_AP_LOWER5MIN_PRICE", - "CUMUL_PRE_AP_LOWERREG_PRICE", - ] - self.assertSequenceEqual(list(price_data.columns), expected_columns) - -class TestWarnings(unittest.TestCase): - def test_no_parse_warning(self): - with patch('sys.stderr', new=StringIO()) as fakeOutput: - print('hello world') - - dynamic_data_compiler( - start_time='2017/01/01 00:00:00', - end_time='2017/01/01 00:05:00', - table_name='DISPATCHPRICE', - raw_data_location=defaults.raw_data_cache - ) - - stderr = fakeOutput.getvalue().strip() - self.assertNotIn("UserWarning: Could not infer format", stderr) - -class TestCacheCompilerRaisesExpectedErrors(unittest.TestCase): - def test_raise_error_for_incorrect_table_name(self): - with self.assertRaises(Exception) as context: - cache_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "NOTATABLE", - defaults.raw_data_cache, - fformat="db", - ) - self.assertTrue( - "Table name provided is not a dynamic table." in str(context.exception) - ) - - def test_raise_error_if_fformat_not_in_expected_set(self): - with self.assertRaises(Exception) as context: - cache_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - fformat="db", - ) - self.assertTrue( - "Argument fformat must be 'feather' or 'parquet'" in str(context.exception) - ) - - def test_raise_error_if_select_columns_used_without_rebuild_true(self): - with self.assertRaises(Exception) as context: - cache_compiler( - "2019/01/01 00:00:00", - "2019/02/01 00:00:00", - "DISPATCHPRICE", - defaults.raw_data_cache, - select_columns="all", - ) - self.assertTrue( - ( - "The select_columns argument must be used with rebuild=True " - + "to ensure the cache is built with the correct columns." - ) - in str(context.exception) - ) - - - -class TestStaticTableRaisesExpectedErrors(unittest.TestCase): - def test_raise_error_for_incorrect_table_name(self): - with self.assertRaises(Exception) as context: - static_table("NOTATABLE", defaults.raw_data_cache) - self.assertTrue( - "Table name provided is not a static table." in str(context.exception) - ) - - def test_raise_error_for_no_data_returned(self): - good_url = defaults.static_table_url["VARIABLES_FCAS_4_SECOND"] - defaults.static_table_url["VARIABLES_FCAS_4_SECOND"] = "bad_url" - path_and_name = ( - defaults.raw_data_cache + "/" + defaults.names["VARIABLES_FCAS_4_SECOND"] - ) - if os.path.isfile(path_and_name): - os.remove(path_and_name) - with self.assertRaises(Exception) as context: - static_table("VARIABLES_FCAS_4_SECOND", defaults.raw_data_cache) - self.assertTrue( - ( - f"Compiling data for table VARIABLES_FCAS_4_SECOND failed. " - + "This probably because none of the requested data " - + "could be download from AEMO. Check your internet " - + "connection and that the requested data is archived on: " - + "https://nemweb.com.au see nemosis.defaults for table specific urls." - ) - in str(context.exception) - ) - defaults.static_table_url["VARIABLES_FCAS_4_SECOND"] = good_url - - def test_raise_error_for_filter_column_not_in_select_columns(self): - with self.assertRaises(Exception) as context: - static_table( - "VARIABLES_FCAS_4_SECOND", - defaults.raw_data_cache, - select_columns=["VARIABLENUMBER"], - filter_cols=["VARIABLETYPE"], - filter_values=(["0"],), - ) - self.assertTrue( - ( - "Filter columns not valid. They must be a part of " - + "select_columns or the table defaults." - ) - in str(context.exception) - ) - - def test_raise_error_for_filter_column_not_in_default_columns(self): - with self.assertRaises(Exception) as context: - static_table( - "VARIABLES_FCAS_4_SECOND", - defaults.raw_data_cache, - select_columns=["VARIABLENUMBER"], - filter_cols=["NOTACOLUMN"], - filter_values=(["0"],), - ) - self.assertTrue( - ( - "Filter columns not valid. They must be a part of " - + "select_columns or the table defaults." - ) - in str(context.exception) - ) - - def test_raise_error_if_select_columns_not_in_data(self): - with self.assertRaises(Exception) as context: - static_table( - "VARIABLES_FCAS_4_SECOND", - defaults.raw_data_cache, - select_columns=["NOTACOLUMN"], - ) - self.assertIn( - ( - f"None of columns ['NOTACOLUMN'] are in D:\\nemosis_test_cache\\Ancillary Services Market Causer Pays Variables File.csv. " - "This may be caused by user input if the 'select_columns' " - "argument is being used, or by changed AEMO data formats. " - "This error can be avoided by using the argument select_columns='all'." - ), - str(context.exception) - ) - - def test_using_select_columns_all_does_not_raise_error(self): - price_data = static_table( - "VARIABLES_FCAS_4_SECOND", defaults.raw_data_cache, select_columns="all" - ) - expected_columns = ["VARIABLENUMBER", "VARIABLETYPE"] - self.assertSequenceEqual(list(price_data.columns), expected_columns) diff --git a/tests/test_format_options.py b/tests/test_format_options.py deleted file mode 100644 index b6fbe77..0000000 --- a/tests/test_format_options.py +++ /dev/null @@ -1,376 +0,0 @@ -import unittest -from datetime import timedelta -from nemosis import defaults, data_fetch_methods -import pandas as pd -import os - - -class TestFormatOptions(unittest.TestCase): - def setUp(self): - # TODO: Clean tests since only one table - BIDDAYOFFER_D is tested - self.table_names = ["BIDDAYOFFER_D"] - - self.table_types = { - "DISPATCHLOAD": "DUID", - "DISPATCHCONSTRAINT": "CONSTRAINTID", - "DISPATCH_UNIT_SCADA": "DUID", - "DISPATCHPRICE": "REGIONID", - "DISPATCHINTERCONNECTORRES": "INTERCONNECTORID", - "DISPATCHREGIONSUM": "REGIONID", - "BIDPEROFFER_D": "DUID-BIDTYPE", - "BIDDAYOFFER_D": "DUID-BIDTYPE", - "TRADINGLOAD": "DUID", - "TRADINGPRICE": "REGIONID", - "TRADINGREGIONSUM": "REGIONID", - "TRADINGINTERCONNECT": "INTERCONNECTORID", - } - - self.filter_values = { - "DUID": (["AGLHAL"],), - "REGIONID": (["SA1"],), - "INTERCONNECTORID": (["VIC1-NSW1"],), - "CONSTRAINTID": (["DATASNAP_DFS_Q_CLST"],), - "DUID-BIDTYPE": (["AGLHAL", "ENERGY"],), - } - - def test_dispatch_tables_start_of_month_just_csv_format_dont_keep(self): - # Empty cache. - for f in os.listdir(defaults.raw_data_cache): - os.remove(os.path.join(defaults.raw_data_cache, f)) - - start_time = "2018/02/01 00:00:00" - end_time = "2018/02/01 05:15:00" - for table in self.table_names: - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - cols = [dat_col, self.table_types[table]] - filter_cols = (self.table_types[table],) - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if table in [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ]: - expected_length = 10 - expected_first_time = "2018/02/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = "2018/02/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if table == "BIDPEROFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - expected_length = 2 - expected_last_time = "2018/02/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_first_time = "2018/01/31 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - fformat="csv", - keep_csv=False, - ) - data = data.reset_index(drop=True) - print(table) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - self.assertEqual(len(os.listdir(defaults.raw_data_cache)), 0) - print("Passed") - - def test_dispatch_tables_start_of_month_just_csv_format(self): - # Empty cache. - for f in os.listdir(defaults.raw_data_cache): - os.remove(os.path.join(defaults.raw_data_cache, f)) - - start_time = "2018/02/01 00:00:00" - end_time = "2018/02/01 05:15:00" - for table in self.table_names: - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - cols = [dat_col, self.table_types[table]] - filter_cols = (self.table_types[table],) - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if table in [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ]: - expected_length = 10 - expected_first_time = "2018/02/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = "2018/02/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if table == "BIDPEROFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - expected_length = 2 - expected_last_time = "2018/02/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_first_time = "2018/01/31 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - fformat="csv", - ) - data = data.reset_index(drop=True) - print(table) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - self.assertNotEqual(len(os.listdir(defaults.raw_data_cache)), 0) - print("Passed") - - # Test that also works on second pass. - start_time = "2018/02/01 00:00:00" - end_time = "2018/02/01 05:15:00" - for table in self.table_names: - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - cols = [dat_col, self.table_types[table]] - filter_cols = (self.table_types[table],) - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime( - end_time, format="%Y/%m/%d %H:%M:%S" - ) - if table in [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ]: - expected_length = 10 - expected_first_time = "2018/02/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = "2018/02/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if table == "BIDPEROFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - expected_length = 2 - expected_last_time = "2018/02/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_first_time = "2018/01/31 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - fformat="csv", - ) - data = data.reset_index(drop=True) - print(table) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - self.assertNotEqual(len(os.listdir(defaults.raw_data_cache)), 0) - print("Passed") - - def test_dispatch_tables_start_of_month_feather_format(self): - start_time = "2018/02/01 00:00:00" - end_time = "2018/02/01 05:15:00" - for table in self.table_names: - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - cols = [dat_col, self.table_types[table]] - filter_cols = (self.table_types[table],) - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if table in [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ]: - expected_length = 10 - expected_first_time = "2018/02/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = "2018/02/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if table == "BIDPEROFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - expected_length = 2 - expected_last_time = "2018/02/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_first_time = "2018/01/31 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - print(table) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - fformat="feather", - ) - data = data.reset_index(drop=True) - print(table) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - print("Passed") - - def test_dispatch_tables_start_of_month_parquet_format(self): - start_time = "2018/02/01 00:00:00" - end_time = "2018/02/01 05:15:00" - for table in self.table_names: - print("Testing {} returing values at start of month.".format(table)) - dat_col = defaults.primary_date_columns[table] - table_type = self.table_types[table] - cols = [dat_col, self.table_types[table]] - filter_cols = (self.table_types[table],) - expected_length = 63 - expected_number_of_columns = 2 - expected_first_time = pd.to_datetime( - start_time, format="%Y/%m/%d %H:%M:%S" - ) + timedelta(minutes=5) - expected_last_time = pd.to_datetime(end_time, format="%Y/%m/%d %H:%M:%S") - if table in [ - "TRADINGLOAD", - "TRADINGPRICE", - "TRADINGREGIONSUM", - "TRADINGINTERCONNECT", - ]: - expected_length = 10 - expected_first_time = "2018/02/01 00:30:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_last_time = "2018/02/01 05:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - if table == "BIDPEROFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - if table == "BIDDAYOFFER_D": - cols = [dat_col, "DUID", "BIDTYPE"] - filter_cols = ("DUID", "BIDTYPE") - expected_number_of_columns = 3 - expected_length = 2 - expected_last_time = "2018/02/01 00:00:00" - expected_last_time = pd.to_datetime( - expected_last_time, format="%Y/%m/%d %H:%M:%S" - ) - expected_first_time = "2018/01/31 00:00:00" - expected_first_time = pd.to_datetime( - expected_first_time, format="%Y/%m/%d %H:%M:%S" - ) - data = data_fetch_methods.dynamic_data_compiler( - start_time, - end_time, - table, - defaults.raw_data_cache, - select_columns=cols, - filter_cols=filter_cols, - filter_values=self.filter_values[table_type], - fformat="parquet", - parse_data_types=True, - ) - data = data.reset_index(drop=True) - print(table) - self.assertEqual(expected_length, data.shape[0]) - self.assertEqual(expected_number_of_columns, data.shape[1]) - self.assertEqual(expected_first_time, data[dat_col][0]) - self.assertEqual(expected_last_time, data[dat_col].iloc[-1]) - self.assertFalse(all(object == data.dtypes)) - print("Passed") diff --git a/tests/test_processing_info_maps.py b/tests/test_processing_info_maps.py index 6c2e4c1..4294f4a 100644 --- a/tests/test_processing_info_maps.py +++ b/tests/test_processing_info_maps.py @@ -1,219 +1,279 @@ -import unittest +"""Offline validation of `processing_info_maps.search_type` against AEMO's +actual storage patterns. + +For each table classified in `search_type`, the classification determines how +NEMOSIS iterates monthly archives and whether it deduplicates on read: + + start_to_end each monthly archive covers its own month's rows only — + no overlap between months (with narrow exceptions where + AEMO publishes a day or two into the next month). + Library concatenates directly. + all each monthly archive is a cumulative snapshot (for true + registration tables like PARTICIPANT) or a change-log + (for constraint tables like GENCONDATA). In either case + the library deduplicates via `drop_duplicates_by_primary_key`. + end the single end-time archive is cumulative back to table + origin, so only one fetch is needed. + +This file reads raw AEMO CSVs from the fixture zips directly (no +`dynamic_data_compiler`, no HTTP server) and checks AEMO's stored data +actually behaves the way each classification assumes. A mis-classified table +or a wrong `table_primary_keys` entry surfaces here even when no per-table +test covers the affected table. + +Scrape-pattern tables (DAILY_REGION_SUMMARY, NEXT_DAY_DISPATCHLOAD, +INTERMITTENT_GEN_SCADA) are excluded — their per-day file layout doesn't +fit the cross-month semantics probed here, and their classification is +exercised by their per-table end-to-end tests. +""" +from __future__ import annotations + +import io +import zipfile +from pathlib import Path -from nemosis import processing_info_maps, defaults, query_wrappers, data_fetch_methods import pandas as pd -from datetime import datetime, timedelta +import pytest + +from nemosis import defaults +from nemosis.processing_info_maps import search_type +from nemosis.query_wrappers import drop_duplicates_by_primary_key + + +FIXTURE_ROOT = Path(__file__).resolve().parent / "fixtures" / "data" +MMS_DATA_DIR = ( + "Data_Archive/Wholesale_Electricity/MMSDM/{year}/" + "MMSDM_{year}_{month:02d}/MMSDM_Historical_Data_SQLLoader/DATA" +) + +_SCRAPE_TABLES = {"DAILY_REGION_SUMMARY", "NEXT_DAY_DISPATCHLOAD", "INTERMITTENT_GEN_SCADA"} +_PARKED = {"FCAS_4_SECOND"} # upstream AEMO outage — issue #64 +_NON_MMS = _SCRAPE_TABLES | _PARKED + +_TIME_COLS = {"SETTLEMENTDATE", "INTERVAL_DATETIME", "TIMESTAMP", "RUN_DATETIME"} + +# `start_to_end` tables whose AEMO archives genuinely overlap at month +# boundaries (the published archive for month N extends a day or two into +# month N+1). Excluded from the disjointness check — this is AEMO's format, +# not a classification bug. NEMOSIS handles the overlap via the SETTLEMENTDATE +# range filter applied after fetch. +_ARCHIVES_OVERLAP_INTO_NEXT_MONTH = {"ROOFTOP_PV_ACTUAL"} + + +# --------------------------------------------------------------------------- +# Fixture zip helpers +# --------------------------------------------------------------------------- + +def _mms_zip_path(table: str, year: int, month: int) -> Path | None: + """Find an MMS fixture zip for `table` at `year-month`. Tries both the + pre-2024-08 `PUBLIC_DVD_` filename and the post-cutover `PUBLIC_ARCHIVE#` + one. Returns None if neither exists.""" + base = FIXTURE_ROOT / MMS_DATA_DIR.format(year=year, month=month) + for name in ( + f"PUBLIC_DVD_{table}_{year}{month:02d}010000.zip", + f"PUBLIC_ARCHIVE#{table}#FILE01#{year}{month:02d}010000.zip", + ): + if (base / name).exists(): + return base / name + return None + + +def _read_mms_zip(path: Path) -> pd.DataFrame: + """Extract the single CSV from an MMS archive zip and return just the + D-row content as a DataFrame. AEMO CSVs have `C,...` header metadata, + one `I,...` row with column names, and many `D,...` data rows. The + first 4 fields of I/D rows are structural (row-type, market section, + table name, version) — this helper strips them so the frame has only + the actual table columns.""" + with zipfile.ZipFile(path) as z: + csv_name = next(n for n in z.namelist() if n.endswith(".CSV")) + content = z.read(csv_name).decode() + + lines = content.splitlines() + header = next(l for l in lines if l.startswith("I,")) + data_rows = [l for l in lines if l.startswith("D,")] + csv_body = header + "\n" + "\n".join(data_rows) + df = pd.read_csv(io.StringIO(csv_body)) + return df.iloc[:, 4:] + + +def _fixtured_months(table: str) -> list[tuple[int, int]]: + """Every (year, month) pair for which a fixture zip exists for `table`, + sorted chronologically. Used to pick consecutive-month pairs for + storage-pattern checks.""" + months: list[tuple[int, int]] = [] + mms_root = FIXTURE_ROOT / "Data_Archive/Wholesale_Electricity/MMSDM" + if not mms_root.exists(): + return months + for year_dir in sorted(mms_root.iterdir()): + for month_dir in sorted(year_dir.iterdir()): + parts = month_dir.name.split("_") + if len(parts) != 3 or parts[0] != "MMSDM": + continue + try: + year, month = int(parts[1]), int(parts[2]) + except ValueError: + continue + if _mms_zip_path(table, year, month) is not None: + months.append((year, month)) + return months + +def _consecutive_pair(months: list[tuple[int, int]]) -> tuple | None: + """Pick the first consecutive-month pair in `months`, or None if + none of the fixtured months are adjacent.""" + for (y1, m1), (y2, m2) in zip(months, months[1:]): + if (y2, m2) == ((y1 + 1, 1) if m1 == 12 else (y1, m1 + 1)): + return (y1, m1), (y2, m2) + return None -class TestSearchTypeValidity(unittest.TestCase): - def setUp(self): - self.time_yesterday = (datetime.now() - timedelta(days=1)).replace( - hour=0, minute=0, second=0, microsecond=0 + +def _shared_pk_columns(table: str, *dfs: pd.DataFrame) -> list[str]: + """PK columns present in every supplied frame. Drops any PK column + that's missing from older archives (handles AEMO schema drift like + BIDPEROFFER_D's `DIRECTION`, added mid-history).""" + full_pk = defaults.table_primary_keys[table] + return [c for c in full_pk if all(c in df.columns for df in dfs)] + + +# --------------------------------------------------------------------------- +# Table groupings by classification +# --------------------------------------------------------------------------- + +_START_TO_END_TABLES = sorted( + t for t, st in search_type.items() + if st == "start_to_end" and t not in _NON_MMS +) +_ALL_TABLES = sorted(t for t, st in search_type.items() if st == "all") +_END_TABLES = sorted(t for t, st in search_type.items() if st == "end") + + +# --------------------------------------------------------------------------- +# Metadata invariants — classification must be consistent with schema. +# Pure unit tests: no fixture needed, just library config. +# --------------------------------------------------------------------------- + +@pytest.mark.parametrize("table", _START_TO_END_TABLES) +def test_start_to_end_table_has_a_time_column(table): + """`start_to_end` implies each row has a unique timestamp, so the table + must have a SETTLEMENTDATE/INTERVAL_DATETIME/TIMESTAMP/RUN_DATETIME + column. Missing it suggests misclassification (should probably be + `all` instead).""" + cols = set(defaults.table_columns[table]) + assert cols & _TIME_COLS, ( + f"{table} classified as start_to_end but has no time column " + f"(looked for {_TIME_COLS})" + ) + + +@pytest.mark.parametrize("table", _ALL_TABLES) +def test_all_table_has_no_time_column(table): + """`all` tables are effective-date config/change-log tables, not time + series. A time column here would suggest the table should probably + be classified as `start_to_end`.""" + cols = set(defaults.table_columns[table]) + leaked = cols & _TIME_COLS + assert not leaked, ( + f"{table} classified as 'all' but has time column(s) {leaked} — " + f"may be misclassified (should likely be start_to_end)" + ) + + +# --------------------------------------------------------------------------- +# Storage-pattern validation — read raw archives and check AEMO's actual +# behaviour matches what the classification assumes. +# --------------------------------------------------------------------------- + +@pytest.mark.parametrize("table", _START_TO_END_TABLES) +def test_start_to_end_archives_are_disjoint_across_months(table): + """`start_to_end` tables should have no overlapping rows between + consecutive monthly archives — each row lands in exactly one month's + file, so concatenation gives zero PK duplicates.""" + if table in _ARCHIVES_OVERLAP_INTO_NEXT_MONTH: + pytest.skip(f"{table}: AEMO archives legitimately overlap at month boundary") + months = _fixtured_months(table) + pair = _consecutive_pair(months) + if pair is None: + pytest.skip( + f"{table}: fixture lacks a consecutive-month pair " + f"(have {[f'{y}-{m:02d}' for y, m in months]})" ) - pass - - def test_start_to_end_no_duplication_between_batches(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "start_to_end": - print("Validating start_to_end type for table {}".format(table_name)) - start_time = datetime.strptime( - "2018/06/01 00:00:00", "%Y/%m/%d %H:%M:%S" - ) - end_time = datetime.strptime("2018/09/01 00:00:00", "%Y/%m/%d %H:%M:%S") - if table_name in ["DAILY_REGION_SUMMARY", "NEXT_DAY_DISPATCHLOAD", "INTERMITTENT_GEN_SCADA"]: - end_time = self.time_yesterday - start_time = self.time_yesterday - timedelta(days=8) - if table_name in ["FCAS_4_SECOND"]: - end_time = self.time_yesterday - start_time = self.time_yesterday - timedelta(hours=2) - data_tables = data_fetch_methods._dynamic_data_fetch_loop( - start_search=start_time, - start_time=start_time, - end_time=end_time, - table_name=table_name, - raw_data_location=defaults.raw_data_cache, - select_columns=defaults.table_primary_keys[table_name], - date_filter=None, - keep_csv=False, - ) - all_data = pd.concat(data_tables, sort=False) - contains_duplicates = all_data.duplicated().any() - self.assertEqual( - False, contains_duplicates, "table {}".format(table_name) - ) - print("Type valid, no duplicates found.") - - def test_start_to_end_has_settlement_or_interval_col(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "start_to_end": - has_settlement_date_col = ( - "SETTLEMENTDATE" in defaults.table_columns[table_name] - ) - has_interval_datetime_col = ( - "INTERVAL_DATETIME" in defaults.table_columns[table_name] - ) - has_interval_timestamp_col = ( - "TIMESTAMP" in defaults.table_columns[table_name] - ) - has_run_datetime_col = ( - "RUN_DATETIME" in defaults.table_columns[table_name] - ) - has_either = ( - has_interval_datetime_col - or has_settlement_date_col - or has_interval_timestamp_col - or has_run_datetime_col - ) - self.assertEqual(True, has_either) - print( - "{} is valid candidate for type start_to_end as there is a SETTLEMENTDATE, " - "INTERVAL_DATETIME or TIMESTAMP column to filter on.".format( - table_name - ) - ) - - def test_all_no_duplication_between_batches(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "all": - print("Validating all type for table {}".format(table_name)) - if table_name in [ - "GENCONDATA", - "SPDCONNECTIONPOINTCONSTRAINT", - "SPDINTERCONNECTORCONSTRAINT", - "DUDETAILSUMMARY", - "LOSSMODEL", - "LOSSFACTORMODEL", - "MNSP_DAYOFFER", - "MNSP_PEROFFER", - "MNSP_INTERCONNECTOR", - "INTERCONNECTOR", - "INTERCONNECTORCONSTRAINT", - "DUDETAIL", - "MARKET_PRICE_THRESHOLDS", - "PARTICIPANT" - ]: - print( - "{} is known to contain duplicate entries and is exempted from this test, a finalise " - "data processing step is included in dynamic data fetch to clean up these duplicates.".format( - table_name - ) - ) - continue - start_test_window = defaults.nem_data_model_start_time - start_time = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - end_time = datetime.strptime("2018/01/01 00:00:00", "%Y/%m/%d %H:%M:%S") - start_search = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - data_tables = data_fetch_methods._dynamic_data_fetch_loop( - start_search=start_search, - start_time=start_time, - end_time=end_time, - table_name=table_name, - raw_data_location=defaults.raw_data_cache, - select_columns=defaults.table_primary_keys[table_name], - date_filter=None, - keep_csv=False, - ) - all_data = pd.concat(data_tables, sort=False) - contains_duplicates = all_data.duplicated().any() - self.assertEqual( - False, contains_duplicates, "table {}".format(table_name) - ) - print("Type valid, no duplicates found.") - - def test_all_no_duplication_between_batches_with_finalise_step(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "all": - print("Testing duplicate removal for table {}".format(table_name)) - start_test_window = defaults.nem_data_model_start_time - # start_test_window = '2018/01/01 00:00:00' - start_time = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - end_time = datetime.strptime("2018/01/01 00:00:00", "%Y/%m/%d %H:%M:%S") - start_search = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - data_tables = data_fetch_methods._dynamic_data_fetch_loop( - start_search=start_search, - start_time=start_time, - end_time=end_time, - table_name=table_name, - raw_data_location=defaults.raw_data_cache, - select_columns=defaults.table_primary_keys[table_name], - date_filter=None, - keep_csv=False, - ) - all_data = pd.concat(data_tables, sort=False) - all_data = query_wrappers.drop_duplicates_by_primary_key( - all_data, start_time, table_name - ) - contains_duplicates = all_data.duplicated().any() - self.assertEqual(False, contains_duplicates) - print("Type valid, no duplicates found.") - - def test_start_to_end_has_no_settlement_interval_or_timestamp_col(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "all": - has_settlement_date_col = ( - "SETTLEMENTDATE" in defaults.table_columns[table_name] - ) - has_interval_datetime_col = ( - "INTERVAL_DATETIME" in defaults.table_columns[table_name] - ) - has_interval_timestamp_col = ( - "TIMESTAMP" in defaults.table_columns[table_name] - ) - has_either = ( - has_interval_datetime_col - or has_settlement_date_col - or has_interval_timestamp_col - ) - self.assertEqual(False, has_either, "table {}".format(table_name)) - print( - "{} is valid candidate for type all as there is not a SETTLEMENTDATE, " - "INTERVAL_DATETIME or TIMESTAMP column to filter on".format( - table_name - ) - ) - - def test_last_contains_data_from_first(self): - for table_name in processing_info_maps.search_type.keys(): - if processing_info_maps.search_type[table_name] == "end": - start_test_window = defaults.nem_data_model_start_time - # start_test_window = '2018/01/01 00:00:00' - start_time = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - end_time = datetime.strptime("2018/01/01 00:00:00", "%Y/%m/%d %H:%M:%S") - start_search = datetime.strptime(start_test_window, "%Y/%m/%d %H:%M:%S") - select_columns = None - ( - _, - _, - select_columns, - _, - _, - ) = data_fetch_methods._set_up_dynamic_compilers( - table_name, start_time, end_time, select_columns - ) - data_tables = data_fetch_methods._dynamic_data_fetch_loop( - start_search=start_search, - start_time=start_time, - end_time=end_time, - table_name=table_name, - raw_data_location=defaults.raw_data_cache, - select_columns=select_columns, - date_filter=None, - keep_csv=False, - ) - first_data_table = data_tables[35].loc[ - :, defaults.table_primary_keys[table_name] - ] - last_data_table = data_tables[-1] - comp = pd.merge( - first_data_table, - last_data_table, - "left", - defaults.table_primary_keys[table_name], - ) - non_primary_col = [ - col - for col in defaults.table_columns[table_name] - if col not in defaults.table_primary_keys[table_name] - ][0] - missing_from_last = comp[comp[non_primary_col].isnull()] - self.assertEqual(False, missing_from_last.empty) + (y1, m1), (y2, m2) = pair + df1 = _read_mms_zip(_mms_zip_path(table, y1, m1)) + df2 = _read_mms_zip(_mms_zip_path(table, y2, m2)) + pk = _shared_pk_columns(table, df1, df2) + assert pk, f"{table}: no PK columns shared between {y1}-{m1:02d} and {y2}-{m2:02d} frames" + + combined = pd.concat([df1, df2]) + dupes = combined.duplicated(pk).sum() + assert dupes == 0, ( + f"{table}: concat of {y1}-{m1:02d} and {y2}-{m2:02d} has " + f"{dupes} PK duplicates on {pk} — either archives aren't " + f"disjoint (should be 'all'?) or the primary-key config is wrong" + ) + + +@pytest.mark.parametrize("table", _ALL_TABLES) +def test_all_dedup_collapses_cleanly(table): + """The core invariant for `all`-type tables: after fetching multiple + monthly archives and running `drop_duplicates_by_primary_key`, the + result must have no PK duplicates. This catches wrong `table_primary_keys` + configs (where the PK isn't actually unique) and classification + mismatches (where the dedup step fails to converge). Covers both + true snapshot tables like PARTICIPANT (where archives overlap heavily) + and change-log tables like GENCONDATA (where archives don't overlap + much but dedup still needs to produce clean output).""" + months = _fixtured_months(table) + if len(months) < 2: + pytest.skip(f"{table}: need 2 fixtured months") + (y1, m1), (y2, m2) = months[0], months[-1] + df1 = _read_mms_zip(_mms_zip_path(table, y1, m1)) + df2 = _read_mms_zip(_mms_zip_path(table, y2, m2)) + combined = pd.concat([df1, df2]) + pk = _shared_pk_columns(table, df1, df2) + assert pk, f"{table}: no PK columns shared between {y1}-{m1:02d} and {y2}-{m2:02d} frames" + + start_time = pd.Timestamp(f"{y1}-{m1:02d}-01") + deduped = drop_duplicates_by_primary_key(combined, start_time, table) + assert not deduped.duplicated(pk).any(), ( + f"{table}: drop_duplicates_by_primary_key left PK duplicates on {pk}" + ) + + +def test_participant_all_archives_do_overlap(): + """Canary for the `all`-type snapshot pattern. PARTICIPANT is a true + registration list — every registered participant appears in every + monthly archive with identical content. If concat shows zero PK + overlap across two distant months, either AEMO stopped publishing + snapshots or the PK config is wrong. We probe PARTICIPANT specifically + because other `all` tables are change-logs that don't overlap much + over a 2-month window.""" + df1 = _read_mms_zip(_mms_zip_path("PARTICIPANT", 2018, 5)) + df2 = _read_mms_zip(_mms_zip_path("PARTICIPANT", 2021, 5)) + pk = _shared_pk_columns("PARTICIPANT", df1, df2) + combined = pd.concat([df1, df2]) + dupes = combined.duplicated(pk).sum() + assert dupes > 0, ( + f"PARTICIPANT: concat of 2018-05 and 2021-05 has 0 PK duplicates on " + f"{pk} — snapshot pattern no longer holds, library's `all` assumption " + f"is wrong, or the PK definition is off" + ) + + +@pytest.mark.parametrize("table", _END_TABLES) +def test_end_archive_contains_historical_rows(table): + """`end` tables are classified such that only the end-time archive is + fetched. That only works if each archive is cumulative back to the + table's origin. Probe: the 2021-05 archive should contain rows with + effective dates well earlier than the archive month itself.""" + zip_path = _mms_zip_path(table, 2021, 5) + if zip_path is None: + pytest.skip(f"{table}: no 2021-05 fixture") + df = _read_mms_zip(zip_path) + date_col = defaults.primary_date_columns[table] + df[date_col] = pd.to_datetime(df[date_col], format="%Y/%m/%d %H:%M:%S") + min_date = df[date_col].min() + assert min_date < pd.Timestamp("2020-01-01"), ( + f"{table} 2021-05 archive min {date_col}={min_date} — expected " + f"historical rows from well before 2020 (cumulative semantics)" + ) \ No newline at end of file diff --git a/uv.lock b/uv.lock index c26c9d0..d929b62 100644 --- a/uv.lock +++ b/uv.lock @@ -1,18 +1,19 @@ version = 1 -requires-python = ">=3.9" +revision = 3 +requires-python = ">=3.10" resolution-markers = [ - "python_full_version < '3.11'", - "python_full_version == '3.11.*'", "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", ] [[package]] name = "altgraph" version = "0.17.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418 } +sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418, upload-time = "2023-09-25T09:04:52.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212 }, + { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212, upload-time = "2023-09-25T09:04:50.691Z" }, ] [[package]] @@ -22,119 +23,106 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181, upload-time = "2024-01-17T16:53:17.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925, upload-time = "2024-01-17T16:53:12.779Z" }, ] [[package]] name = "certifi" version = "2024.12.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010, upload-time = "2024-12-14T13:52:38.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927, upload-time = "2024-12-14T13:52:36.114Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload-time = "2024-12-24T18:09:43.671Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload-time = "2024-12-24T18:09:48.113Z" }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload-time = "2024-12-24T18:09:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload-time = "2024-12-24T18:09:52.078Z" }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload-time = "2024-12-24T18:09:54.575Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload-time = "2024-12-24T18:09:57.324Z" }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload-time = "2024-12-24T18:09:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload-time = "2024-12-24T18:10:02.357Z" }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload-time = "2024-12-24T18:10:03.678Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload-time = "2024-12-24T18:10:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload-time = "2024-12-24T18:10:08.848Z" }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload-time = "2024-12-24T18:10:10.044Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload-time = "2024-12-24T18:10:11.323Z" }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload-time = "2024-12-24T18:10:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload-time = "2024-12-24T18:10:18.369Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload-time = "2024-12-24T18:10:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload-time = "2024-12-24T18:10:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload-time = "2024-12-24T18:10:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload-time = "2024-12-24T18:10:24.802Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload-time = "2024-12-24T18:10:26.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload-time = "2024-12-24T18:10:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload-time = "2024-12-24T18:10:32.679Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload-time = "2024-12-24T18:10:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload-time = "2024-12-24T18:10:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "et-xmlfile" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] [[package]] @@ -144,36 +132,24 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyarrow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/e8/ee99f142f19d35588501943510f8217f9dd77184574b0c933c53218e0f19/feather-format-0.4.1.tar.gz", hash = "sha256:45f67e3745d394d4f160ca6d636bbfd4f8b68d01199dc1649b6e487d3e878903", size = 3151 } +sdist = { url = "https://files.pythonhosted.org/packages/67/e8/ee99f142f19d35588501943510f8217f9dd77184574b0c933c53218e0f19/feather-format-0.4.1.tar.gz", hash = "sha256:45f67e3745d394d4f160ca6d636bbfd4f8b68d01199dc1649b6e487d3e878903", size = 3151, upload-time = "2020-04-27T21:56:00.48Z" } [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "importlib-metadata" -version = "8.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] [[package]] @@ -183,14 +159,14 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "altgraph" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309 } +sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309, upload-time = "2023-09-25T09:10:16.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094 }, + { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, ] [[package]] name = "nemosis" -version = "4.0.0" +version = "3.8.1" source = { editable = "." } dependencies = [ { name = "beautifulsoup4" }, @@ -229,54 +205,150 @@ dev = [ [[package]] name = "numpy" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, - { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, - { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, - { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, - { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, - { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, - { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, - { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, - { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, - { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, - { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, - { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, - { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, - { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, - { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, - { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, - { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, - { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, - { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, - { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, - { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, - { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, - { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, - { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, - { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, - { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, - { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, - { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, - { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, - { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942 }, - { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512 }, - { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976 }, - { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494 }, - { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596 }, - { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099 }, - { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823 }, - { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424 }, - { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809 }, - { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314 }, - { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288 }, - { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793 }, - { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885 }, - { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784 }, +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.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] [[package]] @@ -286,18 +358,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "et-xmlfile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] @@ -305,130 +377,117 @@ name = "pandas" version = "2.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, - { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, - { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, - { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, - { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, - { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, - { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, - { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, - { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, - { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, - { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, - { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, - { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, - { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, - { url = "https://files.pythonhosted.org/packages/ca/8c/8848a4c9b8fdf5a534fe2077af948bf53cd713d77ffbcd7bd15710348fd7/pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39", size = 12595535 }, - { url = "https://files.pythonhosted.org/packages/9c/b9/5cead4f63b6d31bdefeb21a679bc5a7f4aaf262ca7e07e2bc1c341b68470/pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30", size = 11319822 }, - { url = "https://files.pythonhosted.org/packages/31/af/89e35619fb573366fa68dc26dad6ad2c08c17b8004aad6d98f1a31ce4bb3/pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c", size = 15625439 }, - { url = "https://files.pythonhosted.org/packages/3d/dd/bed19c2974296661493d7acc4407b1d2db4e2a482197df100f8f965b6225/pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c", size = 13068928 }, - { url = "https://files.pythonhosted.org/packages/31/a3/18508e10a31ea108d746c848b5a05c0711e0278fa0d6f1c52a8ec52b80a5/pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea", size = 16783266 }, - { url = "https://files.pythonhosted.org/packages/c4/a5/3429bd13d82bebc78f4d78c3945efedef63a7cd0c15c17b2eeb838d1121f/pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761", size = 14450871 }, - { url = "https://files.pythonhosted.org/packages/2f/49/5c30646e96c684570925b772eac4eb0a8cb0ca590fa978f56c5d3ae73ea1/pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e", size = 11618011 }, +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827, upload-time = "2024-09-20T13:08:42.347Z" }, + { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897, upload-time = "2024-09-20T13:08:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908, upload-time = "2024-09-20T18:37:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210, upload-time = "2024-09-20T13:08:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292, upload-time = "2024-09-20T19:01:54.443Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379, upload-time = "2024-09-20T13:08:50.882Z" }, + { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471, upload-time = "2024-09-20T13:08:53.332Z" }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, ] [[package]] name = "parameterized" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/49/00c0c0cc24ff4266025a53e41336b79adaa5a4ebfad214f433d623f9865e/parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1", size = 24351 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/49/00c0c0cc24ff4266025a53e41336b79adaa5a4ebfad214f433d623f9865e/parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1", size = 24351, upload-time = "2023-03-27T02:01:11.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size = 20475 }, + { url = "https://files.pythonhosted.org/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size = 20475, upload-time = "2023-03-27T02:01:09.31Z" }, ] [[package]] name = "pefile" version = "2023.2.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854 } +sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854, upload-time = "2023-02-07T12:23:55.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791 }, + { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pyarrow" version = "18.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620 }, - { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521 }, - { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905 }, - { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881 }, - { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517 }, - { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187 }, - { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314 }, - { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860 }, - { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076 }, - { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135 }, - { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195 }, - { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884 }, - { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877 }, - { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811 }, - { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, - { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, - { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, - { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, - { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, - { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, - { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, - { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, - { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, - { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, - { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, - { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, - { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, - { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, - { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, - { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, - { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, - { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, - { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, - { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, - { url = "https://files.pythonhosted.org/packages/fd/9b/60516e3876ec6f25b0909afa70f90a15de83b48c7c0d8042fac4e64c4411/pyarrow-18.1.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b331e477e40f07238adc7ba7469c36b908f07c89b95dd4bd3a0ec84a3d1e21e", size = 29543752 }, - { url = "https://files.pythonhosted.org/packages/14/a7/bd08b6f1a2bd2e71dc6bb0451fc1872607e44c83daf1ee63c82764a2d233/pyarrow-18.1.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c4dd0c9010a25ba03e198fe743b1cc03cd33c08190afff371749c52ccbbaf76", size = 30850753 }, - { url = "https://files.pythonhosted.org/packages/84/c9/62ef9c6281c0e5b4ee1afa9d7bd556e72e06da6706b7906c32c15e69b3d6/pyarrow-18.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f97b31b4c4e21ff58c6f330235ff893cc81e23da081b1a4b1c982075e0ed4e9", size = 39226870 }, - { url = "https://files.pythonhosted.org/packages/b2/99/a6e89e71655a38475e76b060777c8bf69c078b772bec3b7daf7361440f05/pyarrow-18.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a4813cb8ecf1809871fd2d64a8eff740a1bd3691bbe55f01a3cf6c5ec869754", size = 40139114 }, - { url = "https://files.pythonhosted.org/packages/64/a9/06d79923890682e4fe7a16524abee307407008a413115354aaf3226b8410/pyarrow-18.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:05a5636ec3eb5cc2a36c6edb534a38ef57b2ab127292a716d00eabb887835f1e", size = 38639231 }, - { url = "https://files.pythonhosted.org/packages/3b/8c/4c3ed19026a00740b81fe1c87f3ff235b2763a0a1ddf5711a9d026b775ce/pyarrow-18.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:73eeed32e724ea3568bb06161cad5fa7751e45bc2228e33dcb10c614044165c7", size = 40070949 }, - { url = "https://files.pythonhosted.org/packages/87/d8/94161a7ca5c55199484e926165e9e33f318ea1d1b0d7cdbcbc3652b933ec/pyarrow-18.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:a1880dd6772b685e803011a6b43a230c23b566859a6e0c9a276c1e0faf4f4052", size = 25301373 }, +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671, upload-time = "2024-11-26T02:01:48.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620, upload-time = "2024-11-26T01:58:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521, upload-time = "2024-11-26T01:58:34.607Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905, upload-time = "2024-11-26T01:58:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881, upload-time = "2024-11-26T01:58:45.561Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517, upload-time = "2024-11-26T01:58:50.922Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187, upload-time = "2024-11-26T01:58:56.848Z" }, + { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314, upload-time = "2024-11-26T01:59:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860, upload-time = "2024-11-26T01:59:06.94Z" }, + { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076, upload-time = "2024-11-26T01:59:11.475Z" }, + { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135, upload-time = "2024-11-26T01:59:16.045Z" }, + { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195, upload-time = "2024-11-26T01:59:21.267Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884, upload-time = "2024-11-26T01:59:26.672Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877, upload-time = "2024-11-26T01:59:31.926Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811, upload-time = "2024-11-26T01:59:35.669Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620, upload-time = "2024-11-26T01:59:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494, upload-time = "2024-11-26T01:59:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624, upload-time = "2024-11-26T01:59:49.189Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341, upload-time = "2024-11-26T01:59:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629, upload-time = "2024-11-26T01:59:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661, upload-time = "2024-11-26T02:00:04.55Z" }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330, upload-time = "2024-11-26T02:00:09.576Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406, upload-time = "2024-11-26T02:00:14.469Z" }, + { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095, upload-time = "2024-11-26T02:00:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527, upload-time = "2024-11-26T02:00:24.085Z" }, + { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443, upload-time = "2024-11-26T02:00:29.483Z" }, + { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750, upload-time = "2024-11-26T02:00:34.069Z" }, + { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690, upload-time = "2024-11-26T02:00:39.603Z" }, + { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054, upload-time = "2024-11-26T02:00:43.611Z" }, + { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542, upload-time = "2024-11-26T02:00:48.094Z" }, + { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412, upload-time = "2024-11-26T02:00:52.458Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106, upload-time = "2024-11-26T02:00:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940, upload-time = "2024-11-26T02:01:02.31Z" }, + { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177, upload-time = "2024-11-26T02:01:07.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567, upload-time = "2024-11-26T02:01:12.931Z" }, ] [[package]] @@ -437,7 +496,6 @@ version = "6.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "altgraph" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "macholib", marker = "sys_platform == 'darwin'" }, { name = "packaging" }, { name = "pefile", marker = "sys_platform == 'win32'" }, @@ -445,19 +503,19 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/d4/54f5f5c73b803e6256ea97ffc6ba8a305d9a5f57f85f9b00b282512bf18a/pyinstaller-6.11.1.tar.gz", hash = "sha256:491dfb4d9d5d1d9650d9507daec1ff6829527a254d8e396badd60a0affcb72ef", size = 4249772 } +sdist = { url = "https://files.pythonhosted.org/packages/55/d4/54f5f5c73b803e6256ea97ffc6ba8a305d9a5f57f85f9b00b282512bf18a/pyinstaller-6.11.1.tar.gz", hash = "sha256:491dfb4d9d5d1d9650d9507daec1ff6829527a254d8e396badd60a0affcb72ef", size = 4249772, upload-time = "2024-11-10T17:01:25.415Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/15/b0f1c0985ee32fcd2f6ad9a486ef94e4db3fef9af025a3655e76cb708009/pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03", size = 991780 }, - { url = "https://files.pythonhosted.org/packages/fd/0f/9f54cb18abe2b1d89051bc9214c0cb40d7b5f4049c151c315dacc067f4a2/pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4", size = 711739 }, - { url = "https://files.pythonhosted.org/packages/32/f7/79d10830780eff8339bfa793eece1df4b2459e35a712fc81983e8536cc29/pyinstaller-6.11.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ddc0fddd75f07f7e423da1f0822e389a42af011f9589e0269b87e0d89aa48c1f", size = 714053 }, - { url = "https://files.pythonhosted.org/packages/25/f7/9961ef02cdbd2dbb1b1a215292656bd0ea72a83aafd8fb6373513849711e/pyinstaller-6.11.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d6475559c4939f0735122989611d7f739ed3bf02f666ce31022928f7a7e4fda", size = 719133 }, - { url = "https://files.pythonhosted.org/packages/6f/4d/7f854842a1ce798de762a0b0bc5d5a4fc26ad06164a98575dc3c54abed1f/pyinstaller-6.11.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e21c7806e34f40181e7606926a14579f848bfb1dc52cbca7eea66eccccbfe977", size = 709591 }, - { url = "https://files.pythonhosted.org/packages/7f/e0/00d29fc90c3ba50620c61554e26ebb4d764569507be7cd1c8794aa696f9a/pyinstaller-6.11.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32c742a24fe65d0702958fadf4040f76de85859c26bec0008766e5dbabc5b68f", size = 710068 }, - { url = "https://files.pythonhosted.org/packages/3e/57/d14b44a69f068d2caaee49d15e45f9fa0f37c6a2d2ad778c953c1722a1ca/pyinstaller-6.11.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:208c0ef6dab0837a0a273ea32d1a3619a208e3d1fe3fec3785eea71a77fd00ce", size = 714439 }, - { url = "https://files.pythonhosted.org/packages/88/01/256824bb57ca208099c86c2fb289f888ca7732580e91ced48fa14e5903b2/pyinstaller-6.11.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ad84abf465bcda363c1d54eafa76745d77b6a8a713778348377dc98d12a452f7", size = 710457 }, - { url = "https://files.pythonhosted.org/packages/7c/f0/98c9138f5f0ff17462f1ad6d712dcfa643b9a283d6238d464d8145bc139d/pyinstaller-6.11.1-py3-none-win32.whl", hash = "sha256:2e8365276c5131c9bef98e358fbc305e4022db8bedc9df479629d6414021956a", size = 1280261 }, - { url = "https://files.pythonhosted.org/packages/7d/08/f43080614b3e8bce481d4dfd580e579497c7dcdaf87656d9d2ad912e5796/pyinstaller-6.11.1-py3-none-win_amd64.whl", hash = "sha256:7ac83c0dc0e04357dab98c487e74ad2adb30e7eb186b58157a8faf46f1fa796f", size = 1340482 }, - { url = "https://files.pythonhosted.org/packages/ed/56/953c6594cb66e249563854c9cc04ac5a055c6c99d1614298feeaeaa9b87e/pyinstaller-6.11.1-py3-none-win_arm64.whl", hash = "sha256:35e6b8077d240600bb309ed68bb0b1453fd2b7ab740b66d000db7abae6244423", size = 1267519 }, + { url = "https://files.pythonhosted.org/packages/96/15/b0f1c0985ee32fcd2f6ad9a486ef94e4db3fef9af025a3655e76cb708009/pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03", size = 991780, upload-time = "2024-11-10T17:00:17.242Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0f/9f54cb18abe2b1d89051bc9214c0cb40d7b5f4049c151c315dacc067f4a2/pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4", size = 711739, upload-time = "2024-11-10T17:00:21.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/f7/79d10830780eff8339bfa793eece1df4b2459e35a712fc81983e8536cc29/pyinstaller-6.11.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ddc0fddd75f07f7e423da1f0822e389a42af011f9589e0269b87e0d89aa48c1f", size = 714053, upload-time = "2024-11-10T17:00:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/25/f7/9961ef02cdbd2dbb1b1a215292656bd0ea72a83aafd8fb6373513849711e/pyinstaller-6.11.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d6475559c4939f0735122989611d7f739ed3bf02f666ce31022928f7a7e4fda", size = 719133, upload-time = "2024-11-10T17:00:30.342Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4d/7f854842a1ce798de762a0b0bc5d5a4fc26ad06164a98575dc3c54abed1f/pyinstaller-6.11.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e21c7806e34f40181e7606926a14579f848bfb1dc52cbca7eea66eccccbfe977", size = 709591, upload-time = "2024-11-10T17:00:34.932Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e0/00d29fc90c3ba50620c61554e26ebb4d764569507be7cd1c8794aa696f9a/pyinstaller-6.11.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32c742a24fe65d0702958fadf4040f76de85859c26bec0008766e5dbabc5b68f", size = 710068, upload-time = "2024-11-10T17:00:39.655Z" }, + { url = "https://files.pythonhosted.org/packages/3e/57/d14b44a69f068d2caaee49d15e45f9fa0f37c6a2d2ad778c953c1722a1ca/pyinstaller-6.11.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:208c0ef6dab0837a0a273ea32d1a3619a208e3d1fe3fec3785eea71a77fd00ce", size = 714439, upload-time = "2024-11-10T17:00:43.838Z" }, + { url = "https://files.pythonhosted.org/packages/88/01/256824bb57ca208099c86c2fb289f888ca7732580e91ced48fa14e5903b2/pyinstaller-6.11.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ad84abf465bcda363c1d54eafa76745d77b6a8a713778348377dc98d12a452f7", size = 710457, upload-time = "2024-11-10T17:00:47.89Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f0/98c9138f5f0ff17462f1ad6d712dcfa643b9a283d6238d464d8145bc139d/pyinstaller-6.11.1-py3-none-win32.whl", hash = "sha256:2e8365276c5131c9bef98e358fbc305e4022db8bedc9df479629d6414021956a", size = 1280261, upload-time = "2024-11-10T17:00:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/08/f43080614b3e8bce481d4dfd580e579497c7dcdaf87656d9d2ad912e5796/pyinstaller-6.11.1-py3-none-win_amd64.whl", hash = "sha256:7ac83c0dc0e04357dab98c487e74ad2adb30e7eb186b58157a8faf46f1fa796f", size = 1340482, upload-time = "2024-11-10T17:01:01.62Z" }, + { url = "https://files.pythonhosted.org/packages/ed/56/953c6594cb66e249563854c9cc04ac5a055c6c99d1614298feeaeaa9b87e/pyinstaller-6.11.1-py3-none-win_arm64.whl", hash = "sha256:35e6b8077d240600bb309ed68bb0b1453fd2b7ab740b66d000db7abae6244423", size = 1267519, upload-time = "2024-11-10T17:01:08.525Z" }, ] [[package]] @@ -465,13 +523,12 @@ name = "pyinstaller-hooks-contrib" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "packaging" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/1b/dc256d42f4217db99b50d6d32dbbf841a41b9615506cde77d2345d94f4a5/pyinstaller_hooks_contrib-2025.1.tar.gz", hash = "sha256:130818f9e9a0a7f2261f1fd66054966a3a50c99d000981c5d1db11d3ad0c6ab2", size = 147043 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/1b/dc256d42f4217db99b50d6d32dbbf841a41b9615506cde77d2345d94f4a5/pyinstaller_hooks_contrib-2025.1.tar.gz", hash = "sha256:130818f9e9a0a7f2261f1fd66054966a3a50c99d000981c5d1db11d3ad0c6ab2", size = 147043, upload-time = "2025-01-31T21:51:40.131Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/48/833d67a585275e395f351e5787b4b7a8d462d87bca22a8c038f6ffdc2b3c/pyinstaller_hooks_contrib-2025.1-py3-none-any.whl", hash = "sha256:d3c799470cbc0bda60dcc8e6b4ab976777532b77621337f2037f558905e3a8e9", size = 346409 }, + { url = "https://files.pythonhosted.org/packages/b7/48/833d67a585275e395f351e5787b4b7a8d462d87bca22a8c038f6ffdc2b3c/pyinstaller_hooks_contrib-2025.1-py3-none-any.whl", hash = "sha256:d3c799470cbc0bda60dcc8e6b4ab976777532b77621337f2037f558905e3a8e9", size = 346409, upload-time = "2025-01-31T21:51:37.45Z" }, ] [[package]] @@ -486,9 +543,9 @@ dependencies = [ { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919, upload-time = "2024-12-01T12:54:25.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083, upload-time = "2024-12-01T12:54:19.735Z" }, ] [[package]] @@ -498,27 +555,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "pytz" version = "2024.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692, upload-time = "2024-09-11T02:24:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002, upload-time = "2024-09-11T02:24:45.8Z" }, ] [[package]] name = "pywin32-ctypes" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, ] [[package]] @@ -531,109 +588,100 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] name = "setuptools" version = "75.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } +sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222, upload-time = "2025-01-08T18:28:23.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, + { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782, upload-time = "2025-01-08T18:28:20.912Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "soupsieve" version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569, upload-time = "2024-08-13T13:39:12.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186, upload-time = "2024-08-13T13:39:10.986Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "tzdata" version = "2024.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282, upload-time = "2024-09-23T18:56:46.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586, upload-time = "2024-09-23T18:56:45.478Z" }, ] [[package]] name = "urllib3" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" }, ] [[package]] name = "xlrd" version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531 }, -] - -[[package]] -name = "zipp" -version = "3.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259, upload-time = "2020-12-11T10:14:22.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, + { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531, upload-time = "2020-12-11T10:14:20.877Z" }, ]