matchlab 0.1.0.dev1__tar.gz → 0.1.0.dev3__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.
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/PKG-INFO +9 -7
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/README.md +8 -6
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/topologies.py +25 -27
- matchlab-0.1.0.dev1/docs/api/models.md → matchlab-0.1.0.dev3/docs/api/document.md +1 -1
- matchlab-0.1.0.dev3/docs/api/locations.md +46 -0
- matchlab-0.1.0.dev3/docs/api/models.md +46 -0
- matchlab-0.1.0.dev3/docs/api/resources.md +12 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/sources.md +5 -1
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/contributing.md +9 -3
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/glossary.md +48 -6
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/guide/build-a-plan.md +72 -31
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/guide/evaluate.md +20 -23
- matchlab-0.1.0.dev3/docs/guide/install.md +21 -0
- matchlab-0.1.0.dev3/docs/guide/serialise.md +183 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/index.md +6 -6
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/migration/matchbox-to-matchlab.md +73 -23
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/mkdocs.yml +4 -1
- matchlab-0.1.0.dev3/src/matchlab/__init__.py +98 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/exceptions.py +4 -4
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/hash.py +10 -8
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/document.py +111 -82
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/lineage.py +4 -4
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/dedupers/base.py +9 -10
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/dedupers/naive.py +7 -13
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/linkers/base.py +12 -13
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/linkers/deterministic.py +14 -22
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/linkers/splinklinker.py +24 -31
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/linkers/weighteddeterministic.py +9 -26
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/models.py +76 -31
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/recordstep.py +15 -6
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/resolvers/__init__.py +1 -2
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/resolvers/base.py +11 -10
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/resolvers/components.py +7 -14
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/resolvers/resolvers.py +62 -24
- matchlab-0.1.0.dev3/src/matchlab/resources.py +153 -0
- matchlab-0.1.0.dev3/src/matchlab/sources/__init__.py +31 -0
- matchlab-0.1.0.dev3/src/matchlab/sources/base.py +50 -0
- matchlab-0.1.0.dev3/src/matchlab/sources/dataframe.py +50 -0
- matchlab-0.1.0.dev3/src/matchlab/sources/relational.py +97 -0
- {matchlab-0.1.0.dev1/src/matchlab → matchlab-0.1.0.dev3/src/matchlab/sources}/sources.py +203 -60
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/specs.py +17 -18
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/steps.py +224 -20
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/linked.py +15 -24
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/matchers.py +6 -18
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/sources.py +26 -23
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/base.py +28 -1
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/clean.py +7 -3
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/group.py +2 -1
- matchlab-0.1.0.dev3/src/matchlab/transformers/transform.py +169 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/PKG-INFO +9 -7
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/SOURCES.txt +14 -5
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/scm_file_list.json +14 -5
- matchlab-0.1.0.dev3/src/matchlab.egg-info/scm_version.json +8 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/top_level.txt +1 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/conftest.py +15 -8
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/core/test_hash.py +43 -50
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/test_dedupers_deterministic.py +3 -4
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/test_linkers_deterministic.py +12 -18
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/test_linkers_probabilistic.py +6 -8
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/plan/test_plan.py +555 -142
- matchlab-0.1.0.dev3/test/plan/test_steps.py +195 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/resolvers/test_components.py +5 -9
- matchlab-0.1.0.dev3/test/sources/conftest.py +39 -0
- matchlab-0.1.0.dev3/test/sources/test_locations.py +225 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/test_cli.py +5 -7
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/test_document.py +130 -82
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/test_lineage.py +23 -17
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/test_progress.py +19 -13
- matchlab-0.1.0.dev3/test/test_resources.py +45 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/test_models.py +4 -2
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/test_sources.py +6 -3
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/test_transform.py +59 -1
- matchlab-0.1.0.dev1/docs/api/locations.md +0 -12
- matchlab-0.1.0.dev1/docs/guide/install.md +0 -52
- matchlab-0.1.0.dev1/src/matchlab/__init__.py +0 -55
- matchlab-0.1.0.dev1/src/matchlab/locations.py +0 -347
- matchlab-0.1.0.dev1/src/matchlab/transformers/transform.py +0 -101
- matchlab-0.1.0.dev1/src/matchlab.egg-info/scm_version.json +0 -8
- matchlab-0.1.0.dev1/test/locations/conftest.py +0 -88
- matchlab-0.1.0.dev1/test/locations/test_locations.py +0 -294
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/actions/setup-toolchain/action.yml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/dependabot.yml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/pull_request_template.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/skills/lucid-docs/README.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/skills/lucid-docs/SKILL.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/skills/lucid-docs/readability.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/workflows/cd.yml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/workflows/ci.yml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.github/workflows/codeql.yml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.gitignore +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.pre-commit-config.yaml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.vscode/launch.json +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/.vscode/settings.json +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/CODEOWNERS +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/LICENSE +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/README.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/__main__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/cases.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/data.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/measure.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/measurements.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/report.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/results/.gitkeep +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/benchmarks/runner.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/adapters.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/core/exceptions.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/core/hash.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/core/resolver_output.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/core/schemas.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/core/transform.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/eval-judgements.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/eval-metrics.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/eval-sampling.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/eval-tui.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/lineage.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/progress.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/recordstep.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/resolvers.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/specs.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/steps.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/testkit.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/api/transformers.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/assets/matchbox-icon-dark.png +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/assets/matchbox-icon.svg +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/assets/matchbox-logo-dark.svg +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/assets/matchbox-logo-light.svg +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/guide/query.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/stylesheets/extra.css +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/use-cases.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/docs/why-matchlab.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/justfile +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/personal-data-exclusions.txt +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/pyproject.toml +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/scripts/test_eval.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/setup.cfg +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/adapters/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/adapters/base.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/adapters/duckdb.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/cli.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/dataframes.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/dsu.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/kinds.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/logging.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/resolver_output.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/schemas.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/core/sql.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/judgements.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/metrics.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/samples.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/app.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/modals.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/styles.tcss +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/widgets/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/widgets/assignment.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/widgets/styling.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/eval/tui/widgets/table.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/comparison.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/dedupers/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/models/linkers/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/progress.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/_generate.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/compare.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/entities.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/features.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/testkit/models.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/explode.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab/transformers/select.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/dependency_links.txt +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/entry_points.txt +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/src/matchlab.egg-info/requires.txt +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/README.md +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/adapters/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/adapters/conftest.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/adapters/test_base.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/adapters/test_contract.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/adapters/test_duckdb.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/core/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/core/test_dsu.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/core/test_eval.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/core/test_resolver_output.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/eval/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/eval/test_samples.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/eval/tui/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/eval/tui/test_app.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/eval/tui/test_widgets.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/test_comparison.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/models/test_models.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/plan/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/plan/test_ground_truth.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/resolvers/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/resolvers/test_resolvers.py +0 -0
- {matchlab-0.1.0.dev1/test/locations → matchlab-0.1.0.dev3/test/sources}/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/test_benchmarks.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/test_entities.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/test_linked.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/testkit/test_scores.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/__init__.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/test_clean.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/test_explode.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/test_group.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/test/transformers/test_select.py +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/trufflehog-exclude.txt +0 -0
- {matchlab-0.1.0.dev1 → matchlab-0.1.0.dev3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matchlab
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev3
|
|
4
4
|
Summary: A local-first library for building, running and evaluating entity resolution pipelines.
|
|
5
5
|
Author: Department for Business and Trade
|
|
6
6
|
Project-URL: Documentation, https://uktrade.github.io/matchlab/
|
|
@@ -40,19 +40,21 @@ Dynamic: license-file
|
|
|
40
40
|
Record matching is a chore. matchlab makes it a pipeline you can build, run, query and measure — on your machine, against your warehouse, with nothing to deploy.
|
|
41
41
|
|
|
42
42
|
```python
|
|
43
|
-
|
|
44
|
-
from matchlab.models.dedupers import NaiveDeduper
|
|
43
|
+
import matchlab as mb
|
|
45
44
|
|
|
46
|
-
companies =
|
|
47
|
-
location=warehouse,
|
|
45
|
+
companies = mb.read_database(
|
|
48
46
|
name="crn",
|
|
49
|
-
|
|
47
|
+
sql="select pk, company, town from companies",
|
|
48
|
+
client=warehouse,
|
|
50
49
|
key_field="pk",
|
|
51
50
|
)
|
|
52
51
|
|
|
53
52
|
entities = (
|
|
54
53
|
companies.clean({"name": "lower(crn_company)"})
|
|
55
|
-
.dedupe(
|
|
54
|
+
.dedupe(
|
|
55
|
+
model_class=mb.NaiveDeduper,
|
|
56
|
+
model_settings={"unique_fields": ["name"]},
|
|
57
|
+
)
|
|
56
58
|
.resolve()
|
|
57
59
|
.collect()
|
|
58
60
|
)
|
|
@@ -5,19 +5,21 @@
|
|
|
5
5
|
Record matching is a chore. matchlab makes it a pipeline you can build, run, query and measure — on your machine, against your warehouse, with nothing to deploy.
|
|
6
6
|
|
|
7
7
|
```python
|
|
8
|
-
|
|
9
|
-
from matchlab.models.dedupers import NaiveDeduper
|
|
8
|
+
import matchlab as mb
|
|
10
9
|
|
|
11
|
-
companies =
|
|
12
|
-
location=warehouse,
|
|
10
|
+
companies = mb.read_database(
|
|
13
11
|
name="crn",
|
|
14
|
-
|
|
12
|
+
sql="select pk, company, town from companies",
|
|
13
|
+
client=warehouse,
|
|
15
14
|
key_field="pk",
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
entities = (
|
|
19
18
|
companies.clean({"name": "lower(crn_company)"})
|
|
20
|
-
.dedupe(
|
|
19
|
+
.dedupe(
|
|
20
|
+
model_class=mb.NaiveDeduper,
|
|
21
|
+
model_settings={"unique_fields": ["name"]},
|
|
22
|
+
)
|
|
21
23
|
.resolve()
|
|
22
24
|
.collect()
|
|
23
25
|
)
|
|
@@ -29,9 +29,7 @@ from typing import cast
|
|
|
29
29
|
|
|
30
30
|
from sqlalchemy import create_engine
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
from matchlab.models.dedupers import NaiveDeduper
|
|
34
|
-
from matchlab.models.linkers import DeterministicLinker
|
|
32
|
+
import matchlab as mb
|
|
35
33
|
|
|
36
34
|
# Strip case, whitespace, punctuation and company suffixes, so the cosmetic variation
|
|
37
35
|
# the generator plants is absorbed and matching has something to agree on.
|
|
@@ -72,27 +70,25 @@ class Topology(StrEnum):
|
|
|
72
70
|
}[self]
|
|
73
71
|
|
|
74
72
|
|
|
75
|
-
def declare(path: Path, names: Sequence[str]) -> list[Source]:
|
|
73
|
+
def declare(path: Path, names: Sequence[str]) -> list[mb.Source]:
|
|
76
74
|
"""Declare one source per table in a generated warehouse.
|
|
77
75
|
|
|
78
76
|
One engine for all of them, because they are one database and a connection per
|
|
79
77
|
source would measure SQLite's connection handling rather than matchlab's.
|
|
80
78
|
"""
|
|
81
|
-
|
|
82
|
-
name="warehouse", client=create_engine(f"sqlite:///{path}")
|
|
83
|
-
)
|
|
79
|
+
client = create_engine(f"sqlite:///{path}")
|
|
84
80
|
return [
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
mb.read_database(
|
|
82
|
+
name,
|
|
83
|
+
sql=f"select key, name, postcode from {name}",
|
|
84
|
+
client=client,
|
|
89
85
|
key_field="key",
|
|
90
86
|
)
|
|
91
87
|
for name in names
|
|
92
88
|
]
|
|
93
89
|
|
|
94
90
|
|
|
95
|
-
def _cleaning(sources: Sequence[Source]) -> dict[str, str]:
|
|
91
|
+
def _cleaning(sources: Sequence[mb.Source]) -> dict[str, str]:
|
|
96
92
|
"""Cleaning expressions that work across however many sources a record step reads.
|
|
97
93
|
|
|
98
94
|
A multi-source record step concatenates the extracts diagonally, so each source's
|
|
@@ -114,8 +110,8 @@ def _cleaning(sources: Sequence[Source]) -> dict[str, str]:
|
|
|
114
110
|
|
|
115
111
|
|
|
116
112
|
def _record_step(
|
|
117
|
-
sources: Sequence[Source], resolver: Resolver | None = None
|
|
118
|
-
) -> RecordStep:
|
|
113
|
+
sources: Sequence[mb.Source], resolver: mb.Resolver | None = None
|
|
114
|
+
) -> mb.RecordStep:
|
|
119
115
|
"""A cleaned record step of these sources, read through a resolver if given one.
|
|
120
116
|
|
|
121
117
|
A resolver is itself a record step, read at `id`=entity root, so passing one is the
|
|
@@ -124,11 +120,11 @@ def _record_step(
|
|
|
124
120
|
one is given. Without a resolver, there is a single source, read directly at
|
|
125
121
|
`id`=leaf.
|
|
126
122
|
"""
|
|
127
|
-
base: RecordStep = resolver if resolver is not None else sources[0]
|
|
123
|
+
base: mb.RecordStep = resolver if resolver is not None else sources[0]
|
|
128
124
|
return base.clean(_cleaning(sources))
|
|
129
125
|
|
|
130
126
|
|
|
131
|
-
def _through(resolver: Resolver, source: Source) -> RecordStep:
|
|
127
|
+
def _through(resolver: mb.Resolver, source: mb.Source) -> mb.RecordStep:
|
|
132
128
|
"""One source's records, per entity, read through a multi-source resolver.
|
|
133
129
|
|
|
134
130
|
The resolver's record step carries every source's rows diagonally, so this
|
|
@@ -146,23 +142,23 @@ def _through(resolver: Resolver, source: Source) -> RecordStep:
|
|
|
146
142
|
)
|
|
147
143
|
|
|
148
144
|
|
|
149
|
-
def _dedupe(record_step: RecordStep) -> Model:
|
|
145
|
+
def _dedupe(record_step: mb.RecordStep) -> mb.Model:
|
|
150
146
|
"""Deduplicate a cleaned record step. Same name and postcode is one entity."""
|
|
151
147
|
return record_step.dedupe(
|
|
152
|
-
model_class=NaiveDeduper, model_settings={"unique_fields": UNIQUE_FIELDS}
|
|
148
|
+
model_class=mb.NaiveDeduper, model_settings={"unique_fields": UNIQUE_FIELDS}
|
|
153
149
|
)
|
|
154
150
|
|
|
155
151
|
|
|
156
|
-
def _link(left: RecordStep, right: RecordStep) -> Model:
|
|
152
|
+
def _link(left: mb.RecordStep, right: mb.RecordStep) -> mb.Model:
|
|
157
153
|
"""Link two cleaned record steps, on the same rule the deduper uses."""
|
|
158
154
|
return left.link(
|
|
159
155
|
right,
|
|
160
|
-
model_class=DeterministicLinker,
|
|
156
|
+
model_class=mb.DeterministicLinker,
|
|
161
157
|
model_settings={"comparisons": COMPARISON},
|
|
162
158
|
)
|
|
163
159
|
|
|
164
160
|
|
|
165
|
-
def build(topology: Topology, sources: Sequence[Source]) -> Resolver:
|
|
161
|
+
def build(topology: Topology, sources: Sequence[mb.Source]) -> mb.Resolver:
|
|
166
162
|
"""Build a plan of this shape over these sources.
|
|
167
163
|
|
|
168
164
|
Args:
|
|
@@ -193,22 +189,24 @@ def build(topology: Topology, sources: Sequence[Source]) -> Resolver:
|
|
|
193
189
|
|
|
194
190
|
|
|
195
191
|
# Which links a star-shaped plan builds, given its record steps.
|
|
196
|
-
Pairs = Callable[[Sequence[RecordStep]], list[tuple[RecordStep, RecordStep]]]
|
|
192
|
+
Pairs = Callable[[Sequence[mb.RecordStep]], list[tuple[mb.RecordStep, mb.RecordStep]]]
|
|
197
193
|
|
|
198
194
|
|
|
199
|
-
def _spokes(
|
|
195
|
+
def _spokes(
|
|
196
|
+
record_steps: Sequence[mb.RecordStep],
|
|
197
|
+
) -> list[tuple[mb.RecordStep, mb.RecordStep]]:
|
|
200
198
|
"""Every (hub, spoke) pair. `n-1` links, and the hub in all of them."""
|
|
201
199
|
return [(record_steps[0], spoke) for spoke in record_steps[1:]]
|
|
202
200
|
|
|
203
201
|
|
|
204
202
|
def _every_pair(
|
|
205
|
-
record_steps: Sequence[RecordStep],
|
|
206
|
-
) -> list[tuple[RecordStep, RecordStep]]:
|
|
203
|
+
record_steps: Sequence[mb.RecordStep],
|
|
204
|
+
) -> list[tuple[mb.RecordStep, mb.RecordStep]]:
|
|
207
205
|
"""Every pair of record steps. `n(n-1)/2` links, and no record step privileged."""
|
|
208
206
|
return list(combinations(record_steps, 2))
|
|
209
207
|
|
|
210
208
|
|
|
211
|
-
def _star(sources: Sequence[Source], pairs: Pairs) -> Resolver:
|
|
209
|
+
def _star(sources: Sequence[mb.Source], pairs: Pairs) -> mb.Resolver:
|
|
212
210
|
"""Dedupe everything into one resolver output, then link the pairs `pairs` chooses.
|
|
213
211
|
|
|
214
212
|
`HUB` and `MESH` differ only in which pairs get linked, so they share everything
|
|
@@ -224,7 +222,7 @@ def _star(sources: Sequence[Source], pairs: Pairs) -> Resolver:
|
|
|
224
222
|
return links[0].resolve(*links[1:])
|
|
225
223
|
|
|
226
224
|
|
|
227
|
-
def _chain(sources: Sequence[Source]) -> Resolver:
|
|
225
|
+
def _chain(sources: Sequence[mb.Source]) -> mb.Resolver:
|
|
228
226
|
"""Add one source per level, each level built on the resolver output below it.
|
|
229
227
|
|
|
230
228
|
Where `MESH` is one resolver output over many links, this is many resolver outputs
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Locations
|
|
2
|
+
|
|
3
|
+
The methodologies a [source](sources.md) reads through. A source wraps exactly one location, named by `location_class` and configured by `location_settings`, with its client or frame passed separately as a [resource](resources.md).
|
|
4
|
+
|
|
5
|
+
::: matchlab.sources.base
|
|
6
|
+
options:
|
|
7
|
+
show_root_heading: true
|
|
8
|
+
show_root_full_path: true
|
|
9
|
+
members_order: source
|
|
10
|
+
show_if_no_docstring: true
|
|
11
|
+
docstring_style: google
|
|
12
|
+
show_signature_annotations: true
|
|
13
|
+
separate_signature: true
|
|
14
|
+
filters:
|
|
15
|
+
- "!^[A-Z]$"
|
|
16
|
+
- "!^_"
|
|
17
|
+
|
|
18
|
+
## Relational databases
|
|
19
|
+
|
|
20
|
+
::: matchlab.sources.relational
|
|
21
|
+
options:
|
|
22
|
+
show_root_heading: true
|
|
23
|
+
show_root_full_path: true
|
|
24
|
+
members_order: source
|
|
25
|
+
show_if_no_docstring: true
|
|
26
|
+
docstring_style: google
|
|
27
|
+
show_signature_annotations: true
|
|
28
|
+
separate_signature: true
|
|
29
|
+
filters:
|
|
30
|
+
- "!^[A-Z]$"
|
|
31
|
+
- "!^_"
|
|
32
|
+
|
|
33
|
+
## Dataframes
|
|
34
|
+
|
|
35
|
+
::: matchlab.sources.dataframe
|
|
36
|
+
options:
|
|
37
|
+
show_root_heading: true
|
|
38
|
+
show_root_full_path: true
|
|
39
|
+
members_order: source
|
|
40
|
+
show_if_no_docstring: true
|
|
41
|
+
docstring_style: google
|
|
42
|
+
show_signature_annotations: true
|
|
43
|
+
separate_signature: true
|
|
44
|
+
filters:
|
|
45
|
+
- "!^[A-Z]$"
|
|
46
|
+
- "!^_"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Models
|
|
2
|
+
|
|
3
|
+
The `Model` step, and the methodologies it runs. A model wraps exactly one [deduper](#matchlab.models.dedupers) or [linker](#matchlab.models.linkers), named by `model_class` and configured by `model_settings`.
|
|
4
|
+
|
|
5
|
+
::: matchlab.models
|
|
6
|
+
options:
|
|
7
|
+
show_root_heading: true
|
|
8
|
+
show_root_full_path: true
|
|
9
|
+
members_order: source
|
|
10
|
+
show_if_no_docstring: true
|
|
11
|
+
docstring_style: google
|
|
12
|
+
show_signature_annotations: true
|
|
13
|
+
separate_signature: true
|
|
14
|
+
filters:
|
|
15
|
+
- "!^[A-Z]$"
|
|
16
|
+
- "!^_"
|
|
17
|
+
|
|
18
|
+
## Dedupers
|
|
19
|
+
|
|
20
|
+
::: matchlab.models.dedupers
|
|
21
|
+
options:
|
|
22
|
+
show_root_heading: true
|
|
23
|
+
show_root_full_path: true
|
|
24
|
+
members_order: source
|
|
25
|
+
show_if_no_docstring: true
|
|
26
|
+
docstring_style: google
|
|
27
|
+
show_signature_annotations: true
|
|
28
|
+
separate_signature: true
|
|
29
|
+
filters:
|
|
30
|
+
- "!^[A-Z]$"
|
|
31
|
+
- "!^_"
|
|
32
|
+
|
|
33
|
+
## Linkers
|
|
34
|
+
|
|
35
|
+
::: matchlab.models.linkers
|
|
36
|
+
options:
|
|
37
|
+
show_root_heading: true
|
|
38
|
+
show_root_full_path: true
|
|
39
|
+
members_order: source
|
|
40
|
+
show_if_no_docstring: true
|
|
41
|
+
docstring_style: google
|
|
42
|
+
show_signature_annotations: true
|
|
43
|
+
separate_signature: true
|
|
44
|
+
filters:
|
|
45
|
+
- "!^[A-Z]$"
|
|
46
|
+
- "!^_"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
::: matchlab.resources
|
|
2
|
+
options:
|
|
3
|
+
show_root_heading: true
|
|
4
|
+
show_root_full_path: true
|
|
5
|
+
members_order: source
|
|
6
|
+
show_if_no_docstring: true
|
|
7
|
+
docstring_style: google
|
|
8
|
+
show_signature_annotations: true
|
|
9
|
+
separate_signature: true
|
|
10
|
+
filters:
|
|
11
|
+
- "!^[A-Z]$"
|
|
12
|
+
- "!^_"
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# Sources
|
|
2
|
+
|
|
3
|
+
The `Source` step, and the shorthands that build one. A source reads rows through a [location](locations.md) and content-addresses them, so it is where data enters a plan.
|
|
4
|
+
|
|
5
|
+
::: matchlab.sources.sources
|
|
2
6
|
options:
|
|
3
7
|
show_root_heading: true
|
|
4
8
|
show_root_full_path: true
|
|
@@ -47,8 +47,7 @@ The whole suite runs on Python alone. Storage uses DuckDB in memory, and warehou
|
|
|
47
47
|
just test
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
No container is needed
|
|
51
|
-
|
|
50
|
+
No container is needed: every warehouse test runs against SQLite, over a SQLAlchemy engine or an ADBC connection.
|
|
52
51
|
If you want a real warehouse to point matchlab at while developing, bring up whatever database you like and pass its client to a `Location`. matchlab has no opinion about where it runs, and ships nothing to manage one.
|
|
53
52
|
|
|
54
53
|
## Documentation
|
|
@@ -88,8 +87,15 @@ When contributing to matchlab and its associated repos, we try to follow consist
|
|
|
88
87
|
New plan steps subclass [`Step`](api/steps.md). A step must:
|
|
89
88
|
|
|
90
89
|
* Hold references to its inputs, and to nothing downstream of it
|
|
91
|
-
* Return a stable `
|
|
90
|
+
* Return a stable `_spec_key()` covering everything that changes its output, so caching is correct
|
|
92
91
|
* Do all its work in `_execute()`, reading inputs from the adapter by fingerprint
|
|
92
|
+
* Settle in `__init__`, listing everything its spec is built from in `_READ_ONLY` so a later assignment raises
|
|
93
|
+
|
|
94
|
+
That last rule is what keeps a step's configuration, the thing it runs, and its cached artifact from disagreeing. A step builds its methodology from its settings at construction, so an assignment afterwards moves one attribute and nothing derived from it: set `model_class` and the instance still runs the old class, leaving `spec` naming one configuration while `_execute` runs another. The cache leans on the same guarantee from the other side — `_ensure` short-circuits on a stored `_fp` *because* a settled step cannot have changed, so it would hand back the old artifact without consulting the spec at all. Validate first, then write through `_set()`, which is the only thing that gets past the guard.
|
|
95
|
+
|
|
96
|
+
Derive anything you can rather than storing it. `Transform.transformer_class` and `Model.model_type` are properties over the attributes they describe, so they cannot drift from them.
|
|
97
|
+
|
|
98
|
+
A methodology must not write into its own settings either, for the same reason: those settings are hashed into the owning step's fingerprint. Copy at the boundary if a library you wrap mutates what you hand it, as `SplinkLinker.prepare` does.
|
|
93
99
|
|
|
94
100
|
### Adapters
|
|
95
101
|
|
|
@@ -50,13 +50,23 @@ The 32-byte SHA-256 digest that keys a step's stored [artifact](#artifact). Two
|
|
|
50
50
|
|
|
51
51
|
A person's decision that one reviewed cluster's records do, or do not, describe the same [entity](#entity), recorded by matchlab's evaluation tools and scored against a [resolver](#resolver)'s clusters. A judgement is anchored to the content a reviewer was shown, not to any record's key, which is why a [leaf](#leaf) ID, a hash of content, rather than a key, is what a judgement endorses or rejects.
|
|
52
52
|
|
|
53
|
+
## Key
|
|
54
|
+
|
|
55
|
+
A record's identifier at the origin, in the [source](#source)'s own data. Its column is named by `key_field`, which defaults to `id`.
|
|
56
|
+
|
|
57
|
+
A key is not the `id` a [record step](#record-step) carries. That one is a [leaf](#leaf) or a [root](#root), derived by hashing content when the plan is collected. The two never collide, because every column an [extract](#extract) returns is [qualified](#qualify) with the source name first, so a key named `id` reaches a record step as `crn_id`. `leaves()` returns both, under the names `key` and `leaf`.
|
|
58
|
+
|
|
59
|
+
A key is read as a string whatever the location stores it as, and a null key is refused. `get_lookup()` returns keys, which is how a plan's result joins back to your own tables.
|
|
60
|
+
|
|
61
|
+
Distinct from an [answer key](#answer-key), which is a [testkit](#testkit) term that shares only the word.
|
|
62
|
+
|
|
53
63
|
## Label
|
|
54
64
|
|
|
55
65
|
A pointer from a name someone chose to a [resolver](#resolver)'s [fingerprint](#fingerprint), created by [publishing](#publish) a resolver's output. A label belongs to the store, not the plan. It can be re-aimed at a different resolver, whereas a source's `name` is part of that source's own output and never moves.
|
|
56
66
|
|
|
57
67
|
## Leaf
|
|
58
68
|
|
|
59
|
-
The stable ID of one record, a hash of its content rather than its key. Identity coming from content rather than key is why a leaf ID changes only when the underlying data does, which is the basis matchlab anchors evaluation judgements to. Reading a source or record step directly, without going through a resolver, exposes the leaf as the `id` column.
|
|
69
|
+
The stable ID of one record, a hash of its content rather than its [key](#key). Identity coming from content rather than key is why a leaf ID changes only when the underlying data does, which is the basis matchlab anchors evaluation judgements to. Reading a source or record step directly, without going through a resolver, exposes the leaf as the `id` column.
|
|
60
70
|
|
|
61
71
|
## Linker
|
|
62
72
|
|
|
@@ -64,7 +74,11 @@ A [methodology](#methodology) that finds candidate matches between two [record s
|
|
|
64
74
|
|
|
65
75
|
## Location
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
A generic way of getting a [source](#source)'s rows, and a [methodology](#methodology) like any other. `RelationalDB` runs a query. `DataFrame` reads a frame already in memory. A custom one subclasses `Location` and registers with `add_location_class`.
|
|
78
|
+
|
|
79
|
+
A location is where a source's rows come from. Do not confuse it with the [store](#store), which keeps a plan's [artifacts](#artifact) once you collect it. A location only reads.
|
|
80
|
+
|
|
81
|
+
Its [settings](#settings) are hashed into the source's [fingerprint](#fingerprint), so editing a query re-runs the source. Its [resources](#resource) are not hashed, so renaming a warehouse changes nothing.
|
|
68
82
|
|
|
69
83
|
## Matcher
|
|
70
84
|
|
|
@@ -78,7 +92,11 @@ The guarantee that a [resolver](#resolver)'s stored output carries forward every
|
|
|
78
92
|
|
|
79
93
|
## Methodology
|
|
80
94
|
|
|
81
|
-
The
|
|
95
|
+
The pluggable class a [step](#step) runs. Every kind of step has one. A [source](#source) runs a [location](#location), a [transform](#transform) runs a transformer, a [model](#model) runs a [deduper](#deduper) or a [linker](#linker), and a [resolver](#resolver) runs a resolver method.
|
|
96
|
+
|
|
97
|
+
Every step is built the same way. It names its methodology in a `*_class` argument and configures it with `*_settings`. Anything that cannot be serialised goes in `*_resources`. Swapping a methodology means changing `*_class`, never restructuring the plan around it.
|
|
98
|
+
|
|
99
|
+
Each kind keeps its own registry, so a custom methodology can be named in a [plan document](#plan-document). Register with `add_location_class`, `add_transformer_class`, `add_model_class`, or `add_resolver_class`.
|
|
82
100
|
|
|
83
101
|
## Model
|
|
84
102
|
|
|
@@ -88,13 +106,19 @@ A step that scores candidate matches by running one [methodology](#methodology).
|
|
|
88
106
|
|
|
89
107
|
A tree of [steps](#step). Each step holds a reference to its own inputs, so the step you are holding is the pipeline. There is no separate object to register steps with. Nothing runs until you [collect](#collect) it.
|
|
90
108
|
|
|
109
|
+
## Plan document
|
|
110
|
+
|
|
111
|
+
A serialisable description of a [plan](#plan), dumped with `dump()` and loaded back with `load()`. Called a **document** for short, which is what the rest of these docs call it. It is JSON, derived from a plan rather than hand-authored, which is why its nodes refer to each other by [position](#position) rather than by any name.
|
|
112
|
+
|
|
113
|
+
A document carries each step's [settings](#settings) and the edges between steps. It carries no code, no [label](#label), no data, and no [resource](#resource), only the name each resource was given. A plan rebuilt from a document [fingerprints](#fingerprint) identically to the plan it came from, given the same data, so a [store](#store) holding the original's [artifacts](#artifact) serves them rather than redoing the work. `PlanDocument` is the class.
|
|
114
|
+
|
|
91
115
|
## Position
|
|
92
116
|
|
|
93
117
|
Where something falls in an ordered sequence, used instead of a name. The repository uses the word for two different sequences, and the numbers do not agree with each other.
|
|
94
118
|
|
|
95
119
|
A step's place in `collect()`'s run order is one sense. `draw()` shows it in brackets, and logs quote it (`[step 5]`). Steps have no names, so position is how a log line, a drawn tree, and `plan.lineage()` all refer to the same step. Positions are relative to the step a plan or drawing starts from, so a sub-plan numbers its steps differently from the full plan it came from.
|
|
96
120
|
|
|
97
|
-
A setting that must point at one of a step's own inputs uses the other sense. It names that input by its index among the step's own inputs, not by that input's place in the whole plan. `
|
|
121
|
+
A setting that must point at one of a step's own inputs uses the other sense. It names that input by its index among the step's own inputs, not by that input's place in the whole plan. `Components.thresholds` keys a per-model threshold this way. A model can sit at plan position 5 while still being resolver-input position 0.
|
|
98
122
|
|
|
99
123
|
## Prune
|
|
100
124
|
|
|
@@ -108,7 +132,7 @@ Re-publishing the same label at the same resolver output is a no-op. Aiming an e
|
|
|
108
132
|
|
|
109
133
|
## Qualify
|
|
110
134
|
|
|
111
|
-
Prefix a column name with its [source](#source)'s name (`first_name` becomes `crn_first_name`), so the same field from two sources can sit side by side without colliding. A qualified column's prefix must parse as a valid identifier, which is why a source's `name` is restricted to safe characters.
|
|
135
|
+
Prefix a column name with its [source](#source)'s name (`first_name` becomes `crn_first_name`), so the same field from two sources can sit side by side without colliding. A qualified column's prefix must parse as a valid identifier, which is why a source's `name` is restricted to safe characters, and it is why two different sources in one plan cannot share a name.
|
|
112
136
|
|
|
113
137
|
## Record step
|
|
114
138
|
|
|
@@ -124,13 +148,31 @@ A step that collapses a model's scored edges into clusters, one per [entity](#en
|
|
|
124
148
|
|
|
125
149
|
Call this a **Resolver**, or its **merge-forwarded Resolver output** if you mean the stored table specifically. Don't call it "a resolution". That noun has been retired.
|
|
126
150
|
|
|
151
|
+
## Resource
|
|
152
|
+
|
|
153
|
+
A named object a node needs to run that can't be serialised. Pass one in a step's `*_resources` argument, never among its [settings](#settings). Wrap it as `Resource("warehouse", engine)` to give it the name a [plan document](#plan-document) records. Supply it again when that document is loaded.
|
|
154
|
+
|
|
155
|
+
For sources, resources are the data-generating mechanism, which affects the [fingerprint](#fingerprint). For all other types of node, resources cannot influence the output of the node because they're ignored by the fingerprint.
|
|
156
|
+
|
|
157
|
+
A location or methodology marks its resource fields `FromResources`. Every other field is a setting. Passing a field in the wrong argument raises an error that names the field.
|
|
158
|
+
|
|
159
|
+
Sharing a resource name means sharing a resource. Two steps of different kinds can't share a resource.
|
|
160
|
+
|
|
127
161
|
## Root
|
|
128
162
|
|
|
129
163
|
The ID of a cluster a resolver produces, a hash of the sorted set of [leaf](#leaf) IDs it contains. Two runs that produce the same clustering produce the same root ID, whatever order the underlying algorithm found its clusters in. Reading a [record step](#record-step) through a resolver exposes the root as the `id` column, so several records can share one `id`.
|
|
130
164
|
|
|
165
|
+
## Settings
|
|
166
|
+
|
|
167
|
+
A step's serialisable configuration. Passed in a step's `*_settings` argument, carried in a plan document, and hashed into the step's [fingerprint](#fingerprint).
|
|
168
|
+
|
|
169
|
+
Hashing is what makes settings the opposite of a [resource](#resource): **editing settings re-runs the step and everything below it**, whether or not the output would differ.
|
|
170
|
+
|
|
171
|
+
Every field of a location or methodology is a setting unless the class marks it a resource.
|
|
172
|
+
|
|
131
173
|
## Source
|
|
132
174
|
|
|
133
|
-
The
|
|
175
|
+
The leaf a plan starts from: a [location](#location) to read, plus the column that [keys](#key) it. Every column the location returns is part of a record's identity, so two rows are the same record exactly when it returns identical values for both. Build one with `read_database` or `read_dataframe`.
|
|
134
176
|
|
|
135
177
|
## Step
|
|
136
178
|
|