ortidy 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. ortidy-0.2.0/.claudeignore +21 -0
  2. ortidy-0.2.0/.gitattributes +3 -0
  3. ortidy-0.2.0/.github/dependabot.yml +20 -0
  4. ortidy-0.2.0/.github/workflows/ci.yml +36 -0
  5. ortidy-0.2.0/.github/workflows/docs.yml +30 -0
  6. ortidy-0.2.0/.github/workflows/publish.yml +35 -0
  7. ortidy-0.2.0/.gitignore +146 -0
  8. ortidy-0.2.0/.pre-commit-config.yaml +19 -0
  9. ortidy-0.2.0/.python-version +1 -0
  10. ortidy-0.2.0/CHANGELOG.md +71 -0
  11. ortidy-0.2.0/CITATION.cff +10 -0
  12. ortidy-0.2.0/CLAUDE.md +157 -0
  13. ortidy-0.2.0/CONTRIBUTING.md +55 -0
  14. ortidy-0.2.0/LICENSE.md +201 -0
  15. ortidy-0.2.0/PKG-INFO +126 -0
  16. ortidy-0.2.0/README.md +106 -0
  17. ortidy-0.2.0/RELEASING.md +56 -0
  18. ortidy-0.2.0/SECURITY.md +22 -0
  19. ortidy-0.2.0/binder/requirements.txt +6 -0
  20. ortidy-0.2.0/docs/api.md +39 -0
  21. ortidy-0.2.0/docs/index.md +49 -0
  22. ortidy-0.2.0/docs/result-shapes.md +55 -0
  23. ortidy-0.2.0/docs/solvers.md +30 -0
  24. ortidy-0.2.0/examples/binning.ipynb +154 -0
  25. ortidy-0.2.0/examples/travelling_salesperson.ipynb +110 -0
  26. ortidy-0.2.0/examples/vehicle_routing.ipynb +121 -0
  27. ortidy-0.2.0/mkdocs.yml +45 -0
  28. ortidy-0.2.0/ortidy/__init__.py +41 -0
  29. ortidy-0.2.0/ortidy/_narwhals.py +62 -0
  30. ortidy-0.2.0/ortidy/_scaling.py +58 -0
  31. ortidy-0.2.0/ortidy/assignment/__init__.py +5 -0
  32. ortidy-0.2.0/ortidy/assignment/assignment.py +101 -0
  33. ortidy-0.2.0/ortidy/binning/__init__.py +7 -0
  34. ortidy-0.2.0/ortidy/binning/bin_packing.py +122 -0
  35. ortidy-0.2.0/ortidy/binning/knapsack.py +80 -0
  36. ortidy-0.2.0/ortidy/binning/multi_knapsack.py +127 -0
  37. ortidy-0.2.0/ortidy/data/__init__.py +61 -0
  38. ortidy-0.2.0/ortidy/data/binning/bins.csv +6 -0
  39. ortidy-0.2.0/ortidy/data/binning/items_bin_packing.csv +12 -0
  40. ortidy-0.2.0/ortidy/data/binning/items_knapsack.csv +51 -0
  41. ortidy-0.2.0/ortidy/data/binning/items_multi.csv +16 -0
  42. ortidy-0.2.0/ortidy/data/routing/locations.csv +18 -0
  43. ortidy-0.2.0/ortidy/data/routing/pickups_and_deliveries.csv +9 -0
  44. ortidy-0.2.0/ortidy/data/routing/vehicles.csv +5 -0
  45. ortidy-0.2.0/ortidy/facility/__init__.py +5 -0
  46. ortidy-0.2.0/ortidy/facility/facility_location.py +140 -0
  47. ortidy-0.2.0/ortidy/flow/__init__.py +5 -0
  48. ortidy-0.2.0/ortidy/flow/flow.py +198 -0
  49. ortidy-0.2.0/ortidy/result.py +98 -0
  50. ortidy-0.2.0/ortidy/routing/__init__.py +6 -0
  51. ortidy-0.2.0/ortidy/routing/distance.py +92 -0
  52. ortidy-0.2.0/ortidy/routing/routing.py +318 -0
  53. ortidy-0.2.0/ortidy/scheduling/__init__.py +5 -0
  54. ortidy-0.2.0/ortidy/scheduling/shift_scheduling.py +152 -0
  55. ortidy-0.2.0/ortidy/schema.py +41 -0
  56. ortidy-0.2.0/pyproject.toml +80 -0
  57. ortidy-0.2.0/tests/__init__.py +0 -0
  58. ortidy-0.2.0/tests/conftest.py +29 -0
  59. ortidy-0.2.0/tests/test_assignment.py +60 -0
  60. ortidy-0.2.0/tests/test_bin_packing.py +58 -0
  61. ortidy-0.2.0/tests/test_distance.py +49 -0
  62. ortidy-0.2.0/tests/test_facility.py +63 -0
  63. ortidy-0.2.0/tests/test_flow.py +78 -0
  64. ortidy-0.2.0/tests/test_knapsack.py +69 -0
  65. ortidy-0.2.0/tests/test_multi_knapsack.py +70 -0
  66. ortidy-0.2.0/tests/test_result.py +32 -0
  67. ortidy-0.2.0/tests/test_routing.py +76 -0
  68. ortidy-0.2.0/tests/test_routing_extensions.py +71 -0
  69. ortidy-0.2.0/tests/test_scaling.py +28 -0
  70. ortidy-0.2.0/tests/test_scheduling.py +73 -0
  71. ortidy-0.2.0/uv.lock +3385 -0
@@ -0,0 +1,21 @@
1
+ # Files Claude Code should skip when reading/searching the project.
2
+ # Generated, vendored, or noisy artifacts — not useful as context.
3
+
4
+ # Virtual environments
5
+ .venv/
6
+
7
+ # Build artifacts
8
+ dist/
9
+ build/
10
+ site/
11
+ *.egg-info/
12
+
13
+ # Tooling caches
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .mypy_cache/
17
+ .uv_cache/
18
+ __pycache__/
19
+
20
+ # Lockfile (large, machine-generated)
21
+ uv.lock
@@ -0,0 +1,3 @@
1
+ * text=auto eol=lf
2
+ *.csv text eol=lf
3
+ *.ipynb text eol=lf
@@ -0,0 +1,20 @@
1
+ version: 2
2
+ updates:
3
+ # Python dependencies (uv / pyproject.toml + uv.lock)
4
+ - package-ecosystem: "uv"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ groups:
9
+ python-dependencies:
10
+ patterns: ["*"]
11
+ open-pull-requests-limit: 5
12
+
13
+ # GitHub Actions used in workflows
14
+ - package-ecosystem: "github-actions"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ groups:
19
+ actions:
20
+ patterns: ["*"]
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, windows-latest]
15
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v7
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: uv sync
26
+
27
+ - name: Lint
28
+ run: |
29
+ uv run ruff check .
30
+ uv run ruff format --check .
31
+
32
+ - name: Type-check
33
+ run: uv run mypy ortidy
34
+
35
+ - name: Test
36
+ run: uv run pytest -q
@@ -0,0 +1,30 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: write # mkdocs gh-deploy pushes to the gh-pages branch
10
+
11
+ jobs:
12
+ docs:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+
17
+ - uses: astral-sh/setup-uv@v7
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - run: uv sync --group docs
22
+
23
+ # Always validate the docs build (strict catches broken refs / nav).
24
+ - name: Build (strict)
25
+ run: uv run mkdocs build --strict
26
+
27
+ # Publish to GitHub Pages only from main.
28
+ - name: Deploy to GitHub Pages
29
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
30
+ run: uv run mkdocs gh-deploy --force
@@ -0,0 +1,35 @@
1
+ name: Publish
2
+
3
+ # Build and publish ortidy to PyPI when a GitHub Release is published.
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token is stored in the repo.
5
+ #
6
+ # One-time PyPI setup (see RELEASING.md):
7
+ # https://pypi.org/manage/account/publishing/ → add a "pending publisher":
8
+ # PyPI project: ortidy
9
+ # Owner: lucasjamar Repository: ortidy
10
+ # Workflow: publish.yml Environment: pypi
11
+
12
+ on:
13
+ release:
14
+ types: [published]
15
+ workflow_dispatch: # allow a manual run from the Actions tab
16
+
17
+ jobs:
18
+ publish:
19
+ runs-on: ubuntu-latest
20
+ environment: pypi
21
+ permissions:
22
+ id-token: write # required for Trusted Publishing
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v7
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: Build sdist + wheel
32
+ run: uv build
33
+
34
+ - name: Publish to PyPI (Trusted Publishing)
35
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,146 @@
1
+ ##########################
2
+ # Common files
3
+
4
+ # IntelliJ
5
+ .idea/
6
+ *.iml
7
+ out/
8
+ .idea_modules/
9
+
10
+ ### macOS
11
+ *.DS_Store
12
+ .AppleDouble
13
+ .LSOverride
14
+ .Trashes
15
+
16
+ # Vim
17
+ *~
18
+ .*.swo
19
+ .*.swp
20
+
21
+ # emacs
22
+ *~
23
+ \#*\#
24
+ /.emacs.desktop
25
+ /.emacs.desktop.lock
26
+ *.elc
27
+
28
+ # JIRA plugin
29
+ atlassian-ide-plugin.xml
30
+
31
+ # C extensions
32
+ *.so
33
+
34
+ ### Python template
35
+ # Byte-compiled / optimized / DLL files
36
+ __pycache__/
37
+ *.py[cod]
38
+ *$py.class
39
+
40
+ # Distribution / packaging
41
+ .Python
42
+ build/
43
+ develop-eggs/
44
+ dist/
45
+ downloads/
46
+ eggs/
47
+ .eggs/
48
+ lib/
49
+ lib64/
50
+ parts/
51
+ sdist/
52
+ var/
53
+ wheels/
54
+ *.egg-info/
55
+ .installed.cfg
56
+ *.egg
57
+ MANIFEST
58
+
59
+ # PyInstaller
60
+ # Usually these files are written by a python script from a template
61
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
62
+ *.manifest
63
+ *.spec
64
+
65
+ # Installer logs
66
+ pip-log.txt
67
+ pip-delete-this-directory.txt
68
+
69
+ # Unit test / coverage reports
70
+ htmlcov/
71
+ .tox/
72
+ .coverage
73
+ .coverage.*
74
+ .cache
75
+ nosetests.xml
76
+ coverage.xml
77
+ *.cover
78
+ .hypothesis/
79
+
80
+ # Translations
81
+ *.mo
82
+ *.pot
83
+
84
+ # Django stuff:
85
+ *.log
86
+ .static_storage/
87
+ .media/
88
+ local_settings.py
89
+
90
+ # Flask stuff:
91
+ instance/
92
+ .webassets-cache
93
+
94
+ # Scrapy stuff:
95
+ .scrapy
96
+
97
+ # Sphinx documentation
98
+ docs/_build/
99
+
100
+ # PyBuilder
101
+ target/
102
+
103
+ # Jupyter Notebook
104
+ .ipynb_checkpoints
105
+
106
+ # IPython
107
+ .ipython/profile_default/history.sqlite
108
+ .ipython/profile_default/startup/README
109
+
110
+ # pyenv
111
+ .python-version
112
+
113
+ # celery beat schedule file
114
+ celerybeat-schedule
115
+
116
+ # SageMath parsed files
117
+ *.sage.py
118
+
119
+ # Environments
120
+ .env
121
+ .venv
122
+ env/
123
+ venv/
124
+ ENV/
125
+ env.bak/
126
+ venv.bak/
127
+
128
+ # mkdocs documentation
129
+ /site
130
+
131
+ # mypy
132
+ .mypy_cache/
133
+ dist/
134
+ site/
135
+
136
+ # uv selects the dev interpreter from this file — keep it tracked
137
+ !.python-version
138
+
139
+ ##########################
140
+ # ortidy / tooling
141
+ .pytest_cache/
142
+ .ruff_cache/
143
+ .mypy_cache/
144
+ .uv_cache/
145
+ site/
146
+ dist/
@@ -0,0 +1,19 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/kynan/nbstripout
10
+ rev: 0.7.1
11
+ hooks:
12
+ - id: nbstripout
13
+
14
+ - repo: https://github.com/pre-commit/mirrors-mypy
15
+ rev: v1.11.2
16
+ hooks:
17
+ - id: mypy
18
+ additional_dependencies: [narwhals]
19
+ files: ^ortidy/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.0] - 2026-06-26 - Renamed to `ortidy`, rebuilt on Narwhals
8
+
9
+ ### Added
10
+ * **Assignment & facility location** (assignment-matrix shape): `assignment`
11
+ (linear sum assignment) and `facility_location` (uncapacitated).
12
+ * **Network flow** (edge-flow shape): `max_flow`, `min_cost_flow`, `shortest_path`.
13
+ * **Shift scheduling** (`shift_scheduling`) — the interval-schedule result shape.
14
+ * **Routing promises**: VRPTW (time windows) and pickups & deliveries in
15
+ `solve_routing`, plus a `distance_matrix` helper (euclidean / haversine) so
16
+ routing can take locations instead of a precomputed matrix.
17
+ * mkdocs-material documentation.
18
+
19
+ ### Changed
20
+ * **Renamed `pandas-or` → `ortidy`** ("OR on tidy dataframes"). The importable
21
+ module is now `ortidy`; the old `pandas-or` 0.1.3 on PyPI is left unmaintained.
22
+ * **Backend-agnostic** via Narwhals: pandas in → pandas out, Polars in → Polars
23
+ out. pandas remains the reference backend.
24
+ * Every solver returns a structured **`SolveResult`** (frame, status, objective,
25
+ metadata) instead of a bare frame / `None`. A `FEASIBLE` solution is a success.
26
+ * Rows are identified by **explicit id columns**, never a positional index.
27
+
28
+ ### Fixed
29
+ * `knapsack`: migrated to the modern `ortools.algorithms.python.knapsack_solver`
30
+ import and honest return type; added float→int unit-scaling.
31
+ * `multi_knapsack` / `bin_packing`: rebuilt on CP-SAT, removing the per-row
32
+ `apply(..BoolVar.., axis=1)` variable-construction anti-pattern.
33
+ * `solve_routing`: removed the phantom final route row and the dead
34
+ `"capacity" in column_names` branch; promoted the hardcoded `3000` / `100` /
35
+ `1s` magic numbers to parameters.
36
+ * Replaced the always-failing `__version__ == "0.1.0"` test with a real suite
37
+ (correctness, golden-file, infeasible-status, backend-parity).
38
+
39
+ ### Tooling
40
+ * Python floor raised to 3.10; **uv** for dependency management (PEP 735
41
+ `[dependency-groups]`) with **hatchling** as the build backend; `ruff`, `mypy`,
42
+ `pre-commit`, and GitHub Actions CI (test matrix + strict docs build).
43
+ * Depend on `ortools>=9.12` (current 9.15), supporting Python 3.10–3.14.
44
+ * Stripped ~8 MB of embedded notebook output.
45
+
46
+ ## [0.1.3] - 2022-08-07
47
+
48
+ ### Fixed
49
+ * Replaced `operations research` with `optimization research` in docs.
50
+
51
+ ## [0.1.2] - 2022-08-07
52
+
53
+ ### Fixed
54
+ * Fixed multi_knapsack to assign each item to only one bin.
55
+ * Added separate dataset for multi_knapsack and bin_packing compared to knapsack.
56
+
57
+ ### Additions
58
+ * Added docstrings.
59
+ * Added CITATION.cff
60
+
61
+ ## [0.1.1] - 2022-08-04
62
+
63
+ ### Fixed
64
+ * Added binder requirements.
65
+ * Filtering of bin_packing with resetting of binId.
66
+
67
+ ## [0.1.0] - 2022-07-31 - Initial release
68
+
69
+ ### Added
70
+ * Functions for solving knapsack type problems.
71
+ * Basic vehicle routing solving functions.
@@ -0,0 +1,10 @@
1
+ cff-version: 1.2.0
2
+ title: ortidy
3
+ version: 0.2.0
4
+ date-released: 2026-06-26
5
+ url: https://github.com/lucasjamar/ortidy
6
+ message: 'If you use this software, please cite it as below.'
7
+ type: software
8
+ authors:
9
+ - given-names: Lucas
10
+ family-names: Jamar
ortidy-0.2.0/CLAUDE.md ADDED
@@ -0,0 +1,157 @@
1
+ # CLAUDE.md — ortidy
2
+
3
+ Guidance for Claude Code working in this repository. Read this fully before making changes.
4
+
5
+ > **Naming note:** this project is the revival of `pandas-or` (PyPI, last release `0.1.3`, dormant ~4 years), renamed to **`ortidy`** to reflect its new backend-agnostic design. "ortidy" = **OR** (operations research) on **tidy** dataframes. See *Renaming & migration* below for the mechanics.
6
+
7
+ ## What this project is
8
+
9
+ `ortidy` is a dataframe-native façade over [Google OR-Tools](https://developers.google.com/optimization). The thesis: bridge operations research and the data ecosystem so that solver outputs come back as tidy dataframes ready for analysis, plotting, and dashboards.
10
+
11
+ **Core principle to internalize:** the dataframe is *glue*, not the compute engine. All heavy lifting happens inside OR-Tools (C++). Our code does input validation, light reshaping (joins, group-bys), the solver handoff, and result assembly. Do not optimize dataframe operations for speed — the bottleneck is always the solve. Optimize for *clarity, correctness, and a consistent API*.
12
+
13
+ ---
14
+
15
+ ## Hard decisions already made (do not relitigate)
16
+
17
+ 1. **Dataframe backend: Narwhals, not raw pandas or Polars.**
18
+ The library must be backend-agnostic. Use [Narwhals](https://narwhals-dev.github.io/narwhals/) as the internal layer so a user who passes pandas gets pandas back, a user who passes Polars gets Polars back. pandas remains the *default/reference* backend. Polars, pyarrow, etc. come for free.
19
+ - Import the stable namespace: `import narwhals.stable.v1 as nw`. This carries a backward-compatibility guarantee and shields us from upstream pandas/Polars churn — the exact rot that killed the original.
20
+ - Accept native frames at the public boundary, convert with `nw.from_native(...)`, do internal work in Narwhals expressions, return with `.to_native()`. The `@nw.narwhalify` decorator is acceptable for simple functions.
21
+ - At the **solver boundary**, extract plain Python lists/ints/floats from columns. OR-Tools does not consume dataframes; this handoff is backend-neutral by design.
22
+
23
+ 2. **No implicit pandas index.** Narwhals follows the Polars index-free model. Identify rows with explicit ID columns (`itemId`, `binId`, `vehicleId`, `locationId`, …), never with a positional index. This is also required by the return contract below.
24
+
25
+ 3. **Name: `ortidy`** (renamed from `pandas-or`). The importable module is `ortidy` (`import ortidy`). The old `pandas-or` 0.1.3 stays on PyPI unmaintained (not deleted). Update all references, badges, and the module folder accordingly.
26
+
27
+ 4. **Modern solver choice:** prefer **CP-SAT** (`from ortools.sat.python import cp_model`) for packing/assignment/scheduling models over the legacy `pywraplp`/SCIP path. CP-SAT is Google's recommended modern solver and removes the variable-construction mess in the old code. Keep the dedicated routing library (`ortools.constraint_solver.pywrapcp`) for routing, and the dedicated graph solvers for flow.
28
+
29
+ ---
30
+
31
+ ## Renaming & migration (pandas-or → ortidy)
32
+
33
+ The two platforms behave differently — handle each correctly.
34
+
35
+ - **GitHub: rename the existing repo in place. Do NOT start a fresh repo.** Renaming preserves history, stars, issues, and watchers, and GitHub auto-redirects the old URL (web, clones, fetches, API) to the new name. Redirects break only if a new repo is later created at the old path — so don't reuse it.
36
+ - Before the rewrite lands, preserve the old version: tag it (`v0.1.3-legacy`) and/or cut a `legacy/0.1.x` branch. Do the rewrite on `main`.
37
+ - **PyPI: there is no rename feature — register `ortidy` as a new project.** Publish the new code under `ortidy`. **Decision:** *no* `pandas-or` deprecation/redirect shim is shipped — `pandas-or` had effectively no adoption, so a redirect shim isn't worth the maintenance. **Do not delete** the old `pandas-or` 0.1.3 project (deleting frees the name for typosquatters); leave it as-is. Its project page links to this GitHub repo, which redirects to `ortidy`.
38
+ - **Availability:** `ortidy` is confirmed free on PyPI and as an import name. If the name is ever changed again, re-check PyPI, the importable module name, GitHub, and Read the Docs *before* committing.
39
+ - **Surfaces to update during the rename:** `pyproject.toml` (name, urls), the module folder `pandas_or/` → `ortidy/`, `CITATION.cff`, README + badges, Read the Docs project, Binder links, and any `import pandas_or` in examples/notebooks.
40
+
41
+ ---
42
+
43
+ ## Design contracts (apply to every solver)
44
+
45
+ These are the through-lines that make this a coherent library rather than a bag of functions.
46
+
47
+ ### Consistent return contract
48
+ Every solver returns the user's original frame (same backend, same rows where applicable) with assignment columns added, plus a status. There are exactly **three result shapes** — design every feature to fit one of them:
49
+ - **assignment-matrix** — rows mapped to columns/bins/resources (knapsack, multi-knapsack, bin packing, assignment, transportation, facility location).
50
+ - **edge-flow** — values on an edge list (max-flow, min-cost-flow, shortest path, transshipment).
51
+ - **interval-schedule** — intervals placed on a timeline (shift scheduling, job-shop, RCPSP).
52
+
53
+ Do **not** invent a fourth shape without flagging it for discussion first.
54
+
55
+ ### Result object, not print-and-None
56
+ The old code does `print("no optimal solution")` and implicitly returns `None`. Replace everywhere with a structured result carrying: the result frame, a status enum (`OPTIMAL` / `FEASIBLE` / `INFEASIBLE` / `UNBOUNDED` / `MODEL_INVALID`), objective value, and solve metadata (wall time, gap). **A `FEASIBLE` solution is a success, not a failure** — the old code wrongly discards anything that isn't `OPTIMAL`.
57
+
58
+ ### Functional API (the canonical interface)
59
+ Solvers are plain functions: pass a native frame, get a `SolveResult` back in the
60
+ same backend, e.g. `ortidy.knapsack(df, capacity=100)` and
61
+ `ortidy.solve_routing(distances, vehicles=...)`. This is the canonical, fully
62
+ backend-agnostic surface — it works for *any* Narwhals backend.
63
+
64
+ > **Decision:** the `.or_` dataframe accessor was **removed**. It only worked on
65
+ > backends we explicitly registered (pandas, Polars) — undercutting the
66
+ > backend-agnostic promise — and mutated the global pandas/Polars namespace at
67
+ > import time. The functional API is the single, portable interface. If a
68
+ > chainable surface is wanted later, prefer an explicit, side-effect-free wrapper
69
+ > (`ortidy.wrap(df).knapsack(...)`) over a monkeypatch accessor.
70
+
71
+ ### Schema validation
72
+ Standardize input validation. The old ad-hoc `if not {"value","weight"}.issubset(...)` pattern is a good instinct — formalize it with [Pandera](https://pandera.readthedocs.io/) (which supports Narwhals/Polars) or explicit schema checks. Raise precise, actionable errors (which column is missing, what dtype was expected). Prefer `ValueError`/`KeyError` over the `AttributeError` the old code raises.
73
+
74
+ ### Surface solver controls
75
+ No magic numbers buried in function bodies. Expose `time_limit`, `random_seed`/determinism, and optimality `gap` as parameters with sensible defaults. (The old routing code hardcodes `3000` max distance, `100` span coefficient, `1`s time limit — these must become parameters.)
76
+
77
+ ### Numeric data handling
78
+ OR-Tools knapsack requires integer values/weights; real data has floats. Provide a unit-scaling layer (scale floats to ints, solve, unscale) rather than silently passing floats through.
79
+
80
+ ### Infeasibility diagnostics
81
+ When there is no solution, report *why* where the solver allows it (e.g. CP-SAT sufficient-assumptions-for-infeasibility, or an irreducible infeasible subset). This is a genuine differentiator — most libraries in this niche just say "no solution."
82
+
83
+ ---
84
+
85
+ ## Roadmap (phased — work in this order)
86
+
87
+ ### P0 — Unbreak, rename, modernize (do first, no new features)
88
+ - **Execute the rename** per *Renaming & migration*: rename the GitHub repo, tag/branch the legacy version, rename the module folder `pandas_or/` → `ortidy/`, update `pyproject.toml`/`CITATION.cff`/README/badges, and register `ortidy` on PyPI (no `pandas-or` shim).
89
+ - **Fix the dead knapsack import.** Current OR-Tools moved the knapsack solver to `from ortools.algorithms.python import knapsack_solver` with snake_case methods. Verify exact method casing (`init` / `solve` / `best_solution_contains`) against the *installed* ortools version before committing.
90
+ - **Fix the broken test.** `tests/` asserts `__version__ == '0.1.0'` while the package was `0.1.3` — it has always failed. Replace with a real suite (see Testing).
91
+ - **Fix known bugs:**
92
+ - `knapsack` return type lies: docstring/annotation says `pd.Series` but `items.index.map(...)` returns an `Index`. Align with the new return contract.
93
+ - `single_vehicle_route` appends a phantom final row after the loop using an out-of-range index — drop it.
94
+ - `solve_routing` has a dead `if "capacity" in column_names` branch that checks a distance matrix's columns for a capacity field that cannot be there — remove.
95
+ - **Modernize packaging/tooling:** Python floor to 3.10+; PEP 621 `[project]` metadata with **uv** as the package/dependency manager (PEP 735 `[dependency-groups]`) and **hatchling** as the build backend; unpin to current `ortools` (`>=9.12`), add `narwhals`; add `ruff` (lint+format, replacing black-only), `mypy`, `pre-commit`; add GitHub Actions CI; add `nbstripout` and strip the embedded notebook output bloating the repo.
96
+ - **Migrate the working solvers to Narwhals + the return contract** without changing their math: `knapsack`, `multi_knapsack`, `bin_packing`, `solve_routing`. While here, kill the `items.apply(lambda x: solver.BoolVar(...), axis=1)` cross-product variable-construction anti-pattern (rebuild on CP-SAT).
97
+
98
+ ### P1 — API consistency + new shapes (cheap, high-value)
99
+ - **Assignment & transportation** (assignment-matrix shape). Linear assignment via `ortools.graph.python.linear_sum_assignment`; generalized assignment (GAP) and transportation/transshipment via CP-SAT/MIP. A cost matrix *is* a dataframe — the most natural fit and the cheapest big win.
100
+ - **Network flow** (edge-flow shape). Max-flow, min-cost-flow, shortest path via the dedicated `ortools.graph.python` solvers. Naturally an edge-list frame.
101
+ - **Finish the routing promises** already advertised in the old README: **time windows (VRPTW)** and **pickups & deliveries**. Also expose multiple depots and heterogeneous fleets.
102
+ - **Separate distance-matrix construction from routing.** The old API secretly requires `df` to be a precomputed distance matrix. Add a helper that builds the matrix from lat/long (haversine) or x/y (euclidean) so the routing API takes *locations* — what users actually have.
103
+ - Ship the **result object** across all P0/P1 solvers. (The `.or_` accessor was removed — see *Functional API* above.)
104
+
105
+ ### P2 — Flagship + polish
106
+ - **Scheduling / rostering** (interval-schedule shape) on CP-SAT: shift scheduling (the long-promised feature), job-shop/flow-shop, RCPSP. Build this only after the result-object design has proven itself on P1.
107
+ - **Facility location & covering:** facility location, p-median, set cover/partition.
108
+ - **Docs:** stand up `mkdocs-material` (the README has promised "Read The Docs coming soon" for four years). Document the three result shapes as the conceptual spine.
109
+ - Optional routing extras: optional visits / prize-collecting, backhauls.
110
+
111
+ ---
112
+
113
+ ## What to preserve from the existing code
114
+
115
+ - `pandas_or/binning/knapsack.py` — cleanest file; keep its structure and docstring style, fix the bugs above.
116
+ - The column-validation instinct — formalize, don't discard.
117
+ - The **route feature-engineering** (`tripsSinceStart`, `tripsTillEnd`, `distanceSinceStart`, `distanceTillEnd`) — the most original idea in the repo and the clearest expression of the OR-meets-analytics thesis. Reimplement on Narwhals `over` expressions (grouped `shift`, `cum_sum`); keep the concept.
118
+ - The sample CSVs under `pandas_or/data/` (move to `ortidy/data/`) and the Binder examples — good onboarding assets and the basis for golden-file tests.
119
+ - The Parameters/Returns/Link docstring convention.
120
+
121
+ ---
122
+
123
+ ## Conventions
124
+
125
+ - **Style:** `ruff format` + `ruff check`. Type-annotate all public functions; `mypy` must pass.
126
+ - **Types:** annotate honestly. If a function returns the result object, say so — no more `pd.Series` annotations on things that aren't.
127
+ - **Errors:** precise and actionable. Name the missing column and expected dtype. Prefer `ValueError`/`KeyError` over `AttributeError`.
128
+ - **No magic numbers** in function bodies — promote to named parameters with documented defaults.
129
+ - **Determinism:** plumb a `random_seed` through solvers that support it so tests and examples are reproducible.
130
+ - **Public API** is re-exported from `ortidy/__init__.py` and listed in `__all__`; keep it curated.
131
+
132
+ ## Testing
133
+
134
+ - `pytest`, run via `uv run pytest`.
135
+ - Every solver needs: a correctness test (assert the math), a **golden-file test** against the bundled sample CSVs, an **infeasible-input test** (assert the right status, not an exception), and a **backend-parity test** (same input as pandas *and* Polars yields equivalent results — this is the whole point of Narwhals).
136
+ - Assert solver *status*, not just output shape. Cover the `FEASIBLE`-is-success path explicitly.
137
+
138
+ ## Dev commands
139
+
140
+ ```bash
141
+ uv sync # install runtime + dev deps into .venv (uses .python-version = 3.12)
142
+ uv sync --group docs # also install the docs toolchain
143
+ uv run pytest
144
+ uv run ruff check . && uv run ruff format --check .
145
+ uv run mypy ortidy
146
+ uv run pre-commit run --all-files
147
+ uv build # build sdist + wheel (hatchling)
148
+ ```
149
+
150
+ ## Guardrails for Claude Code
151
+
152
+ - Do not reintroduce a hard pandas dependency in solver logic — go through the Narwhals layer.
153
+ - Do not pin OR-Tools to an old version to make old import paths work; migrate to current imports instead.
154
+ - Do not add a result shape beyond the three above without raising it first.
155
+ - When unsure whether a solver returned its best answer, prefer accepting `FEASIBLE` over rejecting it.
156
+ - Verify any OR-Tools import path and method casing against the installed version — the bindings have drifted and will drift again.
157
+ - Do not start a fresh GitHub repo for the rename — rename in place to preserve history and redirects.
@@ -0,0 +1,55 @@
1
+ # Contributing to ortidy
2
+
3
+ Thanks for your interest! `ortidy` is a backend-agnostic dataframe façade over
4
+ Google OR-Tools. Please read [CLAUDE.md](CLAUDE.md) for the design principles
5
+ before proposing larger changes.
6
+
7
+ ## Development setup
8
+
9
+ `ortidy` uses [uv](https://docs.astral.sh/uv/). The dev interpreter is pinned by
10
+ `.python-version` (3.12).
11
+
12
+ ```bash
13
+ uv sync # runtime + dev dependencies
14
+ uv sync --group docs # also the docs toolchain
15
+ ```
16
+
17
+ ## The checks (all must pass)
18
+
19
+ ```bash
20
+ uv run pytest # tests
21
+ uv run ruff check . && uv run ruff format --check . # lint + format
22
+ uv run mypy ortidy # types
23
+ uv run --group docs mkdocs build --strict # docs
24
+ ```
25
+
26
+ `uv run pre-commit run --all-files` runs ruff, nbstripout, and mypy locally.
27
+
28
+ ## Design contracts
29
+
30
+ These are the through-lines that keep `ortidy` coherent — match them:
31
+
32
+ - **Three result shapes.** Every solver returns *assignment-matrix*, *edge-flow*,
33
+ or *interval-schedule* (see [docs/result-shapes.md](docs/result-shapes.md)).
34
+ Don't add a fourth without raising it first.
35
+ - **One result object.** Solvers return a `SolveResult` (frame, status, objective,
36
+ metadata); a `FEASIBLE` solution is a success.
37
+ - **Backend-agnostic.** Go through the Narwhals layer (`ortidy/_narwhals.py`);
38
+ accept native frames in, return the same backend out, extract plain Python at
39
+ the solver boundary. No hard pandas dependency in solver logic.
40
+ - **Explicit id columns**, never a positional index.
41
+ - **Verify OR-Tools import paths/method casing against the installed version** —
42
+ the bindings drift.
43
+
44
+ ## Adding a solver
45
+
46
+ 1. Validate inputs with `ortidy/schema.py` (precise `ValueError`/`KeyError`).
47
+ 2. Return a `SolveResult`; expose `time_limit` / `random_seed` where supported.
48
+ 3. Add it to `ortidy/__init__.py` `__all__` and the docs.
49
+ 4. Tests: correctness, golden-file (bundled CSVs), infeasible-status, and
50
+ pandas + Polars backend-parity.
51
+
52
+ ## Pull requests
53
+
54
+ Keep PRs focused; make sure the checks above are green. Update `CHANGELOG.md`
55
+ under an `## [Unreleased]` heading.