Metadata-Version: 2.4
Name: osw-repair
Version: 0.1.0
Summary: Validate and iteratively repair OSW datasets with GenAI.
License-Expression: MIT
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai<4,>=2.0
Requires-Dist: pydantic<3,>=2.10
Requires-Dist: python-dotenv<2,>=1.0
Requires-Dist: python-osw-validation==0.5.0
Provides-Extra: dev
Requires-Dist: httpx<1,>=0.27; extra == "dev"
Requires-Dist: pytest<10,>=8; extra == "dev"
Dynamic: license-file

# osw-repair

Standalone Python package for validating and iteratively repairing
OpenSidewalks ZIP datasets.

The package contains archive handling, validator integration, constrained
repair planning, candidate validation, data models, and the `osw-repair` CLI.
It has no dependency on FastAPI, Uvicorn, or the web UI.

## Local install

For development, install directly from this directory in editable mode:

```bash
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
```

Set the model credentials when an invalid dataset needs model-assisted repair:

```dotenv
OPEN_ROUTER_KEY=your-key

# Optional overrides (defaults shown)
# OSW_MODEL_PROVIDER=openrouter
# OSW_MODEL=anthropic/claude-opus-4.5
# OSW_MODEL_REASONING_EFFORT=low
# OPEN_ROUTER_MODEL_FALLBACKS=anthropic/claude-haiku-4.5
```

Only `OPEN_ROUTER_KEY` is required for the default OpenRouter configuration.
Fallback model IDs are comma-separated and tried in order by OpenRouter.
`OSW_MODEL` also controls the model displayed in the UI; CLI `--model` overrides it.
For direct OpenAI access, set `OSW_MODEL_PROVIDER=openai`,
`OSW_MODEL=gpt-5.6`, and `OPENAI_API_KEY=your-key`. The legacy
`OPENAI_MODEL` setting remains supported when `OSW_MODEL` is unset.

## CLI

```bash
osw-repair dataset.zip \
  --output fixed-osw.zip \
  --max-iterations 10 \
  --max-errors 50
```

Structured debug traces are written to `fixed-osw.trace/` by default. Pass
`--trace-dir PATH` to choose another directory.

The command exits with `0` when the resulting dataset is valid, `2` when the
repair budget is exhausted, and `1` for input, configuration, or API errors.

## Python API

```python
from pathlib import Path

from osw_repair.engine import RepairEngine
from osw_repair.config import create_repairer
from osw_repair.validator import OSWValidator

engine = RepairEngine(
    validator=OSWValidator(max_errors=50),
    repairer=create_repairer(),
    max_iterations=10,
    trace_dir=Path("repair-traces"),
)
result = engine.run(Path("dataset.zip"), Path("fixed-osw.zip"))
print(result.iterations)
```

The model receives validation findings and a small dataset manifest, then reads
only affected features, schema constraints, and references through bounded tools.
Identities/references, feature structure, and existing required fields are enforced
as immutable in Python. Geometry is protected except for exact coordinate rounding
when validation explicitly reports a precision limit. Optional rejected properties
and precision-only geometry repairs are handled deterministically; other model
turns are limited to one focused issue. Near-miss endpoints and polygon vertices
may be snapped to nodes already referenced by the feature within a strict
`0.000001°` tolerance. Non-improving candidates are discarded.
For duplicate IDs, records with no meaningful tags (properties containing only
structural fields such as `_id`) are removed before tagged records. If this does
not select one record, exact duplicates may be collapsed and conflicting point
IDs are deduplicated only when every existing spatial reference uniquely supports
one canonical record. Ambiguous duplicates remain untouched.
An ID-less node is preserved when exactly one unresolved reference matches its
coordinates, allowing its missing ID to be restored from topology evidence. Without
that unique evidence, an untagged node is removed first, followed by features with
dangling references. Invalid conditionally required values are replaced only when
the compatible active-schema branches permit exactly one value; otherwise the
affected feature is removed. Mandatory properties are not deleted or guessed.

## Tests

```bash
pytest
```

Tests use fake validators and repairers and do not consume model tokens.

## Publishing to PyPI

Two GitHub Actions workflows publish only this directory. The wheel contains
only the `osw_repair` library and CLI. Both workflows run tests, check metadata
and distribution contents to exclude service and UI code, and smoke-test the
installed wheel before publishing.

| Branch | Workflow | Destination | Version |
| --- | --- | --- | --- |
| `dev` | `.github/workflows/publish-testpypi.yml` | TestPyPI | Base package version plus `.dev<run-number>`, such as `0.1.0.dev42` |
| `main` | `.github/workflows/publish-pypi.yml` | PyPI | Version committed in `osw-repair/pyproject.toml` |

Both workflows trigger on pushes to their branch, including PR merges, only
when files under `osw-repair/` change. Changes limited to the service, frontend,
or workflow files do not publish a package. GitHub Releases and tags do not
trigger publishing. Use branch protection to require PR merges if direct pushes
should be prohibited.

TestPyPI version changes happen only in the runner and are not committed back.
Before merging a production release into `main`, bump the package version in
`osw-repair/pyproject.toml` to an unused PyPI version. Registry versions cannot
be overwritten. Existing distribution files are skipped on reruns; a new
production version is still required to publish changed code.

One-time setup:

1. Create GitHub Actions environments named `Development` and `Production`
   under repository **Settings → Environments**. Restrict `Development` to
   the `dev` branch and `Production` to `main`.
2. Create a separate API token on each registry using its account settings.
   Use a project-scoped token for `osw-repair` when the project already exists;
   for an initial upload, use an account-scoped token, then replace it with a
   project-scoped token after the project is created.
3. Add an environment secret named `PYPI_API_TOKEN` to **each** GitHub
   environment, with the matching registry's token:

   | GitHub environment | Secret name | Secret value |
   | --- | --- | --- |
   | `Development` | `PYPI_API_TOKEN` | Token from TestPyPI |
   | `Production` | `PYPI_API_TOKEN` | Token from PyPI |

The publishing jobs use `${{ secrets.PYPI_API_TOKEN }}` from their selected
environment. TestPyPI and PyPI tokens are separate credentials. Trusted Publisher
configuration is not required for these token-based workflows. The package name
must be available or owned by the token's account on each registry.

After a successful production release, users can install the core package with:

```bash
python -m pip install osw-repair
```
