diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81d639f..a5ae64f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -23,7 +23,7 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: Install dependencies - run: uv sync --dev + run: uv sync --group dev --all-extras - name: Run tests run: uv run pytest tests/ -v --tb=short @@ -40,7 +40,7 @@ jobs: run: uv python install 3.12 - name: Install dependencies - run: uv sync --dev + run: uv sync --group dev --all-extras - name: Run ruff check run: uv run ruff check . @@ -65,6 +65,14 @@ jobs: - name: Check build artifacts run: uvx twine check dist/* + - name: Smoke test the core wheel + run: | + uv venv /tmp/tablint-smoke + uv pip install --python /tmp/tablint-smoke/bin/python dist/*.whl + status=0 + /tmp/tablint-smoke/bin/tablint scan datasets/train.csv --target Survived --preset gate --fail-on critical --json || status=$? + test "$status" -eq 2 + web-check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b452be0..0b87d2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,10 +25,14 @@ jobs: - name: Build package run: uv build + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/* + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - # with: - # repository-url: https://upload.pypi.org/legacy/ # Optional: defaults to PyPI github-release: runs-on: ubuntu-latest @@ -37,6 +41,11 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: diff --git a/.gitignore b/.gitignore index 01a5eda..9199f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,9 @@ build/ dist/ hashprep-dev.json +tablecheck-dev.json +datalint-dev.json +tablint-dev.json todo.md test.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b95fc1..a3c47d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.0b1] - 2026-10-23 + +Project renamed from `hashprep` to `tablint`. This beta ships the training-gate behaviour previously developed as hashprep 0.2.0. + +### Changed +- Distribution name, import package, and CLI are `tablint`. +- `HashPrepConfig` is now `TablintConfig`. +- Report filenames use the `_tablint_report` suffix. + +## [0.2.0] - 2026-10-20 + +### Breaking +- `critical` means a training blocker. Non-normality is always a warning. Drift is critical only when the shift is large, not merely when the p-value is small. +- `DatasetAnalyzer` and `tablint scan` / `details` default to the `gate` preset. `tablint report` defaults to `profile`. Pass `preset="profile"` or `--preset profile` for the previous full catalogue. +- Sampling is off unless `--sample-size` or `auto_sample=True` is set. +- `scan --json` now uses `schema_version`, `critical_count`, `warning_count`, `total_issues`, `issues`, and `sampling_info`. `details --json` prints the same object. +- F-statistic leakage thresholds are ignored. Leakage uses correlation, bias-corrected CramΓ©r's V, or correlation ratio. +- Plots, HTML, and PDF require `pip install "tablint[reports]"`. Parquet requires `pip install "tablint[parquet]"`. + +### Added +- Column roles (`target`, `identifier`, `boolean`, `categorical`, `numeric`, `datetime`, `text`) shared by every check. +- `--preset`, `--fail-on`, and Parquet input. +- `statistic` and `effect_size` on each issue. +- Generated code escapes column names. Sklearn steps with the same transformation are grouped. Outlier clipping is a real pre-pipeline function. + ## [0.1.0] - 2026-05-22 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41d8778..a3bdc5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to HashPrep +# Contributing to Tablint -First off – thank you for considering contributing to HashPrep πŸ’œ +First off – thank you for considering contributing to Tablint πŸ’œ Your contributions help make this platform more useful for the ML community! ## How to Contribute @@ -12,8 +12,8 @@ For new contributors, we suggest starting with Easy tasks. ### Fork and clone the repository ```bash -git clone https://github.com/cachevector/hashprep.git -cd hashprep +git clone https://github.com/cachevector/tablint.git +cd tablint ``` ### Create a virtual environment @@ -26,13 +26,13 @@ source .venv/bin/activate ### Install in development mode ```bash -uv sync --extra dev +uv sync --group dev --all-extras ``` - + - +

@@ -27,15 +27,15 @@ > [!NOTE] -> HashPrep is now in its first stable release (v0.1.0). Core features are fully tested with CI. +> Tablint 0.1.0b1 is the first public beta. `critical` means "do not train this table as-is." ## Overview -**HashPrep** is a Python library for intelligent dataset profiling and debugging that acts as a comprehensive pre-training quality assurance tool for machine learning projects. +**Tablint** is a Python library for intelligent dataset profiling and debugging that acts as a comprehensive pre-training quality assurance tool for machine learning projects. Think of it as **"Pandas Profiling + PyLint for datasets"**, designed specifically for machine learning workflows. It catches critical dataset issues before they derail your ML pipeline, explains the problems, and suggests context-aware fixes. -If you want, HashPrep can even apply those fixes for you automatically. +If you want, Tablint can even apply those fixes for you automatically. --- @@ -48,7 +48,7 @@ Key features include: - **ML-Specific Checks**: Identify data leakage, dataset drift, class imbalance, and high-cardinality features. - **Automated Preparation**: Get suggestions for encoding, imputation, scaling, and transformations. - **Rich Reporting**: Generate statistical summaries and exportable reports (HTML/PDF/Markdown/JSON) with embedded visualizations. -- **Production-Ready Pipelines**: Output reproducible cleaning and preprocessing code (`fixes.py`) that integrates seamlessly with ML workflows. +- **Reviewed preparation code**: Write a pandas cleaning script and a scikit-learn pipeline. Fit the pipeline on the training split. The pandas script computes statistics from the frame you pass in. - **Modern Themes**: Choose between "Minimal" (professional) and "Neubrutalism" (bold) report styles. --- @@ -57,7 +57,7 @@ Key features include: ### Using pip ```bash -pip install hashprep +pip install tablint ``` ### Using uv (recommended) @@ -65,29 +65,29 @@ pip install hashprep # Install uv if you haven't already curl -LsSf https://astral.sh/uv/install.sh | sh -# Install hashprep -uv pip install hashprep +# Install tablint +uv pip install tablint # Or for development from source -git clone https://github.com/cachevector/hashprep.git -cd hashprep +git clone https://github.com/cachevector/tablint.git +cd tablint uv sync ``` -After installation, the `hashprep` command will be available directly in your terminal. +After installation, the `tablint` command will be available directly in your terminal. --- ## Usage -HashPrep can be used both as a **command-line tool** and as a **Python library**. +Tablint can be used both as a **command-line tool** and as a **Python library**. ### CLI Usage #### 1. Quick Scan Get a quick summary of critical issues in your terminal. ```bash -hashprep scan dataset.csv +tablint scan dataset.csv ``` **Options:** @@ -97,36 +97,38 @@ hashprep scan dataset.csv - `--target COLUMN`: Specify target column for ML-specific checks - `--checks CHECKS`: Run specific checks (comma-separated) - `--comparison FILE`: Compare with another dataset for drift detection -- `--sample-size N`: Limit analysis to N rows -- `--no-sample`: Disable automatic sampling +- `--preset {gate,profile}`: `gate` is the default and is the training-blocker set. `profile` runs every check. +- `--fail-on {critical,warning}`: Exit 2 when that severity is present. Exit 0 means the bar was not hit. Exit 1 is a usage or runtime error. +- `--sample-size N`: Analyze at most N rows. Sampling stays off unless this is set. +- `--no-sample`: Disable sampling even when `--sample-size` is set. - `--config FILE`: Load thresholds from a YAML/TOML/JSON config file **Example:** ```bash -# Scan with target column and specific checks -hashprep scan train.csv --target Survived --checks outliers,high_missing_values,class_imbalance +# Block a training job when the table is not trainable +tablint scan train.csv --target Survived --preset gate --fail-on critical # Quick scan with JSON output -hashprep scan dataset.csv --json --quiet +tablint scan dataset.csv --json --quiet ``` #### 2. Detailed Analysis Get comprehensive details about all detected issues. ```bash -hashprep details dataset.csv +tablint details dataset.csv ``` **Options:** Same as `scan` command (including `--config`) **Example:** ```bash -hashprep details train.csv --target Survived +tablint details train.csv --target Survived ``` #### 3. Generate Reports Generate comprehensive reports in multiple formats with visualizations. ```bash -hashprep report dataset.csv --format html --theme minimal +tablint report dataset.csv --format html --theme minimal ``` **Options:** @@ -139,43 +141,44 @@ hashprep report dataset.csv --format html --theme minimal - `--target COLUMN`: Specify target column - `--checks CHECKS`: Run specific checks - `--comparison FILE`: Compare with another dataset for drift detection -- `--sample-size N`: Limit analysis to N rows -- `--no-sample`: Disable automatic sampling +- `--preset {gate,profile}`: `profile` is the default for reports. `gate` runs only training blockers. +- `--sample-size N`: Analyze at most N rows. Sampling stays off unless this is set. +- `--no-sample`: Disable sampling even when `--sample-size` is set. - `--config FILE`: Load thresholds from a YAML/TOML/JSON config file **Examples:** ```bash # Generate HTML report with minimal theme -hashprep report dataset.csv --format html --theme minimal --full +tablint report dataset.csv --format html --theme minimal --full # Generate PDF report without visualizations (faster) -hashprep report dataset.csv --format pdf --no-visualizations +tablint report dataset.csv --format pdf --no-visualizations # Generate report with automatic fix scripts -hashprep report dataset.csv --with-code +tablint report dataset.csv --with-code # Generate report with custom output path -hashprep report dataset.csv --format html --output my_reports/analysis.html +tablint report dataset.csv --format html --output my_reports/analysis.html # This creates: -# - dataset_hashprep_report.md (or .html/.pdf/.json) -# - dataset_hashprep_report_fixes.py (pandas script) -# - dataset_hashprep_report_pipeline.py (sklearn pipeline) +# - dataset_tablint_report.md (or .html/.pdf/.json) +# - dataset_tablint_report_fixes.py (pandas script) +# - dataset_tablint_report_pipeline.py (sklearn pipeline) # Compare two datasets for drift detection -hashprep report train.csv --comparison test.csv --format html +tablint report train.csv --comparison test.csv --format html ``` #### 4. List Available Checks -Discover all data quality checks that HashPrep can perform. +Discover all data quality checks that Tablint can perform. ```bash -hashprep checks +tablint checks ``` #### 5. Version -Check HashPrep version. +Check Tablint version. ```bash -hashprep version +tablint version ``` #### Available Checks @@ -218,7 +221,7 @@ hashprep version #### Basic Analysis ```python import pandas as pd -from hashprep import DatasetAnalyzer +from tablint import DatasetAnalyzer # Load your dataset df = pd.read_csv("dataset.csv") @@ -283,7 +286,7 @@ summary = analyzer.analyze() #### Generate Reports Programmatically ```python -from hashprep.reports import generate_report +from tablint.reports import generate_report # Analyze dataset analyzer = DatasetAnalyzer(df, include_plots=True) @@ -325,10 +328,10 @@ generate_report( #### Generate Fix Scripts ```python -from hashprep.checks.core import Issue -from hashprep.preparers.codegen import CodeGenerator -from hashprep.preparers.pipeline_builder import PipelineBuilder -from hashprep.preparers.suggestions import SuggestionProvider +from tablint.checks.core import Issue +from tablint.preparers.codegen import CodeGenerator +from tablint.preparers.pipeline_builder import PipelineBuilder +from tablint.preparers.suggestions import SuggestionProvider # After running analysis analyzer = DatasetAnalyzer(df, target_col='target') @@ -361,17 +364,17 @@ with open('pipeline.py', 'w') as f: #### Load Config from File ```python -from hashprep.utils.config_loader import load_config -from hashprep import DatasetAnalyzer +from tablint.utils.config_loader import load_config +from tablint import DatasetAnalyzer # Load thresholds from YAML, TOML, or JSON -config = load_config("hashprep.yaml") # or .toml / .json +config = load_config("tablint.yaml") # or .toml / .json analyzer = DatasetAnalyzer(df, config=config) summary = analyzer.analyze() ``` -Example `hashprep.yaml`: +Example `tablint.yaml`: ```yaml missing_values: warning: 0.3 @@ -386,7 +389,7 @@ Only the keys you specify are overridden; all others fall back to defaults. #### Custom Sampling ```python -from hashprep.utils.sampling import SamplingConfig +from tablint.utils.sampling import SamplingConfig # Configure sampling for large datasets sampling_config = SamplingConfig(max_rows=10000) @@ -406,6 +409,35 @@ if 'sampling_info' in summary: --- +## Production gate + +`DatasetAnalyzer` defaults to the `gate` preset. `tablint report` defaults to `profile`. + +`critical` is reserved for defects that should stop a training job: an empty table, an all-null column, missingness at or above the critical threshold, infinite values, duplicate rows above the critical ratio, a column identical or nearly identical to the target, and drift whose effect size is large. Skew, non-normality, ordinary correlation with the target, and zero-inflation of a binary target are warnings, and most of them run only in the `profile` preset. + +Stable keys in `analyze()` and in `scan --json` / `details --json`: + +- `schema_version` (`"0.2"`), `critical_count`, `warning_count`, `total_issues` +- `issues[]`: `category`, `severity`, `column`, `description`, `impact_score`, `quick_fix`, `statistic`, `effect_size` +- `sampling_info` + +`summaries` and plots are best-effort and can change without a major version. + +```bash +tablint scan train.csv --target Survived --preset gate --fail-on critical --json +``` + +Exit 2 when `--fail-on` matches. A job that only checks the exit code should pass `--fail-on critical`. + +### Limitations + +- Input is CSV, or Parquet when `tablint[parquet]` is installed. HTML, PDF, and plots need `tablint[reports]`. +- Leakage means near-deterministic association with the target, not a proof that a column was built from the label. +- Sampling is off unless you pass `--sample-size` or `auto_sample=True`. Checks then describe the analyzed frame, which is the full table by default. +- Generated pandas code learns fill values from the frame you pass. Pass the training split, or fit the generated sklearn pipeline on that split. + +--- + ## License This project is licensed under the [**MIT License**](./LICENSE). @@ -414,7 +446,7 @@ This project is licensed under the [**MIT License**](./LICENSE). ## Contributing -We welcome contributions from the community to make HashPrep better! +We welcome contributions from the community to make Tablint better! Before you get started, please: diff --git a/RELEASE_TODO.md b/RELEASE_TODO.md index 5c3bb49..b0620ea 100644 --- a/RELEASE_TODO.md +++ b/RELEASE_TODO.md @@ -1,10 +1,10 @@ -# HashPrep Official Release TODO +# Tablint Official Release TODO ## Release Scope - [x] Decide the official version target, likely `0.1.0` unless the release should signal `1.0.0`. - [x] Freeze public API expectations for `DatasetAnalyzer`. -- [x] Freeze public API expectations for `HashPrepConfig`. +- [x] Freeze public API expectations for `TablintConfig`. - [x] Freeze public API expectations for `load_config`. - [x] Freeze public API expectations for `generate_report`. - [x] Freeze CLI behavior for `scan`, `details`, `report`, and `version`. @@ -65,11 +65,11 @@ ### Strongly Recommended -- [x] Add an `--output` option to `hashprep report`. -- [x] Allow `hashprep report data.csv --format html --output reports/data.html`. -- [x] Add machine-readable JSON output for `hashprep details`. -- [x] Add check discovery through a command such as `hashprep checks`. -- [x] Alternatively add check discovery through an option such as `hashprep scan --list-checks`. +- [x] Add an `--output` option to `tablint report`. +- [x] Allow `tablint report data.csv --format html --output reports/data.html`. +- [x] Add machine-readable JSON output for `tablint details`. +- [x] Add check discovery through a command such as `tablint checks`. +- [x] Alternatively add check discovery through an option such as `tablint scan --list-checks`. - [x] Document why issues are classified as `critical` versus `warning`. - [x] Review whether PDF/reporting dependencies should move to optional extras in a future release. - [x] Document any dependency-extra plan if it is deferred. @@ -84,7 +84,7 @@ ## Packaging -- [x] Update `hashprep/__init__.py` from `0.1.0b3` to the official release version. +- [x] Update `tablint/__init__.py` from `0.1.0b3` to the official release version. - [x] Update the beta note in `README.md`. - [x] Update the beta support table in `SECURITY.md`. - [x] Add or verify PyPI classifiers in `pyproject.toml`. @@ -96,10 +96,10 @@ - [x] Build wheel distribution. - [x] Inspect built package artifacts. - [x] Install the built wheel in a clean environment. -- [x] Smoke test `import hashprep` from the built wheel. -- [x] Smoke test `hashprep version` from the built wheel. -- [x] Smoke test `hashprep scan datasets/train.csv` from the built wheel. -- [x] Smoke test `hashprep report datasets/train.csv --format html` from the built wheel. +- [x] Smoke test `import tablint` from the built wheel. +- [x] Smoke test `tablint version` from the built wheel. +- [x] Smoke test `tablint scan datasets/train.csv` from the built wheel. +- [x] Smoke test `tablint report datasets/train.csv --format html` from the built wheel. - [x] Confirm `MANIFEST.in` excludes dev/demo files intentionally. - [x] Confirm `MANIFEST.in` includes all runtime files needed by reports and templates. @@ -165,15 +165,15 @@ - [x] Tag the release, for example `v0.1.0`. - [x] Publish to PyPI. - [x] Create GitHub release with release notes. -- [x] Verify public install with `pip install hashprep`. -- [x] Verify public CLI with `hashprep version`. +- [x] Verify public install with `pip install tablint`. +- [x] Verify public CLI with `tablint version`. - [x] Announce the release as the first stable release after alpha and beta. ## Known Immediate Gaps - [x] Version is still `0.1.0b3`. - [x] `README.md` still says beta. -- [x] Website hero still says beta and shows `hashprep-0.1.0b3`. +- [x] Website hero still says beta and shows `tablint-0.1.0b3`. - [x] `SECURITY.md` only describes beta support. - [x] `CHANGELOG.md` is not present. - [x] CI does not currently build or check publish artifacts. diff --git a/SECURITY.md b/SECURITY.md index 4255a0d..3c900f6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,23 +2,21 @@ ## Supported Versions -hashprep has reached a stable `0.1.0` release. Only the latest minor release is supported for security updates. +Only the latest minor release is supported for security updates. | Version | Supported | | ---------- | ------------------ | -| `0.1.0` | :white_check_mark: | -| `< 0.1.0` | :x: | - -Once hashprep reaches a stable `0.1.0` release, this table will be updated to reflect supported minor versions. +| `0.1.0b1` | :white_check_mark: | +| `< 0.1.0b1`| :x: | ## Reporting a Vulnerability -If you believe you have found a security vulnerability in hashprep (the Python package, the CLI, or the documentation website under `web/`), **please do not open a public GitHub issue**. +If you believe you have found a security vulnerability in tablint (the Python package, the CLI, or the documentation website under `web/`), **please do not open a public GitHub issue**. Instead, report it privately through one of the following channels: -- **GitHub Private Vulnerability Reporting** β€” preferred. Open a report at . -- **Email** β€” `aftaab@aftaab.xyz` with the subject line `hashprep security report`. +- **GitHub Private Vulnerability Reporting** β€” preferred. Open a report at . +- **Email** β€” `aftaab@aftaab.xyz` with the subject line `tablint security report`. Please include as much of the following as you can: @@ -37,12 +35,12 @@ Please include as much of the following as you can: In scope: -- The `hashprep` Python package and CLI. +- The `tablint` Python package and CLI. - The documentation site in `web/`. - Build, packaging, and release tooling in this repository. Out of scope: -- Vulnerabilities in third-party dependencies β€” please report those upstream. If a dependency issue affects hashprep users, we still appreciate a heads-up so we can bump the pinned version. +- Vulnerabilities in third-party dependencies β€” please report those upstream. If a dependency issue affects tablint users, we still appreciate a heads-up so we can bump the pinned version. - Issues that require physical access to a user's machine or already-compromised environments. - Denial of service caused by passing intentionally malformed datasets far outside the documented usage (e.g., multi-TB crafted inputs on a laptop). diff --git a/docs/assets/hashprep-dark.svg b/docs/assets/hashprep-dark.svg deleted file mode 100644 index 01c531e..0000000 --- a/docs/assets/hashprep-dark.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/docs/assets/hashprep-light-bg.svg b/docs/assets/hashprep-light-bg.svg deleted file mode 100644 index e254a13..0000000 --- a/docs/assets/hashprep-light-bg.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/docs/assets/hashprep-light.svg b/docs/assets/hashprep-light.svg deleted file mode 100644 index 4ac37b0..0000000 --- a/docs/assets/hashprep-light.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/docs/assets/hashprep-wobg.svg b/docs/assets/hashprep-wobg.svg deleted file mode 100644 index 89cbec1..0000000 --- a/docs/assets/hashprep-wobg.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - diff --git a/docs/assets/tablint-dark.svg b/docs/assets/tablint-dark.svg new file mode 100644 index 0000000..0c21059 --- /dev/null +++ b/docs/assets/tablint-dark.svg @@ -0,0 +1,18 @@ + + Tablint logo: a dataset table inspected with a magnifying glass + + + + + + + + + + + + + + + + diff --git a/docs/assets/tablint-light-bg.svg b/docs/assets/tablint-light-bg.svg new file mode 100644 index 0000000..95c27c7 --- /dev/null +++ b/docs/assets/tablint-light-bg.svg @@ -0,0 +1,18 @@ + + Tablint logo: a dataset table inspected with a magnifying glass + + + + + + + + + + + + + + + + diff --git a/docs/assets/tablint-light.svg b/docs/assets/tablint-light.svg new file mode 100644 index 0000000..8efb316 --- /dev/null +++ b/docs/assets/tablint-light.svg @@ -0,0 +1,18 @@ + + Tablint logo: a dataset table inspected with a magnifying glass + + + + + + + + + + + + + + + + diff --git a/docs/assets/tablint-wobg.svg b/docs/assets/tablint-wobg.svg new file mode 100644 index 0000000..b4527f5 --- /dev/null +++ b/docs/assets/tablint-wobg.svg @@ -0,0 +1,17 @@ + + Tablint logo: a dataset table inspected with a magnifying glass + + + + + + + + + + + + + + + diff --git a/examples/reports/train_hashprep_report.html b/examples/reports/train_tablint_report.html similarity index 99% rename from examples/reports/train_hashprep_report.html rename to examples/reports/train_tablint_report.html index 3246499..c63f1c9 100644 --- a/examples/reports/train_hashprep_report.html +++ b/examples/reports/train_tablint_report.html @@ -4,7 +4,7 @@ - HashPrep Quality Report [NEO] + Tablint Quality Report [NEO] @@ -22,9 +22,12 @@

-
-

HashPrep // Report

+
+ +
+

Tablint // Report

VERSION: 0.1.0a4

+
@@ -2846,13 +2849,13 @@

Tail (last 5 r
- *** HashPrep ::: Dataset Debugger *** + *** Tablint ::: Dataset Debugger ***

@@ -116,7 +116,8 @@ def _minimal_template(self):
- HashPrep + {{ logo_svg }} + Tablint v{{ version }}
@@ -281,7 +282,7 @@ def _minimal_template(self):
Software version
-
hashprep v{{ version }}
+
tablint v{{ version }}
{% if not pdf_mode %} @@ -678,7 +679,7 @@ def _minimal_template(self):
-

Built with HashPrep

+

Built with Tablint

@@ -731,9 +732,12 @@ def _neubrutalism_template(self):
-
-

HashPrep // Report

-

VERSION: {{ version }}

+
+ {{ logo_svg }} +
+

Tablint // Report

+

VERSION: {{ version }}

+
@@ -1172,7 +1176,7 @@ def _neubrutalism_template(self): {% endif %}
- *** HashPrep ::: Dataset Debugger *** + *** Tablint ::: Dataset Debugger ***
@@ -1184,7 +1188,7 @@ def _neubrutalism_template(self): const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'hashprep_config.json'; + a.download = 'tablint_config.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); diff --git a/hashprep/reports/json.py b/tablint/reports/json.py similarity index 98% rename from hashprep/reports/json.py rename to tablint/reports/json.py index ec9b92a..79b6035 100644 --- a/hashprep/reports/json.py +++ b/tablint/reports/json.py @@ -3,7 +3,7 @@ import numpy as np -import hashprep +import tablint def json_numpy_handler(obj): @@ -24,7 +24,7 @@ def generate(self, summary, full=False, output_file=None): report: dict = { "metadata": { "generated": datetime.now().isoformat(), - "version": hashprep.__version__, + "version": tablint.__version__, }, "dataset_overview": { "statistics": { diff --git a/tablint/reports/logo.py b/tablint/reports/logo.py new file mode 100644 index 0000000..9517e23 --- /dev/null +++ b/tablint/reports/logo.py @@ -0,0 +1,22 @@ +"""The Tablint mark, inlined so reports render without fetching a file.""" + +from markupsafe import Markup + +LOGO_SVG = Markup( + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" + """""" +) diff --git a/hashprep/reports/markdown.py b/tablint/reports/markdown.py similarity index 98% rename from hashprep/reports/markdown.py rename to tablint/reports/markdown.py index 1af75c4..d09b028 100644 --- a/hashprep/reports/markdown.py +++ b/tablint/reports/markdown.py @@ -3,7 +3,7 @@ import pandas as pd -import hashprep +import tablint from ..utils.logging import get_logger from .base import BaseReport @@ -60,7 +60,7 @@ def generate(self, summary, full=False, output_file=None): content += f"| Analysis started | {analysis_started[:19] if analysis_started != 'N/A' else 'N/A'} |\n" content += f"| Analysis finished | {analysis_finished[:19] if analysis_finished != 'N/A' else 'N/A'} |\n" content += f"| Duration | {reproduction_info.get('duration_seconds', 0)} seconds |\n" - content += f"| Software version | hashprep v{hashprep.__version__} |\n\n" + content += f"| Software version | tablint v{tablint.__version__} |\n\n" if full: img_dir = None @@ -241,7 +241,7 @@ def generate(self, summary, full=False, output_file=None): content += "### Tail (last 5 rows)\n\n" content += pd.DataFrame(summary["summaries"]["tail"]).to_markdown(index=False) + "\n\n" - content += "\n---\nGenerated by HashPrep\n" + content += "\n---\nGenerated by Tablint\n" if output_file: with open(output_file, "w") as f: diff --git a/hashprep/reports/pdf.py b/tablint/reports/pdf.py similarity index 97% rename from hashprep/reports/pdf.py rename to tablint/reports/pdf.py index 609dfe6..2a9374d 100644 --- a/hashprep/reports/pdf.py +++ b/tablint/reports/pdf.py @@ -1,17 +1,17 @@ from datetime import datetime import pandas as pd -from jinja2 import Template from weasyprint import HTML -import hashprep +import tablint -from .base import BaseReport +from .base import BaseReport, render_html +from .logo import LOGO_SVG class PdfReport(BaseReport): def generate(self, summary, full=False, output_file=None, **kwargs): - template = Template(self._get_template()) + template_str = self._get_template() head_df = pd.DataFrame(summary["summaries"]["head"]) tail_df = pd.DataFrame(summary["summaries"]["tail"]) @@ -41,7 +41,8 @@ def generate(self, summary, full=False, output_file=None, **kwargs): "analysis_started": reproduction_info.get("analysis_started", ""), "analysis_finished": reproduction_info.get("analysis_finished", ""), "duration_seconds": reproduction_info.get("duration_seconds", 0), - "version": hashprep.__version__, + "version": tablint.__version__, + "logo_svg": LOGO_SVG, "full": full, "head_table": head_df.to_html(index=False, border=0), "tail_table": tail_df.to_html(index=False, border=0), @@ -53,7 +54,7 @@ def generate(self, summary, full=False, output_file=None, **kwargs): "missing_plots": summary["summaries"].get("plots", {}), } - html_content = template.render(context) + html_content = render_html(template_str, context) pdf_content = HTML(string=html_content).write_pdf() if output_file: with open(output_file, "wb") as f: @@ -82,6 +83,9 @@ def _get_template(self): h4 { font-size: 9pt; margin: 10pt 0 4pt 0; color: #555; text-transform: uppercase; letter-spacing: 0.5pt; } .header { margin-bottom: 16pt; } + .header .brand { display: flex; align-items: center; gap: 8pt; } + .header .brand h1 { margin: 0; } + .header .brand svg { width: 28pt; height: 28pt; } .header .version { font-size: 9pt; color: #888; } .header .badges { margin-top: 4pt; } .badge { @@ -269,7 +273,7 @@ def _get_template(self):
-

HashPrep Report

+
{{ logo_svg }}

Tablint Report

v{{ version }}
{{ critical_count }} Critical @@ -333,7 +337,7 @@ def _get_template(self):
Software version
-
hashprep v{{ version }}
+
tablint v{{ version }}
@@ -612,6 +616,6 @@ def _get_template(self):
{{ tail_table | safe }}
{% endif %} - + """ diff --git a/hashprep/summaries/__init__.py b/tablint/summaries/__init__.py similarity index 100% rename from hashprep/summaries/__init__.py rename to tablint/summaries/__init__.py diff --git a/hashprep/summaries/dataset.py b/tablint/summaries/dataset.py similarity index 97% rename from hashprep/summaries/dataset.py rename to tablint/summaries/dataset.py index 92ae295..98ac9bf 100644 --- a/hashprep/summaries/dataset.py +++ b/tablint/summaries/dataset.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd -import hashprep +import tablint def get_dataset_preview(df): @@ -82,5 +82,5 @@ def add_reproduction_info(df: pd.DataFrame) -> dict: dataset_hash = hashlib.md5(pd.util.hash_pandas_object(df, index=True).values).hexdigest() return { "dataset_hash": dataset_hash, - "software_version": hashprep.__version__, + "software_version": tablint.__version__, } diff --git a/hashprep/summaries/interactions.py b/tablint/summaries/interactions.py similarity index 100% rename from hashprep/summaries/interactions.py rename to tablint/summaries/interactions.py diff --git a/hashprep/summaries/missing.py b/tablint/summaries/missing.py similarity index 100% rename from hashprep/summaries/missing.py rename to tablint/summaries/missing.py diff --git a/hashprep/summaries/mutual_info.py b/tablint/summaries/mutual_info.py similarity index 100% rename from hashprep/summaries/mutual_info.py rename to tablint/summaries/mutual_info.py diff --git a/hashprep/summaries/variables.py b/tablint/summaries/variables.py similarity index 100% rename from hashprep/summaries/variables.py rename to tablint/summaries/variables.py diff --git a/hashprep/utils/config_loader.py b/tablint/utils/config_loader.py similarity index 87% rename from hashprep/utils/config_loader.py rename to tablint/utils/config_loader.py index 9c19f6c..5269230 100644 --- a/hashprep/utils/config_loader.py +++ b/tablint/utils/config_loader.py @@ -1,15 +1,15 @@ -"""Load HashPrepConfig from YAML, TOML, or JSON files.""" +"""Load TablintConfig from YAML, TOML, or JSON files.""" from __future__ import annotations import json from pathlib import Path -from ..config import HashPrepConfig, config_from_dict +from ..config import TablintConfig, config_from_dict -def load_config(path: str | Path) -> HashPrepConfig: - """Load a HashPrepConfig from a YAML (.yaml/.yml), TOML (.toml), or JSON (.json) file. +def load_config(path: str | Path) -> TablintConfig: + """Load a TablintConfig from a YAML (.yaml/.yml), TOML (.toml), or JSON (.json) file. Only keys present in the file are overridden; all others fall back to defaults. """ diff --git a/hashprep/utils/logging.py b/tablint/utils/logging.py similarity index 66% rename from hashprep/utils/logging.py rename to tablint/utils/logging.py index 7c0efb8..2f71060 100644 --- a/hashprep/utils/logging.py +++ b/tablint/utils/logging.py @@ -1,4 +1,4 @@ -"""Structured logging for HashPrep. +"""Structured logging for Tablint. Provides a package-level logger that callers can use via get_logger(). By default, logs at WARNING level so end users don't see noise. @@ -7,22 +7,22 @@ import logging -LOGGER_NAME = "hashprep" +LOGGER_NAME = "tablint" def get_logger(module_name: str = "") -> logging.Logger: - """Get a logger scoped to a hashprep submodule. + """Get a logger scoped to a tablint submodule. Args: module_name: Dot-separated submodule path (e.g. "checks.correlations"). - If empty, returns the root hashprep logger. + If empty, returns the root tablint logger. """ name = f"{LOGGER_NAME}.{module_name}" if module_name else LOGGER_NAME return logging.getLogger(name) -# Configure root hashprep logger with NullHandler (library best practice). -# This prevents "No handlers could be found" warnings when hashprep is used +# Configure root tablint logger with NullHandler (library best practice). +# This prevents "No handlers could be found" warnings when tablint is used # as a library. End users or the CLI can attach their own handlers. _root_logger = logging.getLogger(LOGGER_NAME) _root_logger.addHandler(logging.NullHandler()) diff --git a/hashprep/utils/sampling.py b/tablint/utils/sampling.py similarity index 100% rename from hashprep/utils/sampling.py rename to tablint/utils/sampling.py diff --git a/tablint/utils/type_inference.py b/tablint/utils/type_inference.py new file mode 100644 index 0000000..79e17c6 --- /dev/null +++ b/tablint/utils/type_inference.py @@ -0,0 +1,150 @@ +import warnings + +import numpy as np +import pandas as pd + +from ..config import DEFAULT_CONFIG, TablintConfig + +_TYPE_TO_ROLE = { + "Numeric": "numeric", + "Categorical": "categorical", + "Text": "text", + "DateTime": "datetime", + "Boolean": "boolean", + "Unsupported": "unsupported", +} + + +def _type_config(config: TablintConfig | None): + cfg = config or DEFAULT_CONFIG + return cfg, cfg.type_inference, cfg.datetime + + +def _looks_like_datetime(series: pd.Series, parse_threshold: float) -> bool: + """Return True if an object/string column parses as datetime above the threshold.""" + sample = series.dropna().head(200) + if len(sample) == 0: + return False + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + parsed = pd.to_datetime(sample, errors="coerce") + parse_ratio = parsed.notna().mean() + return float(parse_ratio) >= parse_threshold + + +def _has_infinity(series: pd.Series) -> bool: + if not pd.api.types.is_numeric_dtype(series): + return False + values = pd.to_numeric(series, errors="coerce").to_numpy(dtype=float) + return bool(np.isinf(values).any()) + + +def _is_boolean_text(series: pd.Series, mappings: dict) -> bool: + if series.empty: + return False + sample = series.dropna().astype(str).str.lower() + if sample.empty: + return False + return bool(sample.isin(list(mappings)).all()) + + +def infer_types(df: pd.DataFrame, config: TablintConfig | None = None) -> dict[str, str]: + """ + Infer semantic types per ydata logic. + Returns: {col: 'Numeric' | 'Categorical' | 'Text' | 'DateTime' | 'Boolean' | 'Unsupported'} + """ + _, type_cfg, dt_cfg = _type_config(config) + types = {} + for col in df.columns: + series = df[col].dropna() + if series.empty: + types[col] = "Unsupported" + continue + + if pd.api.types.is_datetime64_any_dtype(series): + types[col] = "DateTime" + + elif pd.api.types.is_numeric_dtype(series) and not pd.api.types.is_bool_dtype(series): + # Infinities are a numeric defect even when the column has few distinct values. + if _has_infinity(series): + types[col] = "Numeric" + elif 1 <= series.nunique() <= type_cfg.num_low_cat_threshold: + types[col] = "Categorical" + else: + types[col] = "Numeric" + + elif pd.api.types.is_bool_dtype(series): + types[col] = "Categorical" + + elif pd.api.types.is_string_dtype(series) or pd.api.types.is_object_dtype(series): + n_unique = series.nunique() + unique_pct = n_unique / len(series) + if _is_boolean_text(series, type_cfg.bool_mappings): + types[col] = "Categorical" + elif _looks_like_datetime(series, dt_cfg.parse_threshold): + types[col] = "DateTime" + elif 1 <= n_unique <= type_cfg.cat_cardinality_threshold and unique_pct < type_cfg.cat_percentage_threshold: + types[col] = "Categorical" + else: + types[col] = "Text" + + elif pd.api.types.is_categorical_dtype(series): + types[col] = "Categorical" + + else: + types[col] = "Unsupported" + + return types + + +def infer_roles( + df: pd.DataFrame, + column_types: dict[str, str], + target_col: str | None = None, + config: TablintConfig | None = None, +) -> dict[str, str]: + """Classify each column for checks. + + Roles: target, identifier, boolean, categorical, numeric, datetime, text, unsupported. + The target wins over every other role. Identifiers are columns whose non-null + unique ratio reaches the configured threshold. Low-cardinality integers stay categorical. + """ + cfg, type_cfg, _ = _type_config(config) + unique_ratio_threshold = cfg.distribution.unique_value_ratio + roles: dict[str, str] = {} + + for col in df.columns: + if target_col is not None and col == target_col: + roles[col] = "target" + continue + + if _has_infinity(df[col]): + roles[col] = "numeric" + continue + + non_null = df[col].dropna() + unique_ratio = (non_null.nunique() / len(non_null)) if len(non_null) >= 2 else 0.0 + # Integer ids and free-text ids repeat only by accident. A float with a + # distinct value on every row is still a measurement, so it stays numeric. + label_like = ( + pd.api.types.is_integer_dtype(non_null) + or pd.api.types.is_object_dtype(df[col]) + or pd.api.types.is_string_dtype(df[col]) + or isinstance(df[col].dtype, pd.CategoricalDtype) + ) + if unique_ratio >= unique_ratio_threshold and label_like: + roles[col] = "identifier" + continue + + if pd.api.types.is_bool_dtype(df[col]) or _is_boolean_text(non_null, type_cfg.bool_mappings): + roles[col] = "boolean" + continue + + roles[col] = _TYPE_TO_ROLE.get(column_types.get(col, "Unsupported"), "unsupported") + + return roles + + +# Helper: Check if series is constant/empty (skip corr) +def is_usable_for_corr(series: pd.Series) -> bool: + return series.nunique() > 1 and len(series.dropna()) > 1 diff --git a/tests/test.py b/tests/test.py index 81b023c..2a80350 100755 --- a/tests/test.py +++ b/tests/test.py @@ -2,8 +2,8 @@ import pandas as pd -import hashprep -from hashprep import DatasetAnalyzer +import tablint +from tablint import DatasetAnalyzer TARGET_COLUMN = "Survived" VALID_CHECKS = DatasetAnalyzer.ALL_CHECKS @@ -17,7 +17,7 @@ critical = [i for i in issues if i["severity"] == "critical"] warnings = [i for i in issues if i["severity"] == "warning"] -print(hashprep.__version__) +print(tablint.__version__) print(issues) print(critical) print(warnings) diff --git a/tests/test_cli.py b/tests/test_cli.py index cb2794f..5bba96d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,10 +1,11 @@ """ -Comprehensive tests for HashPrep CLI commands. +Comprehensive tests for Tablint CLI commands. """ import json import os import subprocess +import sys import tempfile from pathlib import Path @@ -12,14 +13,13 @@ def has_pdf_deps(): - try: - # We need to check if we can actually run a PDF report - from hashprep.reports.pdf import PdfReport - - PdfReport() - return True - except Exception: - return False + # Probe in a child process. A broken WeasyPrint/Pango install can abort + # the interpreter, which a try/except in this process cannot catch. + probe = subprocess.run( + [sys.executable, "-c", "from tablint.reports.pdf import PdfReport; PdfReport()"], + capture_output=True, + ) + return probe.returncode == 0 skip_pdf = pytest.mark.skipif(not has_pdf_deps(), reason="PDF dependencies missing") @@ -40,7 +40,7 @@ def temp_output_dir(): def run_cli(args, cwd=None): """Helper to run CLI commands.""" - cmd = ["uv", "run", "hashprep"] + args + cmd = [sys.executable, "-m", "tablint.interfaces.cli.main", *args] if cwd is None: cwd = Path(__file__).parent.parent result = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd) @@ -48,7 +48,7 @@ def run_cli(args, cwd=None): class TestCLIScan: - """Test 'hashprep scan' command.""" + """Test 'tablint scan' command.""" def test_scan_basic(self, titanic_csv): """Test basic scan command.""" @@ -109,7 +109,7 @@ def test_scan_with_sampling(self, titanic_csv): class TestCLIDetails: - """Test 'hashprep details' command.""" + """Test 'tablint details' command.""" def test_details_basic(self, titanic_csv): """Test basic details command.""" @@ -136,7 +136,7 @@ def test_details_specific_checks(self, titanic_csv): class TestCLIReport: - """Test 'hashprep report' command.""" + """Test 'tablint report' command.""" def test_report_markdown(self, titanic_csv, temp_output_dir): """Test Markdown report generation.""" @@ -144,10 +144,10 @@ def test_report_markdown(self, titanic_csv, temp_output_dir): assert result.returncode == 0 assert "Report saved to:" in result.stdout - assert "train_hashprep_report.md" in result.stdout + assert "train_tablint_report.md" in result.stdout # Check file was created - report_file = os.path.join(temp_output_dir, "train_hashprep_report.md") + report_file = os.path.join(temp_output_dir, "train_tablint_report.md") assert os.path.exists(report_file) def test_report_json(self, titanic_csv, temp_output_dir): @@ -155,10 +155,10 @@ def test_report_json(self, titanic_csv, temp_output_dir): result = run_cli(["report", titanic_csv, "--format", "json"], cwd=temp_output_dir) assert result.returncode == 0 - assert "train_hashprep_report.json" in result.stdout + assert "train_tablint_report.json" in result.stdout # Verify JSON is valid - report_file = os.path.join(temp_output_dir, "train_hashprep_report.json") + report_file = os.path.join(temp_output_dir, "train_tablint_report.json") assert os.path.exists(report_file) with open(report_file) as f: data = json.load(f) @@ -172,9 +172,9 @@ def test_report_html_minimal(self, titanic_csv, temp_output_dir): ) assert result.returncode == 0 - assert "train_hashprep_report.html" in result.stdout + assert "train_tablint_report.html" in result.stdout - report_file = os.path.join(temp_output_dir, "train_hashprep_report.html") + report_file = os.path.join(temp_output_dir, "train_tablint_report.html") assert os.path.exists(report_file) def test_report_html_neubrutalism(self, titanic_csv, temp_output_dir): @@ -184,7 +184,7 @@ def test_report_html_neubrutalism(self, titanic_csv, temp_output_dir): ) assert result.returncode == 0 - assert "train_hashprep_report.html" in result.stdout + assert "train_tablint_report.html" in result.stdout @skip_pdf def test_report_pdf(self, titanic_csv, temp_output_dir): @@ -192,9 +192,9 @@ def test_report_pdf(self, titanic_csv, temp_output_dir): result = run_cli(["report", titanic_csv, "--format", "pdf", "--full"], cwd=temp_output_dir) assert result.returncode == 0 - assert "train_hashprep_report.pdf" in result.stdout + assert "train_tablint_report.pdf" in result.stdout - report_file = os.path.join(temp_output_dir, "train_hashprep_report.pdf") + report_file = os.path.join(temp_output_dir, "train_tablint_report.pdf") assert os.path.exists(report_file) # Check PDF magic number with open(report_file, "rb") as f: @@ -209,8 +209,8 @@ def test_report_with_code_generation(self, titanic_csv, temp_output_dir): assert "pipeline script saved" in result.stdout # Check files were created - assert os.path.exists(os.path.join(temp_output_dir, "train_hashprep_report_fixes.py")) - assert os.path.exists(os.path.join(temp_output_dir, "train_hashprep_report_pipeline.py")) + assert os.path.exists(os.path.join(temp_output_dir, "train_tablint_report_fixes.py")) + assert os.path.exists(os.path.join(temp_output_dir, "train_tablint_report_pipeline.py")) def test_report_no_visualizations(self, titanic_csv, temp_output_dir): """Test report without visualizations.""" @@ -241,14 +241,14 @@ def test_report_specific_checks(self, titanic_csv, temp_output_dir): class TestCLIVersion: - """Test 'hashprep version' command.""" + """Test 'tablint version' command.""" def test_version(self): """Test version command.""" result = run_cli(["version"]) assert result.returncode == 0 - assert "hashprep" in result.stdout.lower() + assert "tablint" in result.stdout.lower() # Should show version number assert any(char.isdigit() for char in result.stdout) @@ -286,16 +286,16 @@ def test_malformed_config(self, titanic_csv, temp_output_dir): assert result.returncode != 0 assert "malformed" in result.stderr.lower() - def test_invalid_format(self, titanic_csv): + def test_invalid_format(self, titanic_csv, temp_output_dir): """Test with invalid report format.""" - result = run_cli(["report", titanic_csv, "--format", "invalid"]) + result = run_cli(["report", titanic_csv, "--format", "invalid"], cwd=temp_output_dir) # Should handle gracefully or error assert result.returncode != 0 or "error" in result.stderr.lower() - def test_invalid_check_name(self, titanic_csv): + def test_invalid_check_name(self, titanic_csv, temp_output_dir): """Test with invalid check name.""" - result = run_cli(["report", titanic_csv, "--checks", "invalid_check_name"]) + result = run_cli(["report", titanic_csv, "--checks", "invalid_check_name"], cwd=temp_output_dir) assert result.returncode == 0 assert "Warning: Invalid checks ignored" in result.stdout @@ -345,9 +345,9 @@ def test_ml_workflow_with_target(self, titanic_csv, temp_output_dir): ) assert result.returncode == 0 - assert os.path.exists(os.path.join(temp_output_dir, "train_hashprep_report.html")) - assert os.path.exists(os.path.join(temp_output_dir, "train_hashprep_report_fixes.py")) - assert os.path.exists(os.path.join(temp_output_dir, "train_hashprep_report_pipeline.py")) + assert os.path.exists(os.path.join(temp_output_dir, "train_tablint_report.html")) + assert os.path.exists(os.path.join(temp_output_dir, "train_tablint_report_fixes.py")) + assert os.path.exists(os.path.join(temp_output_dir, "train_tablint_report_pipeline.py")) if __name__ == "__main__": diff --git a/tests/test_code_execution.py b/tests/test_code_execution.py index 370d3a7..c3abef3 100644 --- a/tests/test_code_execution.py +++ b/tests/test_code_execution.py @@ -2,15 +2,34 @@ import pandas as pd import pytest -from hashprep.preparers.codegen import CodeGenerator -from hashprep.preparers.models import ( +from tablint.preparers.codegen import CodeGenerator +from tablint.preparers.models import ( EncodeMethod, FixSuggestion, FixType, ImputeMethod, ScaleMethod, ) -from hashprep.preparers.pipeline_builder import PipelineBuilder +from tablint.preparers.pipeline_builder import PipelineBuilder + + +def test_hostile_column_name_does_not_execute_injected_code(capfd): + column = "a']; print('injected')" + suggestions = [ + FixSuggestion( + fix_type=FixType.IMPUTE, + columns=[column], + method=ImputeMethod.MEDIAN.value, + reason="Missing", + ) + ] + frame = pd.DataFrame({column: [1.0, np.nan, 3.0]}) + namespace: dict = {} + exec(CodeGenerator(suggestions).generate_pandas_script(), namespace) + result = namespace["apply_fixes"](frame.copy()) + assert result[column].isna().sum() == 0 + captured = capfd.readouterr() + assert "injected" not in captured.out def test_execute_pandas_fixes(): diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 2cc8776..7a52811 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,7 +1,7 @@ """Tests for code generation module.""" -from hashprep.preparers.codegen import CodeGenerator -from hashprep.preparers.models import ( +from tablint.preparers.codegen import CodeGenerator +from tablint.preparers.models import ( EncodeMethod, FixSuggestion, FixType, diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py index 6ef4113..290b298 100644 --- a/tests/test_config_loader.py +++ b/tests/test_config_loader.py @@ -7,8 +7,8 @@ import pandas as pd import pytest -from hashprep import DatasetAnalyzer, HashPrepConfig, load_config -from hashprep.config import DEFAULT_CONFIG, config_from_dict +from tablint import DatasetAnalyzer, TablintConfig, load_config +from tablint.config import DEFAULT_CONFIG, config_from_dict # --------------------------------------------------------------------------- # config_from_dict @@ -53,9 +53,9 @@ def test_unknown_nested_keys_are_ignored(self): with pytest.raises(ValueError, match="Unknown configuration key"): config_from_dict({"outliers": {"z_score": 3.0, "nonexistent": 99}}) - def test_returns_hashprepconfig_instance(self): + def test_returns_tablintconfig_instance(self): cfg = config_from_dict({}) - assert isinstance(cfg, HashPrepConfig) + assert isinstance(cfg, TablintConfig) def test_result_is_frozen(self): cfg = config_from_dict({"outliers": {"z_score": 2.0}}) @@ -195,8 +195,8 @@ def test_low_missing_threshold_triggers_issue(self): assert "missing_values" in categories def test_skewness_threshold_respected(self): - # Highly skewed data β€” lower the threshold to catch it with warning=0.5 - data = [1.0] * 90 + [100.0] * 10 + # Continuous skew. Two-valued numeric columns are categorical and are not skew-tested. + data = np.random.default_rng(0).exponential(scale=1.0, size=400) df = pd.DataFrame({"x": data}) custom = config_from_dict({"outliers": {"skewness_warning": 0.5, "skewness_critical": 20.0}}) analyzer = DatasetAnalyzer(df, auto_sample=False, config=custom, selected_checks=["skewness"]) diff --git a/tests/test_datetime.py b/tests/test_datetime.py index e282b07..5a971df 100644 --- a/tests/test_datetime.py +++ b/tests/test_datetime.py @@ -3,14 +3,14 @@ import numpy as np import pandas as pd -from hashprep import DatasetAnalyzer -from hashprep.checks.datetime_checks import ( +from tablint import DatasetAnalyzer +from tablint.checks.datetime_checks import ( _check_datetime_future_dates, _check_datetime_gaps, _check_datetime_monotonicity, ) -from hashprep.summaries.variables import _summarize_datetime -from hashprep.utils.type_inference import infer_types +from tablint.summaries.variables import _summarize_datetime +from tablint.utils.type_inference import infer_types # --------------------------------------------------------------------------- # Type inference @@ -67,7 +67,7 @@ class _FakeAnalyzer: """Minimal stand-in for DatasetAnalyzer used in unit tests.""" def __init__(self, df, column_types): - from hashprep.config import DEFAULT_CONFIG + from tablint.config import DEFAULT_CONFIG self.df = df self.column_types = column_types diff --git a/tests/test_drift.py b/tests/test_drift.py index 1401a1a..98e8dae 100644 --- a/tests/test_drift.py +++ b/tests/test_drift.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd -from hashprep.checks.drift import check_drift +from tablint.checks.drift import check_drift class TestNumericDrift: diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 71cf1cb..52cacb4 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -3,8 +3,8 @@ import numpy as np import pandas as pd -from hashprep import DatasetAnalyzer -from hashprep.checks.drift import check_drift +from tablint import DatasetAnalyzer +from tablint.checks.drift import check_drift class TestEmptyDataframes: diff --git a/tests/test_gate.py b/tests/test_gate.py new file mode 100644 index 0000000..3102d93 --- /dev/null +++ b/tests/test_gate.py @@ -0,0 +1,142 @@ +"""Acceptance tests for the 0.2 production gate contract.""" + +import json +from pathlib import Path + +import numpy as np +import pandas as pd +from click.testing import CliRunner + +from tablint import DatasetAnalyzer +from tablint.interfaces.cli.main import _json_payload, cli + +TITANIC = Path(__file__).parent.parent / "datasets" / "train.csv" + + +def _invoke(args): + return CliRunner().invoke(cli, args) + + +def _critical(summary): + return [issue for issue in summary["issues"] if issue["severity"] == "critical"] + + +class TestGateContract: + def test_titanic_gate_critical_is_cabin_only(self): + df = pd.read_csv(TITANIC) + summary = DatasetAnalyzer(df, target_col="Survived", preset="gate").analyze() + + critical = _critical(summary) + assert {issue["column"] for issue in critical} == {"Cabin"} + assert {issue["category"] for issue in critical} == {"missing_values"} + assert summary["schema_version"] == "0.2" + assert summary["sampling_info"]["was_sampled"] is False + + warned = {(issue["category"], issue["column"]) for issue in summary["issues"]} + assert ("unique_values", "Name") in warned + assert ("unique_values", "PassengerId") in warned + assert all(issue["category"] != "normality" for issue in summary["issues"]) + assert all( + issue["column"] != "Survived" or issue["category"] == "class_imbalance" for issue in summary["issues"] + ) + + def test_titanic_profile_does_not_fail_on_expected_signal(self): + df = pd.read_csv(TITANIC) + summary = DatasetAnalyzer(df, target_col="Survived", preset="profile").analyze() + + normality = [issue for issue in summary["issues"] if issue["category"] == "normality"] + assert normality + assert all(issue["severity"] == "warning" for issue in normality) + assert all(issue["column"] not in {"Survived", "Pclass", "SibSp", "Parch"} for issue in normality) + assert not any( + issue["category"] in {"feature_correlation", "categorical_correlation", "mixed_correlation"} + and "Sex" in issue["description"] + and "Survived" in issue["description"] + for issue in summary["issues"] + ) + assert not any( + issue["category"] == "high_zero_counts" and issue["column"] == "Survived" for issue in summary["issues"] + ) + assert not any( + issue["category"] == "constant_length" and issue["column"] == "Embarked" for issue in summary["issues"] + ) + + def test_clean_frame_has_no_critical_issues(self): + rng = np.random.default_rng(0) + frame = pd.DataFrame( + { + "y": np.array([0, 1] * 250), + "x1": rng.normal(size=500), + "x2": rng.normal(size=500), + "group": rng.choice(["a", "b", "c", "d"], size=500), + } + ) + summary = DatasetAnalyzer(frame, target_col="y", preset="gate").analyze() + assert summary["critical_count"] == 0 + + def test_broken_frame_flags_each_blocker(self): + rng = np.random.default_rng(1) + rows = 200 + target = rng.integers(0, 2, rows) + sparse = np.concatenate([np.full(160, np.nan), rng.normal(size=40)]) + values = np.concatenate([rng.normal(size=rows - 5), np.full(5, np.inf)]) + frame = pd.DataFrame( + { + "y": target, + "leak": target, + "inf_col": values, + "empty": np.full(rows, np.nan), + "sparse": sparse, + "ok": rng.normal(size=rows), + } + ) + summary = DatasetAnalyzer(frame, target_col="y", preset="gate").analyze() + found = {(issue["category"], issue["column"]) for issue in _critical(summary)} + assert ("data_leakage", "leak") in found + assert ("infinite_values", "inf_col") in found + assert ("empty_column", "empty") in found + assert ("missing_values", "sparse") in found + + def test_sampling_is_opt_in(self): + frame = pd.DataFrame({"x": range(1_000)}) + summary = DatasetAnalyzer(frame).analyze() + assert summary["sampling_info"]["was_sampled"] is False + assert summary["summaries"]["dataset_info"]["rows"] == 1_000 + + def test_json_payload_matches_scan_and_details(self, tmp_path): + frame = pd.DataFrame({"y": [0, 1, 0, 1], "x": [1.0, 2.0, 3.0, 4.0]}) + path = tmp_path / "tiny.csv" + frame.to_csv(path, index=False) + summary = DatasetAnalyzer(frame, target_col="y", preset="gate").analyze() + payload = _json_payload(summary) + + scan = _invoke(["scan", str(path), "--target", "y", "--json", "--preset", "gate"]) + details = _invoke(["details", str(path), "--target", "y", "--json", "--preset", "gate"]) + assert scan.exit_code == 0 + assert details.exit_code == 0 + scan_data = json.loads(scan.output) + details_data = json.loads(details.output) + assert scan_data["schema_version"] == payload["schema_version"] == "0.2" + assert ( + set(scan_data) + == set(details_data) + == { + "schema_version", + "critical_count", + "warning_count", + "total_issues", + "issues", + "sampling_info", + } + ) + + def test_fail_on_critical_exit_code(self, tmp_path): + clean = tmp_path / "clean.csv" + pd.DataFrame({"y": [0, 1] * 20, "x": np.linspace(0, 1, 40)}).to_csv(clean, index=False) + ok = _invoke(["scan", str(clean), "--target", "y", "--preset", "gate", "--fail-on", "critical"]) + assert ok.exit_code == 0 + + broken = tmp_path / "broken.csv" + pd.DataFrame({"y": [0, 1] * 20, "leak": [0, 1] * 20, "x": np.linspace(0, 1, 40)}).to_csv(broken, index=False) + failed = _invoke(["scan", str(broken), "--target", "y", "--preset", "gate", "--fail-on", "critical"]) + assert failed.exit_code == 2 diff --git a/tests/test_library_api.py b/tests/test_library_api.py index fee87be..ab511dc 100644 --- a/tests/test_library_api.py +++ b/tests/test_library_api.py @@ -1,29 +1,30 @@ """ -Comprehensive tests for HashPrep library API usage. +Comprehensive tests for Tablint library API usage. Tests all features when used as a Python library. """ import os +import subprocess +import sys import tempfile from pathlib import Path import pandas as pd import pytest -from hashprep import DatasetAnalyzer -from hashprep.reports import generate_report -from hashprep.utils.sampling import SamplingConfig +from tablint import DatasetAnalyzer +from tablint.reports import generate_report +from tablint.utils.sampling import SamplingConfig def has_pdf_deps(): - try: - from hashprep.reports.pdf import PdfReport - - # Try to initialize it to catch weasyprint dependency issues - PdfReport() - return True - except Exception: - return False + # Probe in a child process. A broken WeasyPrint/Pango install can abort + # the interpreter, which a try/except in this process cannot catch. + probe = subprocess.run( + [sys.executable, "-c", "from tablint.reports.pdf import PdfReport; PdfReport()"], + capture_output=True, + ) + return probe.returncode == 0 skip_pdf = pytest.mark.skipif(not has_pdf_deps(), reason="PDF dependencies (weasyprint/libgobject) missing") @@ -204,7 +205,7 @@ def test_html_report_minimal(self, sample_dataframe): with open(output_file) as f: content = f.read() assert "= { + "schema_version", + "critical_count", + "warning_count", + "total_issues", + "issues", + "sampling_info", + } + assert summary["issues"] + assert set(summary["issues"][0]) >= ISSUE_KEYS + assert "memory_exceeded" in summary["sampling_info"] + assert summary["sampling_info"]["was_sampled"] is False + assert summary["summaries"]["dataset_info"]["rows"] == 40 + + def test_explicit_sample_is_recorded_and_limits_rows(self): + from tablint.utils.sampling import SamplingConfig + + frame = pd.DataFrame({"x": range(300)}) + summary = DatasetAnalyzer( + frame, + sampling_config=SamplingConfig(max_rows=50), + auto_sample=True, + selected_checks=["empty_dataset"], + ).analyze() + assert summary["sampling_info"]["was_sampled"] is True + assert summary["summaries"]["dataset_info"]["rows"] == 50 + + +class TestSeverity: + def test_blockers_are_critical_and_ordinary_signal_is_not(self): + rng = np.random.default_rng(6) + rows = 200 + signal = rng.normal(size=rows) + target = signal + rng.normal(scale=2.0, size=rows) + leaked = target + rng.normal(scale=0.001, size=rows) + repeated = pd.concat([pd.DataFrame({"y": [0], "x": [1.0]})] * 40, ignore_index=True) + frame = pd.DataFrame( + { + "y": target, + "signal": signal, + "leaked": leaked, + "label": ["a"] * 190 + ["b"] * 10, + } + ) + + summary = DatasetAnalyzer(frame, target_col="y", preset="profile").analyze() + leakage = [issue for issue in summary["issues"] if issue["column"] == "leaked"] + assert any(issue["category"] == "target_leakage" and issue["severity"] == "critical" for issue in leakage) + assert all(issue["column"] != "signal" or issue["category"] != "target_leakage" for issue in summary["issues"]) + + imbalance = DatasetAnalyzer(frame, target_col="label", selected_checks=["class_imbalance"]).analyze() + assert imbalance["issues"][0]["severity"] == "warning" + assert imbalance["critical_count"] == 0 + + duplicates = DatasetAnalyzer(repeated, selected_checks=["duplicates"]).analyze() + assert duplicates["issues"][0]["severity"] == "critical" + + def test_small_shift_is_not_critical_drift_and_integer_codes_use_categories(self): + rng = np.random.default_rng(7) + train = pd.DataFrame({"amount": rng.normal(0, 1, 1500), "code": [1, 2, 3] * 500}) + nudged = pd.DataFrame({"amount": rng.normal(0.02, 1, 1500), "code": [1, 2, 3] * 500}) + shifted = pd.DataFrame({"amount": rng.normal(8, 1, 1500), "code": [1] * 1200 + [2] * 200 + [3] * 100}) + + quiet = check_drift(train, nudged) + assert all(issue.column != "amount" or issue.severity != "critical" for issue in quiet) + + loud = check_drift(train, shifted) + amount = next(issue for issue in loud if issue.column == "amount") + code = next(issue for issue in loud if issue.column == "code") + assert amount.severity == "critical" + assert amount.effect_size >= 0.25 + assert "categorical" in code.description + assert code.effect_size is not None + + def test_constant_target_is_critical(self): + frame = pd.DataFrame({"y": [1] * 30, "x": range(30)}) + summary = DatasetAnalyzer(frame, target_col="y", selected_checks=["single_value_columns"]).analyze() + assert summary["issues"][0]["severity"] == "critical" + assert summary["issues"][0]["column"] == "y" + + +class TestPreparation: + def test_suggestions_drop_critical_missing_data_and_impute_partial_numbers(self): + provider = SuggestionProvider( + issues=[ + Issue("missing_values", "critical", "cabin", "80.0% missing values in 'cabin'", "high", "drop"), + Issue("missing_values", "warning", "age", "20.0% missing values in 'age'", "medium", "impute"), + ], + column_types={"cabin": "Text", "age": "Numeric"}, + ) + by_column = {suggestion.columns[0]: suggestion for suggestion in provider.get_suggestions()} + assert by_column["cabin"].fix_type == FixType.DROP_COLUMN + assert by_column["age"].fix_type == FixType.IMPUTE + assert by_column["age"].method == ImputeMethod.MEDIAN.value + + def test_empty_mode_does_not_crash_generated_code(self): + column = "city" + suggestion = FixSuggestion( + fix_type=FixType.IMPUTE, + columns=[column], + method=ImputeMethod.MODE.value, + reason="Missing category", + ) + namespace: dict = {} + exec(CodeGenerator([suggestion]).generate_pandas_script(), namespace) + result = namespace["apply_fixes"](pd.DataFrame({column: [np.nan, np.nan]})) + assert result[column].isna().all() + + def test_same_imputers_share_one_sklearn_step(self): + suggestions = [ + FixSuggestion(FixType.IMPUTE, ["age"], method=ImputeMethod.MEDIAN.value, reason="age"), + FixSuggestion(FixType.IMPUTE, ["fare"], method=ImputeMethod.MEDIAN.value, reason="fare"), + ] + code = PipelineBuilder(suggestions).generate_pipeline_code() + assert code.count("SimpleImputer(") == 1 + assert "age" in code and "fare" in code + + namespace: dict = {} + exec(code, namespace) + frame = pd.DataFrame({"age": [1.0, np.nan, 3.0], "fare": [4.0, 8.0, np.nan]}) + transformed = namespace["build_preprocessing_pipeline"]().fit_transform(frame) + assert transformed.shape == (3, 2) + assert not np.isnan(transformed).any() + + def test_clip_step_is_runnable(self): + suggestion = FixSuggestion( + fix_type=FixType.CLIP_OUTLIERS, + columns=["fare"], + reason="Clip", + ) + code = PipelineBuilder([suggestion]).generate_pipeline_code() + assert "_clip_outliers" in code + assert "None" not in code.split("def get_pre_pipeline_steps", 1)[1] + + namespace: dict = {} + exec(code, namespace) + frame = pd.DataFrame({"fare": [1.0, 2.0, 3.0, 4.0, 10_000.0]}) + clipped = namespace["get_pre_pipeline_steps"]()[0][1](frame) + assert clipped["fare"].max() < 10_000 + + +class TestReportsAndConfig: + def test_html_report_escapes_dataset_text(self): + column = '">' + value = "" + frame = pd.DataFrame({column: [value, "ok"] * 8}) + summary = DatasetAnalyzer(frame, include_plots=False, preset="profile").analyze() + + for theme in ("minimal", "neubrutalism"): + html = generate_report(summary, format="html", full=True, theme=theme) + assert "" not in html + assert "" not in html + assert "<script>" in html + assert "= '3.12'", @@ -198,7 +198,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } wheels = [ @@ -269,7 +269,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -373,7 +373,7 @@ name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -471,14 +471,11 @@ wheels = [ ] [[package]] -name = "hashprep" +name = "tablint" source = { editable = "." } dependencies = [ { name = "click" }, - { name = "fonttools" }, { name = "fuzzybunny" }, - { name = "jinja2" }, - { name = "matplotlib" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas" }, @@ -486,8 +483,19 @@ dependencies = [ { name = "scikit-learn" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "seaborn" }, { name = "tabulate" }, +] + +[package.optional-dependencies] +parquet = [ + { name = "pyarrow", version = "25.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pyarrow", version = "26.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +reports = [ + { name = "fonttools" }, + { name = "jinja2" }, + { name = "matplotlib" }, + { name = "seaborn" }, { name = "weasyprint" }, ] @@ -500,19 +508,21 @@ dev = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.3.0" }, - { name = "fonttools", specifier = ">=4.60.2" }, + { name = "fonttools", marker = "extra == 'reports'", specifier = ">=4.60.2" }, { name = "fuzzybunny" }, - { name = "jinja2", specifier = ">=3.1.6" }, - { name = "matplotlib", specifier = ">=3.10.6" }, + { name = "jinja2", marker = "extra == 'reports'", specifier = ">=3.1.6" }, + { name = "matplotlib", marker = "extra == 'reports'", specifier = ">=3.10.6" }, { name = "numpy", specifier = ">=2.2.6" }, { name = "pandas", specifier = ">=2.3.2" }, + { name = "pyarrow", marker = "extra == 'parquet'", specifier = ">=19.0.0" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "scikit-learn", specifier = ">=1.7.2" }, { name = "scipy", specifier = ">=1.15.3" }, - { name = "seaborn", specifier = ">=0.13.2" }, + { name = "seaborn", marker = "extra == 'reports'", specifier = ">=0.13.2" }, { name = "tabulate", specifier = ">=0.9.0" }, - { name = "weasyprint", specifier = ">=68.0" }, + { name = "weasyprint", marker = "extra == 'reports'", specifier = ">=68.0" }, ] +provides-extras = ["reports", "parquet"] [package.metadata.requires-dev] dev = [ @@ -1106,6 +1116,120 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pyarrow" +version = "25.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653, upload-time = "2026-08-10T12:40:53.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/3e/5cd70becb51e1d044c54ba5e627424a6e87df5b98008cbd22cc6abd409ca/pyarrow-25.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0b1edbb2f385a6a65e9711b62ba86ac54a7816a3f8d17bb3e8a5929d65fb2485", size = 35954271, upload-time = "2026-08-10T12:36:33.857Z" }, + { url = "https://files.pythonhosted.org/packages/64/be/17599e086df264ea7dc221d1101e3131e181e00da428a2f9bd0358f0d06b/pyarrow-25.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:a4dd8bf99a8fac133efc0ed6a92f5fddbe2adba0d0f6dd720e39ba9855cea85c", size = 37647543, upload-time = "2026-08-10T12:36:39.486Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/e138b451fd3970a6eda4599f68ae3b2b32b661bc958de3239d54a0bf6575/pyarrow-25.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bddd0c4f7630c2a3ddf6347c1bdaa79d97bcf6bd445f9e60c816b7d77c85a5ae", size = 46837120, upload-time = "2026-08-10T12:36:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/57/5c/f8fc0eb2de03464a557d5a4d0c15e972d73362414696618833b771f7eddd/pyarrow-25.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a4d6d5e9a3d1879a97c08ded0c797579b7965eafd0f0c26c30b45ccc06db939b", size = 50066460, upload-time = "2026-08-10T12:36:53.702Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d1/0dd64fd06de0333b808a02f60981635f067b71aad3a30698a9a104fae778/pyarrow-25.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:514ddb60285631af068875550c90eddc181db3e8e63a032b1559be189e82f056", size = 49937892, upload-time = "2026-08-10T12:37:00.349Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/f89d1bd76d5f3284c2a44d7d7ebbd8204535e5ae2b41f4077069b4ff2ec6/pyarrow-25.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cab40b1edfef0262e0e5251aa2c58d75630f24d06dd7794480243acc001a1d7d", size = 53107240, upload-time = "2026-08-10T12:37:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/67/67/b554a8e09f3f3decccf405eb8fbe86696321cbcb5b62d18b4a5057a4c113/pyarrow-25.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:60e89d8f13861a1f7f8d950fa54aebb8023b30734d0ac51ffa80beabe2df4bba", size = 27848683, upload-time = "2026-08-10T12:37:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8b/0d23b47702fcfe8b3618d5292035099675c5a1c48258932350c08020f7b5/pyarrow-25.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51093dd9e10325fbdb3c10a2ae7c4806e5c822d94e74ae4938b26524a3323fee", size = 35946180, upload-time = "2026-08-10T12:37:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/17/707d17a5476c55a9541fde0db8213ac30979a792864d72415f176ba50c45/pyarrow-25.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:eb6203482ff3746a5632303a7279ae0b5a304c46985b49ed1378cb350ea6728d", size = 37644787, upload-time = "2026-08-10T12:37:25.795Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b2/cdc98ecf1a6408280bc3a6a07054cdd99a3f4670acc0545d383ce113e87d/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:880523be3d29efcf83d3998835d206118ccf35e3871dbd2fb60408cf6b007a80", size = 46834633, upload-time = "2026-08-10T12:37:33.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/6e/d3fafc41f378b2c65be43b827798c0fae42049a641c8526633ed3eb573e2/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:25f8720bf6387d5dc2ebd2622112de630760419e4b66134405dd24110d15f37e", size = 50065507, upload-time = "2026-08-10T12:37:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/d5/12/8d0698954b8c3001844a898e0a6900bebe83d7ee40c11195174c5122f324/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4facd65742a024a4a366328a1d2292062d72d6e023c1b7dda8d4c37544933a25", size = 49955690, upload-time = "2026-08-10T12:37:46.644Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/1ecb936ac6409e90a34d58eea1c7cec09a9ae6d2141b9e49ad01a2b1ea47/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa0559502e1cd6254d6814614085dd9c5a3dd0419362978a936a3f68a9e5c3df", size = 53128198, upload-time = "2026-08-10T12:37:52.531Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/5236033550633c9b7377b2a53660b2bbb06cb06dc09c4356332d67643ca1/pyarrow-25.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:62cd0d785b8aa6675ee355f9fc02252a340f4441257c42674937826fd7594325", size = 27857263, upload-time = "2026-08-10T12:37:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559, upload-time = "2026-08-10T12:38:02.567Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383, upload-time = "2026-08-10T12:38:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190, upload-time = "2026-08-10T12:38:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437, upload-time = "2026-08-10T12:38:22.487Z" }, + { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424, upload-time = "2026-08-10T12:38:28.755Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206, upload-time = "2026-08-10T12:38:34.862Z" }, + { url = "https://files.pythonhosted.org/packages/65/ee/197cbf47e49f83e6ebeb946a5259a48a638dea27ac774db42fe78022179d/pyarrow-25.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8858d7bfc22e3f51529aeaa4077225029724623e4595dc9eff8c793935c34140", size = 27953934, upload-time = "2026-08-10T12:38:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8d/8f271a7a034c834910ec925d56fa4b29733b1380f5289419f5aaa3b02777/pyarrow-25.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c7c534ec03c358a76ea3e505e74c1b6aef290af90c444dfd092dbfe23e755b85", size = 35855328, upload-time = "2026-08-10T12:38:45.489Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/5bac242f4e841b9971d5eb94fdfe2577e2b70be983e27401e72055786037/pyarrow-25.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dda9470024204d7bbf2042b47c6e8a0e47a3eeb8e34405882dfaea6577e0c153", size = 37622415, upload-time = "2026-08-10T12:38:51.107Z" }, + { url = "https://files.pythonhosted.org/packages/63/1f/96d03b4e1506524f7087adb0fd6b2f69f0c9c7aaff1ec36d8030082e15a5/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:44a9120ce5bd81936b8ab9a88076e3fd47c2c6838e0e43630fed83626aca81d9", size = 46813813, upload-time = "2026-08-10T12:38:57.773Z" }, + { url = "https://files.pythonhosted.org/packages/98/d6/33a411115b61dbfc16ad6ad73e71730f6fea654ee3667673bc53ab0e2fe7/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f", size = 50104452, upload-time = "2026-08-10T12:39:04.579Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/b1b97c9ca87f9f9ddbb5230c798df94eccce61bd79b9b45458c69a478588/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f89685964f46e4216103c75483aac0c0692a5f72212d7ca835adba5ede56ce3", size = 49951343, upload-time = "2026-08-10T12:39:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/a112df5cfd5a68cb1d9fc31cfe38c28d5aec9f10865ce37ecef2e4450873/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6943e2fe7954d29d84de45d29d34c8dc36ce96570e67d89aa9976e650a4a9138", size = 53144784, upload-time = "2026-08-10T12:39:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/24/97e8bd98f1e3b07e2ba08bcdff690674fbe16d69a7d2712cc3884665e615/pyarrow-25.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:31e49a7888fcdf3a835da33ae777f6bb9a866334e5a789282fc26dcf426f7f15", size = 27870159, upload-time = "2026-08-10T12:39:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/36/4c/b525824ad3094076919273cd97db61fb3d78252dee76fa3b8dc8f76774aa/pyarrow-25.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bf0b672390cdcb640d7288f96b826d71ff4e9abb254a86c89890baf51a29cee6", size = 35885255, upload-time = "2026-08-10T12:39:32.366Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/448bb0e940de41aec31d1a956e63ad9c54afdf122a103cc3ab20c2a3ce33/pyarrow-25.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:38a9a4b4b9613380e200641891495a56c3d5a98a092db4a870af9975e220471d", size = 37644461, upload-time = "2026-08-10T12:39:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9a/13587e38bd4806fd218f50fd13b8903fab60588a699ff0c406372e5b4043/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b", size = 46877146, upload-time = "2026-08-10T12:39:43.722Z" }, + { url = "https://files.pythonhosted.org/packages/8d/61/1c5d1229fa21da4cff5365e41e57177aaac57c563c727f35419b8513d1c1/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:9171748cdf796972d85a4b60157c279913e242992e350c90c7450182a9838b2a", size = 50131616, upload-time = "2026-08-10T12:39:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/43/20/291e1d65cc0b09aa19f03cf25cf51a2f5fa94b5db315178f2d254ed5cad4/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7a296aac7a71fa0886c08e155ddb6c636a50013f801f6178daafa0f9e726188", size = 50008879, upload-time = "2026-08-10T12:39:56.891Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7c/1b7c9ec28e76576337e4f97b31141c9a181b89b6d1d6221e9d8205621a58/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0fe7c8b6c03969b49c8c66182e4a18e3819ab92d07cfab5d8370c531b9369ef0", size = 53170864, upload-time = "2026-08-10T12:40:04.918Z" }, + { url = "https://files.pythonhosted.org/packages/b7/75/f3d789dc06011a765d14d86bda799cf72ac1d715b6a6edecaa0d73d95062/pyarrow-25.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f", size = 28620729, upload-time = "2026-08-10T12:40:51.41Z" }, + { url = "https://files.pythonhosted.org/packages/fc/05/647a8ee6f7c2662feb6921315617bc04dcd6034763fb61b1199720bf6162/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:59a2de54c0cbd954da861eee4d1d330f8e909c45b53455baef696380f2c55033", size = 36130288, upload-time = "2026-08-10T12:40:11.014Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/c9ee997554d7bea94520667dd1933f109ac1da3ee3556d2b49381e023484/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:35935cd5de130aa5cf4dea052a63e6bf2e17006c35c3a468194242b9b2bf5956", size = 37762187, upload-time = "2026-08-10T12:40:16.592Z" }, + { url = "https://files.pythonhosted.org/packages/a2/08/a28c01c7fe9e96e8233ce2d13df1d402f4f999f848f51d2daacd6bb4c036/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44", size = 46888003, upload-time = "2026-08-10T12:40:23.242Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b9/58612e977d28dc58c878448866838369ee8da2f1e7cc8ed2c84b952aafee/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a1fdfc6659b6b19022f2e50627fb5cf7156a66c46bf4299379955cbe742382a", size = 50079036, upload-time = "2026-08-10T12:40:29.169Z" }, + { url = "https://files.pythonhosted.org/packages/72/13/66e1402dcc860e1dc2760b1e0292c9a569b62b3bccab69def1b3e907d006/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:169d3429d5be7c752125890620f75a60776d38b0035eddae939651640822332e", size = 50040226, upload-time = "2026-08-10T12:40:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/78/10/3f1a5497a7ef732ab0f03ecca3e66d89d9c0f57fdc61b4794c456b781f01/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:119297a6dc197e45d9c6d4415f7814a67ffa36c180d26f68c154c58067ae782d", size = 53149035, upload-time = "2026-08-10T12:40:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/93/c0/37d4a7e8e2f7a6076283673d5298018ca26478b934c6ee369e10505ab32c/pyarrow-25.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4288f27577352d608ca08553b0865e4a9b3aa14820c5d95b53337218d609835b", size = 28753071, upload-time = "2026-08-10T12:40:46.623Z" }, +] + +[[package]] +name = "pyarrow" +version = "26.0.0" +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/ec/34/17c34cb38e5d940e38f0f0d9fdfa0e8a506676409ea9b85aff7e3079f831/pyarrow-26.0.0.tar.gz", hash = "sha256:0cccd36e00ea3afeb52ded61f2721ce71f604853d70c45365c58324eb773d6ae", size = 1239433, upload-time = "2026-10-09T08:26:25.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/68/e0707097cee93be7f693e7e89495fabfeb8bf95ee30619063f8b30fffc29/pyarrow-26.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:fcdd1e04982637c6042337d3e24d472f938f01fdc502e2b994844b726d12c3f4", size = 36370896, upload-time = "2026-10-09T08:13:28.874Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f0/591211c00612aef83236daff1620412b24aeb07c646de08c18a8a6c95a39/pyarrow-26.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:f800e9e722c145ccd18012d82a864cb21bfee4ba4ceffde77100d25eced511a9", size = 38709806, upload-time = "2026-10-09T08:13:33.417Z" }, + { url = "https://files.pythonhosted.org/packages/50/ea/9b035a9d1556e06e64ea86169d9a985d0fc092d427ac5edbb3af7183289c/pyarrow-26.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7aa12ab8e236789b1ecd2d6ecaef036b4e63d675ddf1864a43c6799d18f2d028", size = 50885975, upload-time = "2026-10-09T08:13:37.737Z" }, + { url = "https://files.pythonhosted.org/packages/e1/81/8e685683897a6d3d5887c3e2fd24f3c14bc5d6d6bb3a2387484e665c580e/pyarrow-26.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6e89dee53aaeb50505ed6152ea55bc7ddfd4f4df264f5427ea255288d8f0e580", size = 53904793, upload-time = "2026-10-09T08:13:42.984Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ad/d474a0b1b00110f3a879aa5df654f857c81929a32b2a4222869240de5220/pyarrow-26.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1c1b4263fd13abbc339a16f2bf19f3a5cbf2a620853d812b1256f03c5342cb8", size = 54458010, upload-time = "2026-10-09T08:13:47.778Z" }, + { url = "https://files.pythonhosted.org/packages/d4/86/2c2861e905810c59fed4d98c85b994c21e8613730c5c3b436781d89110f2/pyarrow-26.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff1e816af7abff71f289242e109217036723ce36aca74ad6691e52d964a74afa", size = 57368406, upload-time = "2026-10-09T08:13:52.651Z" }, + { url = "https://files.pythonhosted.org/packages/0e/02/823e606633c15155bb965c7a0f3750c4f20dd47c4ab48213c7693df0e0ba/pyarrow-26.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b0972a3dc71b642050d1bc72664a3916e14f59c943d8c1368154d6e4b0c2d5", size = 28522657, upload-time = "2026-10-09T08:13:56.513Z" }, + { url = "https://files.pythonhosted.org/packages/b3/60/6793778f2617cce469383dac0ba08c4f2401cf342df0c7b9ca53939d9b46/pyarrow-26.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:90ddaf7c625307ad52f31a9b25c34fe5e4897c7529ee3481135822b2b6842ff1", size = 36333953, upload-time = "2026-10-09T08:14:00.387Z" }, + { url = "https://files.pythonhosted.org/packages/db/81/f944cc63ce8a753e5fbff25de6d1d475ebd7fffdf9cf98c65130294fc896/pyarrow-26.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee341973f78a0b46e073d065e88e75026a9c584051e97f98a0d05d96c6bac7dd", size = 38688456, upload-time = "2026-10-09T08:14:04.344Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2d/7e5c722fa5d5d9f3b75e62fe11694b34217664d4f05ac88031197166b277/pyarrow-26.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:01c863a18bd9c8412453dd0d92de6d0ee7b2b3d6fb079d9734a4b2a3c8bd4453", size = 50867603, upload-time = "2026-10-09T08:14:09.115Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/9cd356d906e71bd79b0c3fc5c9a54e01a0020dcf14c152ccfbcb503c7298/pyarrow-26.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6a628922ba20705fa964ca73e4ef959c2fb2f14b9bbec5589a6a1e68e6257c85", size = 53931932, upload-time = "2026-10-09T08:14:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e4/5bae3133b7fe04c24907a20f3bc1fba388cbbde659199e7b76445982047a/pyarrow-26.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:954d971b363b16ee41f89389a4053315dc71265f2ce5c2468eb0a910b1166268", size = 54444720, upload-time = "2026-10-09T08:14:31.214Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b4/ee422493bb6dafdbef776cfe2c2a73106a1063a79bf4e78d1e5f51176885/pyarrow-26.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d5768d03426abe6526d5274adefa00abf00a7f81118c46e98b5a46390f5549e", size = 57388949, upload-time = "2026-10-09T08:14:38.964Z" }, + { url = "https://files.pythonhosted.org/packages/54/3c/1783aab1dac28e175dcf26dfc7123725efc474caecaed91e8a34cb89cad0/pyarrow-26.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cc903e1069e9dd5e9dcf780324c0112e27e051e422ecfaff574fb33ed65d9160", size = 28567581, upload-time = "2026-10-09T08:14:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/4d/35/ca95493712af97c46a312945c8e9d16b21c5fe2f148be5466168d0290505/pyarrow-26.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a6ca849f90cf73fe361f08a5762c783ead9671e4548c1f558cc637b54c9103f2", size = 36336700, upload-time = "2026-10-09T08:14:51.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/ef/b1a675f79c9babfd4fcd99af62141d3c2d1a78a524e311b0c6b80110445a/pyarrow-26.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:c2ba350957076b1b3a22f549261dc3e9c67ca20816d8bd5f79d7b9c69be4c4c2", size = 38698502, upload-time = "2026-10-09T08:14:57.114Z" }, + { url = "https://files.pythonhosted.org/packages/3b/7c/cea852a832a327a8de797b3a68e5c25ce0f5aa1d20503807671bd90ec642/pyarrow-26.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e3b190ba1d3d22a5a8758597f797111b77d433473744352a184a5ee0a42d672e", size = 50865064, upload-time = "2026-10-09T08:20:01.614Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d6/e95834b29360092376fe4da9956ba41bb7b021869efe6ee9d4172d05cb15/pyarrow-26.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:240bd18a7487f8767616a948a69dd4e740a8bc36a1c9da49e4dc9a32c5c2faed", size = 53926722, upload-time = "2026-10-09T08:23:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7f/98257444e2aea2e1fddceee3af3bd2077236d550428413f80393bd1f888d/pyarrow-26.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2b5fcd69c0e1107b79e55839877db5a6ed04651b73fd6fec581d09e230bed5e4", size = 54443093, upload-time = "2026-10-09T08:23:16.971Z" }, + { url = "https://files.pythonhosted.org/packages/88/ca/dac99cfb25cfa62bf7194600cc99abc14a6bd2af50d7fdb7f15eeaf6e202/pyarrow-26.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f7444ea6975c49a857c68f9bd8fa11acae96dede63d120ffb3bf0a603ea82516", size = 57381937, upload-time = "2026-10-09T08:23:24.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ed/138d29fddaf803b90f4527e124bb6aaddc18aaf4a6c50fd0a5f577c94989/pyarrow-26.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:3de30a7432b48b98b9decbd9e25a53bb9251d202c2e6c5a29a50869592ccb117", size = 28478571, upload-time = "2026-10-09T08:23:30.535Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/01858422a37f083911c2bb4d15cc32c5eeaa9d9b2bf5ddedee995a7146a6/pyarrow-26.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5780d487ff6c6ed7b42298609680d87fe0036e529a9dc2e1105364bce9697f50", size = 36378402, upload-time = "2026-10-09T08:23:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/00/85/f6b5976c2878b752d0804d371684e0495a71de296b6dc6559e6fbaa4311a/pyarrow-26.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:a0e4e92eeb088f1d7c2c04d6c7de8434c75abb4b4ccf0bbcd045aa7164c68d93", size = 38733074, upload-time = "2026-10-09T08:23:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/81/bc/c90fcbbcf893631e23dab1b0fb3fa29a508a8614326571b03c0894eda00b/pyarrow-26.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:eaf9e7cc7ab59f6c760232bbde18f64d559bbc50544841303bfb32be53533297", size = 50929201, upload-time = "2026-10-09T08:23:50.507Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c1/0c1ff38ab7df1b2cf54cf0ad9f19a516c4e416c6c9b4c966cc2c9d587f77/pyarrow-26.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ab6914db225d7f399652ae1f08588dfbc9efe617612715701e3d9d5cfa5ca19f", size = 53951865, upload-time = "2026-10-09T08:23:57.692Z" }, + { url = "https://files.pythonhosted.org/packages/9f/70/6a6b170496925472adad45a32528770fc8632db35fc60d4edd1e9ce1be0b/pyarrow-26.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41dd3661ef40790a78870052ad7a58ad827b27c67a4511f06962eb9e9b74d19b", size = 54496388, upload-time = "2026-10-09T08:24:05.23Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/033ef9dba80976820190e292a10a5a23e9406572b76bbeb4d685d90e5c8d/pyarrow-26.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6e949744dcfc2d379808f7013c5f9cafaf0f817656dff7d46c6931528dd1784b", size = 57411588, upload-time = "2026-10-09T08:24:12.043Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ff/a74892c50aaf1f9f744a84493e08a2f99221e77c39d2d4a926de21a99edf/pyarrow-26.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:4a5fa8dc70dd50808990ff36faf44088e357b353d86c7682dd92d4b78d4c97d5", size = 29237858, upload-time = "2026-10-09T08:24:58.106Z" }, + { url = "https://files.pythonhosted.org/packages/03/10/f0ee0976ef08a851a743c57608917ac9a47623f688b9ee0efe5429975ba1/pyarrow-26.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:e2a1856e9565fe2679863b372478c681806aebbf7d0a6e72f33e77f804e647d6", size = 36495870, upload-time = "2026-10-09T08:24:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/0bc431a509bf10b4472dbb94f4184752ecbbddeb7f467152dac0fdaed469/pyarrow-26.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:4bcba83299cb2b8f8e443d36c6ba6269a5034431879015fb0719495df8a14de2", size = 38819754, upload-time = "2026-10-09T08:24:20.875Z" }, + { url = "https://files.pythonhosted.org/packages/61/59/2be41d26af7a07fb71581fb753cae396403ba1a2978355fd553929d44a9a/pyarrow-26.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a4d235876f14b4136b4d616ec42eb469ea0d6ead336cae631aa1dd29b21c962", size = 50933671, upload-time = "2026-10-09T08:24:27.199Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/b6d5048cf3178be9678f5c9c60040199894b2f69c3439c87ced91fd24da9/pyarrow-26.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:210cc9b83888b87cdc8f793eebb264f22b20d0dedbedefc73b9687a7047b4747", size = 53906419, upload-time = "2026-10-09T08:24:33.536Z" }, + { url = "https://files.pythonhosted.org/packages/09/2b/23e30fbd776c81d18d134d2592eb60daca13e8a57ab087d0fa042f9d9f3d/pyarrow-26.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca77c43ca55bfc9a4eeb1f0cd5f093f08731b77c24cdba0829035f084959b0bb", size = 54527960, upload-time = "2026-10-09T08:24:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/e2/23/fce251cd6b0546dfc181b00d5c8ef1c95a8c4cae83266bc3dfd5f719c62c/pyarrow-26.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:290a74c48e9491b436fd5edacfadf357943f82aa45c81110bd83a69aab33d1cf", size = 57388010, upload-time = "2026-10-09T08:24:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/44/a5/0126fb0ef8d59bf257bdd68bb41623b72afc6e81790a0b4ac863a0f58861/pyarrow-26.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:515a10dae2a1d236bc9c9209d0317acb6746ea63cd4f98704904af7156d90ed1", size = 29406123, upload-time = "2026-10-09T08:24:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/ed/66/8ada1b5165359d84b4b9b5384742304d1081da670f77d458fd9c9b8a2161/pyarrow-26.0.0-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e890816e5ee89c74a0f8b9379fe8b5ba83f46132b2a0bbb9b1c21359ec30dfda", size = 36373215, upload-time = "2026-10-09T08:25:03.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/83/74f10c3d803a6834b2acab21847724d4bdbc74d246eb17321432844707f3/pyarrow-26.0.0-cp315-cp315-macosx_12_0_x86_64.whl", hash = "sha256:9db18a9dc0af52135c9eac549d80a7a882696efbe5406cf882b044525d4ecc2e", size = 38730866, upload-time = "2026-10-09T08:25:07.924Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5a/ea2fa2163b1bd8ff73efd39c4060be63fd6ddec03e7887a471acd1e042a4/pyarrow-26.0.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:734312d3d99088d9ec28c5b17bad40389bd8373a1afc10acb60b83fd217af087", size = 50924443, upload-time = "2026-10-09T08:25:13.864Z" }, + { url = "https://files.pythonhosted.org/packages/78/80/8c47b6cf8cfd42826df65193eff026c1cc81fa6cb213a3c3f5d203e6f67a/pyarrow-26.0.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:24f892fdf1ae1942d69d3f7742e2f49960ec95277cfb1a70b8a1d91f4a96d935", size = 53948540, upload-time = "2026-10-09T08:25:19.305Z" }, + { url = "https://files.pythonhosted.org/packages/69/1f/3a506a76d944ec5c5e4b7f01d8d0446b392a6fb384de627a12e503f616b4/pyarrow-26.0.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:879331ddea2a26479fa18fade71e6facf684a6cf19f67daec3775c871569e8e5", size = 54494863, upload-time = "2026-10-09T08:25:24.517Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/08c4bb04d651788d2eaca78065743f4f6ded974d4ef96ae3c473993e9d0c/pyarrow-26.0.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:5b827650e874f1f9f9392524ea3e9e3e8a245de5ba64acca1f81ab188090afb9", size = 57409877, upload-time = "2026-10-09T08:25:31.157Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f3/c64781fbd7b6d3c07993b698c14944d0d195f07e800fa931c486ae6ab36a/pyarrow-26.0.0-cp315-cp315-win_amd64.whl", hash = "sha256:8e8e28c464552b5ca03e30d4504168c4425ce383884f8611b00e972f9fd933fc", size = 29236658, upload-time = "2026-10-09T08:26:22.607Z" }, + { url = "https://files.pythonhosted.org/packages/06/55/2ee3729daea999f19f061f03898d4895a242c4cd94f26e1324e5fdfbfe10/pyarrow-26.0.0-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:ce28748cbeb0f29c3ce9603782979c7117580fc76f16aa3ca448b38a22281adb", size = 36489011, upload-time = "2026-10-09T08:25:37.64Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7d/3eb17f601f2bf13eda5f2ed28956379ca628b4dda97619cbb1cb1721622d/pyarrow-26.0.0-cp315-cp315t-macosx_12_0_x86_64.whl", hash = "sha256:106bb9290fc6fd9a84138a9440038ef184bac86463543c5ff099229cb30d996c", size = 38808480, upload-time = "2026-10-09T08:25:43.579Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/f0047360b0f4bfc031b256dc0aec3837a61f245b2fb70f8363438e2db665/pyarrow-26.0.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a413046eba9896e632925066c74095182200ba32e19ff0166bf64d2f936ac", size = 50923273, upload-time = "2026-10-09T08:25:51.445Z" }, + { url = "https://files.pythonhosted.org/packages/38/d9/56d9fb91210407df31cbeb9b91138601c88c7c8fb5f6bf773b20d65509bf/pyarrow-26.0.0-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:d58798c4d8d629700058e9afc1e16b9801023f3ce4dc1c92d945e79b5ffe4e98", size = 53900905, upload-time = "2026-10-09T08:25:59.554Z" }, + { url = "https://files.pythonhosted.org/packages/cf/40/8e8a7e9e027c731520c7eb179dd00a153b76ebf0bc11d213c6c8f8502851/pyarrow-26.0.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:645917e976671debabf854abab6e2b75c571ca4f82adc33a2d338697f7c27d93", size = 54518345, upload-time = "2026-10-09T08:26:07.125Z" }, + { url = "https://files.pythonhosted.org/packages/be/89/1e768a3fdb88d34e708ad2dc00dbf8e4e30290784eb84198d59308963bea/pyarrow-26.0.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7c3fda041e7078802589cf257750323ee3d0cd1e56e53a9b20ec845697fb3d28", size = 57379403, upload-time = "2026-10-09T08:26:13.624Z" }, + { url = "https://files.pythonhosted.org/packages/96/be/7b81a44d6a8e70581dcc1d6f01541f9000a973b1e5d75394aec91e7b179a/pyarrow-26.0.0-cp315-cp315t-win_amd64.whl", hash = "sha256:68cd662e9e2b00876a131950cf32336ace2d0865e1f9418763e3d3be8481dfa4", size = 29389953, upload-time = "2026-10-09T08:26:18.277Z" }, +] + [[package]] name = "pycparser" version = "2.23" @@ -1313,7 +1437,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -1373,7 +1497,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } wheels = [ diff --git a/web/bun.lock b/web/bun.lock index f2c8960..1546502 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -3,18 +3,24 @@ "configVersion": 0, "workspaces": { "": { - "name": "hashprep-website", + "name": "tablint-website", "devDependencies": { "@sveltejs/adapter-static": "^3.0.0", - "@sveltejs/kit": "^2.0.0", + "@sveltejs/kit": "^2.56.1", "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", + "svelte": "^5.55.1", "svelte-check": "^4.0.0", "typescript": "^5.0.0", - "vite": "^6.0.0", + "vite": "^6.4.2", }, }, }, + "overrides": { + "cookie": "^0.7.0", + "devalue": "^5.6.4", + "picomatch": "^4.0.4", + "rollup": "^4.59.0", + }, "packages": { "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], @@ -74,69 +80,71 @@ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64", "libc": "glibc" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.65.0", "", { "os": "android", "cpu": "arm" }, "sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.65.0", "", { "os": "android", "cpu": "arm64" }, "sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.65.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.65.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.65.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.65.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.65.0", "", { "os": "linux", "cpu": "arm", "libc": "glibc" }, "sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.65.0", "", { "os": "linux", "cpu": "arm", "libc": "musl" }, "sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.65.0", "", { "os": "linux", "cpu": "arm64", "libc": "glibc" }, "sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.65.0", "", { "os": "linux", "cpu": "arm64", "libc": "musl" }, "sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.65.0", "", { "os": "linux", "cpu": "none", "libc": "glibc" }, "sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.65.0", "", { "os": "linux", "cpu": "none", "libc": "musl" }, "sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.65.0", "", { "os": "linux", "cpu": "ppc64", "libc": "glibc" }, "sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.65.0", "", { "os": "linux", "cpu": "ppc64", "libc": "musl" }, "sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.65.0", "", { "os": "linux", "cpu": "none", "libc": "glibc" }, "sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.65.0", "", { "os": "linux", "cpu": "none", "libc": "musl" }, "sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.65.0", "", { "os": "linux", "cpu": "s390x", "libc": "glibc" }, "sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.65.0", "", { "os": "linux", "cpu": "x64", "libc": "glibc" }, "sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.65.0", "", { "os": "linux", "cpu": "x64", "libc": "musl" }, "sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.65.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.65.0", "", { "os": "none", "cpu": "arm64" }, "sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.65.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.65.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.65.0", "", { "os": "win32", "cpu": "x64" }, "sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.65.0", "", { "os": "win32", "cpu": "x64" }, "sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.8", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA=="], + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.13", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ=="], "@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="], - "@sveltejs/kit": ["@sveltejs/kit@2.50.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA=="], + "@sveltejs/kit": ["@sveltejs/kit@2.70.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.9", "@types/cookie": "^0.6.0", "acorn": "^8.16.0", "cookie": "^0.6.0", "devalue": "^5.8.1", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-UDvEYuZqAMbfB/oXIoqKvbKcb7YczK5zYrzmsGV1zRJk03jntwp8dXiYoIJotxAndsKvcPFtx9H1GRSKFdSHgg=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="], @@ -144,11 +152,11 @@ "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn": ["acorn@8.19.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw=="], - "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -156,19 +164,19 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="], + "devalue": ["devalue@5.9.4", "", {}, "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], - "esrap": ["esrap@2.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ=="], + "esrap": ["esrap@2.4.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-CQPcneEHw/czj32uwQC52nYnax8H4SXt25FCM+LIwGZ49cMIxpOj3bP8wmzcI+Z0HIy5KkClaPhRLfCySzwHPA=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -192,13 +200,13 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + "rollup": ["rollup@4.65.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.65.0", "@rollup/rollup-android-arm64": "4.65.0", "@rollup/rollup-darwin-arm64": "4.65.0", "@rollup/rollup-darwin-x64": "4.65.0", "@rollup/rollup-freebsd-arm64": "4.65.0", "@rollup/rollup-freebsd-x64": "4.65.0", "@rollup/rollup-linux-arm-gnueabihf": "4.65.0", "@rollup/rollup-linux-arm-musleabihf": "4.65.0", "@rollup/rollup-linux-arm64-gnu": "4.65.0", "@rollup/rollup-linux-arm64-musl": "4.65.0", "@rollup/rollup-linux-loong64-gnu": "4.65.0", "@rollup/rollup-linux-loong64-musl": "4.65.0", "@rollup/rollup-linux-ppc64-gnu": "4.65.0", "@rollup/rollup-linux-ppc64-musl": "4.65.0", "@rollup/rollup-linux-riscv64-gnu": "4.65.0", "@rollup/rollup-linux-riscv64-musl": "4.65.0", "@rollup/rollup-linux-s390x-gnu": "4.65.0", "@rollup/rollup-linux-x64-gnu": "4.65.0", "@rollup/rollup-linux-x64-musl": "4.65.0", "@rollup/rollup-openbsd-x64": "4.65.0", "@rollup/rollup-openharmony-arm64": "4.65.0", "@rollup/rollup-win32-arm64-msvc": "4.65.0", "@rollup/rollup-win32-ia32-msvc": "4.65.0", "@rollup/rollup-win32-x64-gnu": "4.65.0", "@rollup/rollup-win32-x64-msvc": "4.65.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw=="], "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], @@ -208,7 +216,7 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "svelte": ["svelte@5.50.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-/Jlom4ddkISyVHXpM2O5dXP9pYnaiFrVQzPbIL1/pEoOa77ZunCb6nDgUCTNCQ/X3t64z9ukrK6R+BbB3kPR3A=="], + "svelte": ["svelte@5.57.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.6.0", "@sveltejs/acorn-typescript": "^1.0.13", "@types/estree": "^1.0.9", "acorn": "^8.18.0", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.9.2", "esm-env": "^1.2.1", "esrap": "^2.3.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-1YiPCAXT0Ct6g1vOMmSLa0Nsc3QRMvtDF32ihgglGVrugIuIK38RyDiFNXVDhtQ81TJjSO57NdMw6DyYLqSGDQ=="], "svelte-check": ["svelte-check@4.3.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": "bin/svelte-check" }, "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q=="], @@ -218,10 +226,20 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": "bin/vite.js" }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + "vite": ["vite@6.4.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg=="], "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "esrap/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "is-reference/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], } } diff --git a/web/package-lock.json b/web/package-lock.json index 356700d..f6939e5 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,11 +1,11 @@ { - "name": "hashprep-website", + "name": "tablint-website", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hashprep-website", + "name": "tablint-website", "version": "1.0.0", "devDependencies": { "@sveltejs/adapter-static": "^3.0.0", diff --git a/web/package.json b/web/package.json index 0223275..e3557a0 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "name": "hashprep-website", + "name": "tablint-website", "version": "1.0.0", "private": true, "type": "module", diff --git a/web/src/app.css b/web/src/app.css index f7eafb3..eec61ff 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,5 +1,5 @@ /* ============================================ - HashPrep Design System + Tablint Design System Primary accent: #b16286 (rose/mauve from logo) Typography: Epilogue (300-600) + DM Mono (300-500) ============================================ */ diff --git a/web/src/app.html b/web/src/app.html index 070ae8f..d12a77c 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -4,7 +4,7 @@ - + - diff --git a/web/src/lib/components/Navbar.svelte b/web/src/lib/components/Navbar.svelte index 84f885c..bbcecae 100644 --- a/web/src/lib/components/Navbar.svelte +++ b/web/src/lib/components/Navbar.svelte @@ -8,7 +8,7 @@ { label: 'Features', href: '/#features' }, { label: 'How It Works', href: '/#how-it-works' }, { label: 'Docs', href: '/docs' }, - { label: 'GitHub', href: 'https://github.com/cachevector/hashprep' } + { label: 'GitHub', href: 'https://github.com/cachevector/tablint' } ]; function closeMobile() { diff --git a/web/src/lib/theme.svelte.ts b/web/src/lib/theme.svelte.ts index 2e6f7d2..9c1fe38 100644 --- a/web/src/lib/theme.svelte.ts +++ b/web/src/lib/theme.svelte.ts @@ -27,7 +27,7 @@ function getSystemTheme(): Theme { function getStoredPreference(): 'light' | 'dark' | 'system' { if (!browser) return 'system'; - return (safeLocalStorage('get', 'hashprep-theme') as 'light' | 'dark' | 'system') || 'system'; + return (safeLocalStorage('get', 'tablint-theme') as 'light' | 'dark' | 'system') || 'system'; } let preference = $state<'light' | 'dark' | 'system'>(getStoredPreference()); @@ -47,7 +47,7 @@ function apply(t: Theme) { function setPreference(pref: 'light' | 'dark' | 'system') { preference = pref; if (browser) { - safeLocalStorage('set', 'hashprep-theme', pref); + safeLocalStorage('set', 'tablint-theme', pref); } apply(pref === 'system' ? getSystemTheme() : pref); } diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 41d439f..78df4b1 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -12,7 +12,7 @@ - HashPrep β€” Dataset Profiler & Debugger for Machine Learning + Tablint β€” Dataset Profiler & Debugger for Machine Learning {@render children()} diff --git a/web/src/routes/docs/+page.svelte b/web/src/routes/docs/+page.svelte index 13a93a2..952940d 100644 --- a/web/src/routes/docs/+page.svelte +++ b/web/src/routes/docs/+page.svelte @@ -9,9 +9,9 @@
Documentation
-

Learn HashPrep in minutes, not hours.

+

Learn Tablint in minutes, not hours.

- A focused guide to installing, running, and integrating HashPrep into your ML workflows. Built to match the + A focused guide to installing, running, and integrating Tablint into your ML workflows. Built to match the minimal, calm UI of the rest of the site.

@@ -42,7 +42,7 @@

Overview

- HashPrep is a dataset profiler and debugger for machine learning. Think of it as + Tablint is a dataset profiler and debugger for machine learning. Think of it as “Pandas Profiling + a linter for datasets” that runs before you train.

@@ -56,7 +56,7 @@

Installation

Using pip

-
pip install hashprep
+
pip install tablint
@@ -64,17 +64,17 @@
# Install uv (if needed)
 curl -LsSf https://astral.sh/uv/install.sh | sh
 
-# Install HashPrep
-uv pip install hashprep
+# Install Tablint
+uv pip install tablint
 
 # Or from source
-git clone https://github.com/cachevector/hashprep.git
-cd hashprep
+git clone https://github.com/cachevector/tablint.git
+cd tablint
 uv sync

- After installation, the hashprep command is available directly in your terminal. + After installation, the tablint command is available directly in your terminal.

@@ -83,7 +83,7 @@

1. Run a quick scan

Get a concise summary of dataset issues in your terminal.

-
hashprep scan dataset.csv
+
tablint scan dataset.csv
@@ -98,7 +98,7 @@

Example

-
hashprep scan train.csv \
+              
tablint scan train.csv \
   --target Survived \
   --checks outliers,high_missing_values,class_imbalance
@@ -106,8 +106,8 @@

2. Detailed analysis

-

Drill into every issue HashPrep found.

-
hashprep details train.csv --target Survived
+

Drill into every issue Tablint found.

+
tablint details train.csv --target Survived
@@ -117,7 +117,7 @@

Basic analysis

import pandas as pd
-from hashprep import DatasetAnalyzer
+from tablint import DatasetAnalyzer
 
 df = pd.read_csv("dataset.csv")
 
@@ -154,7 +154,7 @@ summary = analyzer.analyze()

Generate a report from the CLI

-
hashprep report dataset.csv --format html --theme minimal
+
tablint report dataset.csv --format html --theme minimal

Reports can be exported as md, json, html, or pdf, using the --format flag. Use --with-code to generate @@ -164,8 +164,8 @@ summary = analyzer.analyze()

Generate reports from Python

-
from hashprep import DatasetAnalyzer
-from hashprep.reports import generate_report
+            
from tablint import DatasetAnalyzer
+from tablint.reports import generate_report
 
 analyzer = DatasetAnalyzer(df, include_plots=True)
 summary = analyzer.analyze()
@@ -174,7 +174,7 @@ generate_report(
     summary,
     format="html",
     full=True,
-    output_file="dataset_hashprep_report.html",
+    output_file="dataset_tablint_report.html",
     theme="minimal",
 )
@@ -184,12 +184,12 @@ generate_report(

CLI reference

-

hashprep scan

+

tablint scan

Run a quick scan and print a compact summary of issues to the terminal. Ideal for a fast health check during development.

-
hashprep scan dataset.csv --target Survived
+
tablint scan dataset.csv --target Survived
  • --target COLUMN — target column for ML checks (class imbalance, leakage, etc.)
  • --checks LIST — comma-separated list of checks to run
  • @@ -203,12 +203,12 @@ generate_report(
-

hashprep details

+

tablint details

Produce a verbose, line-by-line description of each issue in the dataset, with statistics and recommendations.

-
hashprep details dataset.csv --target Survived
+
tablint details dataset.csv --target Survived

Accepts the same options as scan (including --comparison and --config) and is best used when you are actively debugging a dataset or deciding which columns to drop or transform. @@ -216,12 +216,12 @@ generate_report(

-

hashprep report

+

tablint report

Generate a full report in HTML, PDF, Markdown, or JSON. Reports contain summaries, plots, and (optionally) auto-generated Python code.

-
hashprep report dataset.csv --format html --theme minimal --with-code
+
tablint report dataset.csv --format html --theme minimal --with-code
  • --format {md,json,html,pdf} — output format (Markdown is the default)
  • --theme {minimal,neubrutalism} — HTML theme
  • @@ -235,7 +235,7 @@ generate_report(

    Exit codes

    - HashPrep is CI-friendly: non-zero exit codes indicate that critical issues were detected or an internal + Tablint is CI-friendly: non-zero exit codes indicate that critical issues were detected or an internal error occurred. You can wire this into pre-commit hooks or data-quality checks in your pipeline.

    @@ -250,7 +250,7 @@ generate_report( The core entry point for programmatic usage. It accepts a pandas DataFrame and optional configuration for targets, sampling, comparison datasets, and which checks to run.

    -
    from hashprep import DatasetAnalyzer
    +            
    from tablint import DatasetAnalyzer
     
     analyzer = DatasetAnalyzer(
         df,
    @@ -271,10 +271,10 @@ summary = analyzer.analyze()

    Sampling large datasets

    - For very large tables, you can control how many rows HashPrep inspects using a sampling configuration. + For very large tables, you can control how many rows Tablint inspects using a sampling configuration. This keeps runtimes predictable in production.

    -
    from hashprep.utils.sampling import SamplingConfig
    +            
    from tablint.utils.sampling import SamplingConfig
     
     sampling = SamplingConfig(max_rows=10000)
     
    @@ -293,12 +293,12 @@ summary = analyzer.analyze()

    Generating fix scripts and pipelines

    - Beyond reports, HashPrep can emit executable Python code that encodes suggested fixes and ML pipelines. + Beyond reports, Tablint can emit executable Python code that encodes suggested fixes and ML pipelines.

    -
    from hashprep.checks.core import Issue
    -from hashprep.preparers.codegen import CodeGenerator
    -from hashprep.preparers.pipeline_builder import PipelineBuilder
    -from hashprep.preparers.suggestions import SuggestionProvider
    +            
    from tablint.checks.core import Issue
    +from tablint.preparers.codegen import CodeGenerator
    +from tablint.preparers.pipeline_builder import PipelineBuilder
    +from tablint.preparers.suggestions import SuggestionProvider
     
     issues = [Issue(**i) for i in summary["issues"]]
     column_types = summary.get("column_types", dict())
    @@ -393,7 +393,7 @@ summary = analyzer.analyze()

    Configuration

    - Every detection threshold in HashPrep has a sensible default. You can override any of them at runtime by + Every detection threshold in Tablint has a sensible default. You can override any of them at runtime by providing a config file — no code changes required.

    @@ -402,7 +402,7 @@ summary = analyzer.analyze()

    Config files can be written in YAML (.yaml / .yml), TOML (.toml), or JSON (.json). Only the keys you specify are changed; everything else falls back to defaults.

    -
    # hashprep.yaml
    +            
    # tablint.yaml
     missing_values:
       warning: 0.3
       critical: 0.6
    @@ -417,19 +417,19 @@ mutual_info:
               

    Via CLI

    -
    hashprep scan dataset.csv \
    -  --config hashprep.yaml
    +              
    tablint scan dataset.csv \
    +  --config tablint.yaml
     
    -hashprep report dataset.csv \
    +tablint report dataset.csv \
       --format html \
    -  --config hashprep.toml
    + --config tablint.toml

    Via Python

    -
    from hashprep.utils.config_loader import load_config
    -from hashprep import DatasetAnalyzer
    +              
    from tablint.utils.config_loader import load_config
    +from tablint import DatasetAnalyzer
     
    -config = load_config("hashprep.yaml")
    +config = load_config("tablint.yaml")
     analyzer = DatasetAnalyzer(df, config=config)
     summary = analyzer.analyze()
    @@ -439,11 +439,11 @@ summary = analyzer.analyze()

    Contributing

    - HashPrep is open source and welcomes contributions. If you want to fix a bug, add a check, or improve the + Tablint is open source and welcomes contributions. If you want to fix a bug, add a check, or improve the reports:

      -
    • Read the contribution guide
    • +
    • Read the contribution guide
    • Open an issue describing the change you’d like to make
    • Create a pull request with clear motivation and tests where appropriate
    diff --git a/web/static/favicon.svg b/web/static/favicon.svg index 748d412..4488f92 100644 --- a/web/static/favicon.svg +++ b/web/static/favicon.svg @@ -1,9 +1,16 @@ - - - - - - - - + + + + + + + + + + + + + + +