hsds-entity-resolution 2.0.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 (52) hide show
  1. hsds_entity_resolution-2.0.0/.gitignore +234 -0
  2. hsds_entity_resolution-2.0.0/PKG-INFO +306 -0
  3. hsds_entity_resolution-2.0.0/README.pypi.md +282 -0
  4. hsds_entity_resolution-2.0.0/pyproject.toml +92 -0
  5. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/__init__.py +5 -0
  6. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/config/__init__.py +31 -0
  7. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/config/entity_resolution_run_config.py +552 -0
  8. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/__init__.py +5 -0
  9. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/apply_mitigation.py +647 -0
  10. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/clean_entities.py +431 -0
  11. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/cluster_pairs.py +384 -0
  12. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/dataframe_utils.py +88 -0
  13. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/domain_utils.py +248 -0
  14. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/evidence_policy.py +36 -0
  15. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/generate_candidates.py +1354 -0
  16. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/generate_candidates_sharded.py +142 -0
  17. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/materialize_review_queue.py +125 -0
  18. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/nlp/__init__.py +6 -0
  19. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/nlp/algorithms.py +83 -0
  20. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/nlp/safeguards.py +41 -0
  21. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/nlp/scoring.py +43 -0
  22. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/nlp/types.py +24 -0
  23. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/pair_tiering.py +54 -0
  24. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/pipeline.py +659 -0
  25. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/prepare_persistence_artifacts.py +106 -0
  26. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/score_candidates.py +1238 -0
  27. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/score_candidates_sharded.py +182 -0
  28. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/source_policy.py +257 -0
  29. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/core/taxonomy_utils.py +249 -0
  30. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/dagster/__init__.py +5 -0
  31. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/dagster/components/__init__.py +7 -0
  32. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/dagster/components/entity_resolution_component.py +212 -0
  33. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/dagster/definitions.py +11 -0
  34. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/defs/__init__.py +1 -0
  35. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/__init__.py +52 -0
  36. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/answers.py +137 -0
  37. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/protocol.py +44 -0
  38. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/reference.py +50 -0
  39. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/stage.py +130 -0
  40. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/judge/state.py +281 -0
  41. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/normalize.py +153 -0
  42. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/observability/__init__.py +6 -0
  43. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/observability/progress.py +133 -0
  44. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/observability/tracer.py +152 -0
  45. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/py.typed +0 -0
  46. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/__init__.py +69 -0
  47. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/artifact_rows.py +64 -0
  48. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/contracts.py +105 -0
  49. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/domain.py +15 -0
  50. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/frames.py +251 -0
  51. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/rows/__init__.py +17 -0
  52. hsds_entity_resolution-2.0.0/src/hsds_entity_resolution/types/rows/clean_entity_rows.py +85 -0
@@ -0,0 +1,234 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ .uv-cache/
195
+ .cursor/
196
+
197
+ # PyPI configuration file
198
+ .pypirc
199
+
200
+ # Marimo
201
+ marimo/_static/
202
+ marimo/_lsp/
203
+ __marimo__/
204
+
205
+ # Streamlit
206
+ .streamlit/secrets.toml
207
+
208
+
209
+ #Ignore cursor AI rules
210
+ .cursor/rules/codacy.mdc
211
+
212
+ # Codacy
213
+ .codacy/AGENTS.md
214
+
215
+ # dbt — target/ contains per-run artifacts; never commit.
216
+ consumer/dbt/target/
217
+ hsds_dbt_macros/target/
218
+
219
+ .dg/
220
+
221
+ consumer/
222
+ hsds_dbt_macros/
223
+ scripts/
224
+
225
+ HSDS_DATA_MODEL.md
226
+ _hsds_table_desc/
227
+
228
+ .agents/skills/snowsql-investigation/
229
+ .codacy/
230
+ docs/
231
+ !docs/
232
+ docs/*
233
+ !docs/deduplication-schema-audit.md
234
+ .DS_Store
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.5
2
+ Name: hsds-entity-resolution
3
+ Version: 2.0.0
4
+ Summary: HSDS entity resolution: candidate generation, scoring, a Pair Judge contract and clustering, with a Dagster component.
5
+ Project-URL: Homepage, https://github.com/211-Connect/hsds-entity-resolution
6
+ Project-URL: Repository, https://github.com/211-Connect/hsds-entity-resolution
7
+ Project-URL: Issues, https://github.com/211-Connect/hsds-entity-resolution/issues
8
+ Keywords: dagster,deduplication,entity-resolution,hsds
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: <3.15,>=3.10
18
+ Requires-Dist: dagster<1.13,>=1.11
19
+ Requires-Dist: numpy<3,>=1.26
20
+ Requires-Dist: polars<2,>=1.18
21
+ Requires-Dist: pydantic<3,>=2
22
+ Requires-Dist: tldextract>=5.3.1
23
+ Description-Content-Type: text/markdown
24
+
25
+ # hsds-entity-resolution
26
+
27
+ Reusable [Dagster](https://dagster.io) component for incremental entity resolution on
28
+ [HSDS](https://docs.openreferral.org/en/latest/) (Human Services Data Specification) records.
29
+
30
+ **Install:**
31
+
32
+ ```bash
33
+ pip install hsds-entity-resolution
34
+ ```
35
+
36
+ ---
37
+
38
+ ## What this package does
39
+
40
+ Community 211 directories and social-services platforms often ingest the same organizations
41
+ and services from multiple data partners. Over time those records diverge — different spellings,
42
+ phone formats, partial addresses — making it hard to know which rows describe the same real-world
43
+ entity.
44
+
45
+ `hsds-entity-resolution` provides a single Dagster component, `EntityResolutionComponent`, that
46
+ runs an incremental seven-stage entity-resolution pipeline on your HSDS data:
47
+
48
+ | Stage | What happens |
49
+ |---|---|
50
+ | **Clean entities** | Normalize contact fields, compute content hashes, detect adds/changes/removals since the last run |
51
+ | **Generate candidates** | Block on overlap signals (email, phone, domain, taxonomy, location) to produce candidate pairs |
52
+ | **Score candidates** | Weighted combination of deterministic overlap signals and NLP fuzzy name/description matching |
53
+ | **Judge pairs** *(optional)* | A caller-supplied `PairJudge` answers Same Site, Same Offering, physical delivery and Organization Relation as probabilities for every scored pair |
54
+ | **Apply mitigation** | Carry forward stable pairs, retire pairs for removed entities, detect pair identity continuity |
55
+ | **Cluster pairs** | Greedy correlation clustering groups high-confidence duplicate pairs into clusters |
56
+ | **Materialize review queue** | Pairs that score above the *maybe* threshold but below *duplicate* are surfaced for human review |
57
+ | **Prepare artifacts** | Package all stage outputs into a typed bundle ready for downstream persistence |
58
+
59
+ The component is **storage-agnostic**: it accepts and emits Polars DataFrames, so you wire it
60
+ into whatever persistence layer your Dagster project uses.
61
+
62
+ ---
63
+
64
+ ## Prerequisites
65
+
66
+ - Python 3.10–3.13
67
+ - A [Dagster](https://dagster.io) project managed with the
68
+ [`dg` CLI](https://docs.dagster.io/guides/build/projects-and-components/components/)
69
+ - Four upstream Dagster assets that supply denormalized HSDS entity data (see [Input assets](#input-assets))
70
+
71
+ ---
72
+
73
+ ## Quickstart
74
+
75
+ ### 1. Discover the component
76
+
77
+ ```bash
78
+ dg list components --package hsds_entity_resolution
79
+ ```
80
+
81
+ ### 2. Add to your component YAML
82
+
83
+ ```yaml
84
+ type: hsds_entity_resolution.dagster.components.EntityResolutionComponent
85
+ attributes:
86
+ team_id: my_team
87
+ scope_id: regional
88
+ entity_type: organization
89
+ ```
90
+
91
+ That's enough to wire the component into your Dagster asset graph. The four required upstream
92
+ assets default to the keys `organization_entities`, `service_entities`,
93
+ `previous_entity_index`, and `previous_pair_state_index` — override them with the
94
+ `*_asset_key` attributes if your project uses different names.
95
+
96
+ ---
97
+
98
+ ## Input assets
99
+
100
+ The component depends on four upstream assets. Each must yield a Polars `DataFrame`
101
+ (or be coercible to one).
102
+
103
+ ### `organization_entities` and `service_entities`
104
+
105
+ Denormalized HSDS entity records. Required columns:
106
+
107
+ | Column | Type | Description |
108
+ |---|---|---|
109
+ | `entity_id` | `str` | Stable unique identifier for this record |
110
+ | `source_schema` | `str` | Tenant or source identifier (e.g. `region_a`) |
111
+ | `name` | `str` | Entity name used for NLP scoring |
112
+ | `description` | `str` | Entity description used for NLP scoring |
113
+ | `emails` | `list[str]` | Normalized email addresses |
114
+ | `phones` | `list[str]` | Normalized phone numbers |
115
+ | `websites` | `list[str]` | Normalized domain names |
116
+ | `locations` | `list[dict]` | Structured address/location objects |
117
+ | `taxonomies` | `list[dict]` | AIRS taxonomy code objects |
118
+ | `identifiers` | `list[dict]` | External identifier objects |
119
+ | `embedding_vector` | `list[float]` | Pre-computed text embedding (e.g. 384-dim) |
120
+
121
+ Optional passthrough columns (`display_name`, `display_description`, `alternate_name`,
122
+ `short_description`, `application_process`, `fees_description`, `eligibility_description`,
123
+ `resource_writer_name`, `assured_date`, `assurer_email`, `original_id`,
124
+ `organization_original_id`, `organization_name`, `organization_id`,
125
+ `services_rollup`) are forwarded unchanged to the normalized entity cache and
126
+ persistence artifacts.
127
+
128
+ ### `previous_entity_index`
129
+
130
+ The `normalized_organization` or `normalized_service` output from the *previous* run of this
131
+ component. Pass an empty `pl.DataFrame()` on first run.
132
+
133
+ ### `previous_pair_state_index`
134
+
135
+ The `pair_state_index` output from the *previous* run of this component. Pass an empty
136
+ `pl.DataFrame()` on first run. The component uses this to carry forward stable pair scores
137
+ and detect pair identity continuity across incremental runs.
138
+
139
+ ---
140
+
141
+ ## Component attributes
142
+
143
+ | Attribute | Type | Default | Description |
144
+ |---|---|---|---|
145
+ | `team_id` | `str` | `"hsds"` | Team identifier written into run metadata |
146
+ | `scope_id` | `str` | `"default"` | Deployment or region identifier |
147
+ | `entity_type` | `"organization" \| "service"` | `"organization"` | Which entity type this instance processes |
148
+ | `policy_version` | `str` | `"hsds-er-v1"` | Scoring policy version tag |
149
+ | `model_version` | `str` | `"embedding-only-v1"` | Embedding model version tag |
150
+ | `explicit_backfill` | `bool` | `False` | Force a full re-run even when no entity changes are detected |
151
+ | `organization_entities_asset_key` | `str` | `"organization_entities"` | Asset key for upstream org entities |
152
+ | `service_entities_asset_key` | `str` | `"service_entities"` | Asset key for upstream service entities |
153
+ | `previous_entity_index_asset_key` | `str` | `"previous_entity_index"` | Asset key for previous-run entity cache |
154
+ | `previous_pair_state_index_asset_key` | `str` | `"previous_pair_state_index"` | Asset key for previous-run pair state |
155
+ | `output_asset_prefix` | `list[str]` | `[]` | Prefix prepended to all output asset keys |
156
+ | `constants_overrides` | `dict` | `{}` | Deep-merge override for scoring constants (see [Tuning](#tuning-scoring-constants)) |
157
+
158
+ ---
159
+
160
+ ## Output assets
161
+
162
+ The component emits fifteen assets. Using `output_asset_prefix: ["my_prefix"]` namespaces
163
+ all keys under `my_prefix/`.
164
+
165
+ | Asset key | Description |
166
+ |---|---|
167
+ | `normalized_organization` | Cleaned, normalized organization records with content hashes |
168
+ | `normalized_service` | Cleaned, normalized service records with content hashes |
169
+ | `entity_delta_summary` | Single-row summary of added / changed / removed entity counts |
170
+ | `removed_entity_ids` | Entity IDs that were present last run but are absent this run |
171
+ | `candidate_pairs` | Raw blocked candidate pairs before scoring |
172
+ | `scored_pairs` | Candidate pairs with composite scores and tier labels (`duplicate`, `maybe`, `non_duplicate`) |
173
+ | `pair_reasons` | Per-feature evidence rows for every scored pair |
174
+ | `mitigation_events` | Pairs reassigned due to entity changes between runs |
175
+ | `removed_pair_ids` | Pair IDs retired this run (entity removed or score dropped) |
176
+ | `pair_id_remap` | Maps old pair IDs to new ones when pair identity shifts |
177
+ | `clusters` | Entity groups identified as duplicates via correlation clustering |
178
+ | `cluster_pairs` | Edges within each cluster |
179
+ | `pair_state_index` | Full pair state — feed this back as `previous_pair_state_index` next run |
180
+ | `review_queue_items` | Borderline pairs above the *maybe* threshold, ready for human review |
181
+ | `run_summary` | Single-row run metrics (candidate count, duplicate count, cluster count, etc.) |
182
+ | `persistence_artifact_bundle` | Dict containing all stage outputs packaged for bulk persistence |
183
+
184
+ ---
185
+
186
+ ## Tuning scoring constants
187
+
188
+ The default thresholds are calibrated for HSDS data. Override any constant via
189
+ `constants_overrides` without subclassing:
190
+
191
+ ```yaml
192
+ type: hsds_entity_resolution.dagster.components.EntityResolutionComponent
193
+ attributes:
194
+ team_id: my_team
195
+ scope_id: regional
196
+ entity_type: organization
197
+ constants_overrides:
198
+ scoring:
199
+ duplicate_threshold: 0.85
200
+ maybe_threshold: 0.70
201
+ nlp:
202
+ fuzzy_threshold: 0.90
203
+ ```
204
+
205
+ Key thresholds:
206
+
207
+ | Constant | Default (org) | Default (service) | Description |
208
+ |---|---|---|---|
209
+ | `scoring.duplicate_threshold` | `0.82` | `0.70` | Minimum score to auto-cluster as duplicate |
210
+ | `scoring.maybe_threshold` | `0.68` | `0.62` | Minimum score to send to review queue |
211
+ | `scoring.deterministic_section_weight` | `0.5625` | `0.50` | Weight of overlap-signal section |
212
+ | `scoring.nlp_section_weight` | `0.4375` | `0.50` | Weight of NLP fuzzy-match section; the two must sum to 1 |
213
+ | `scoring.nlp.fuzzy_threshold` | `0.88` | `0.86` | Minimum name similarity to count as NLP match |
214
+ | `blocking.similarity_threshold` | `0.75` | `0.75` | Minimum embedding cosine similarity for blocking |
215
+ | `blocking.max_candidates_per_entity` | `50` | `125` | Maximum candidate pairs per entity |
216
+
217
+ ---
218
+
219
+ ## Multiple instances
220
+
221
+ Deploy one component instance per entity type and scope:
222
+
223
+ ```yaml
224
+ # organizations
225
+ type: hsds_entity_resolution.dagster.components.EntityResolutionComponent
226
+ attributes:
227
+ team_id: my_team
228
+ scope_id: regional
229
+ entity_type: organization
230
+ output_asset_prefix: ["org_er"]
231
+
232
+ ---
233
+
234
+ # services
235
+ type: hsds_entity_resolution.dagster.components.EntityResolutionComponent
236
+ attributes:
237
+ team_id: my_team
238
+ scope_id: regional
239
+ entity_type: service
240
+ output_asset_prefix: ["svc_er"]
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Pair Judge contract
246
+
247
+ The engine fixes what a judge reads and what it answers; it ships no questions or model.
248
+
249
+ - `hsds_entity_resolution.judge.PairState`: both records as HSDS text (name, alternate
250
+ name, description, short description, eligibility, fees, application process,
251
+ taxonomy names, sites, phones as digits, websites, organization name and
252
+ description), a code-computed site comparison word (`same address`, `same city`,
253
+ `different`, `unknown`), the prior score as a bucket in words, and one free-text
254
+ `source_profile` slot per side. The engine fills the HSDS fields and leaves the
255
+ profile slots empty unless you pass `source_profiles={source_schema: text}`.
256
+ - `hsds_entity_resolution.judge.PairAnswers`: `same_site`, `same_offering`,
257
+ `physically_delivered` (probabilities; the last two are null for organization
258
+ pairs), and `organization_relation` as a distribution over `same`,
259
+ `parent_and_chapter`, `affiliated`, `unrelated`, `cannot_tell`, plus `model_id` and
260
+ `question_set_version`. The judge never answers "duplicate"; compose that in your
261
+ own code.
262
+ - `hsds_entity_resolution.judge.PairJudge`: a protocol with `judge(states) -> answers`
263
+ and a `state_token_budget`. Size states for the smallest model you may switch to
264
+ (`SMALLEST_MODEL_STATE_BUDGET_TOKENS`, 32k).
265
+ - `ReferenceJudge` answers 0.5 everywhere and a uniform relation, so the pipeline runs
266
+ end to end without a model.
267
+
268
+ ```python
269
+ from hsds_entity_resolution.core import run_incremental
270
+ from hsds_entity_resolution.judge import ReferenceJudge
271
+
272
+ result = run_incremental(..., judge=ReferenceJudge(), source_profiles={"source_a": "..."})
273
+ result.judge_answers # one row per scored pair, JUDGE_ANSWERS_SCHEMA
274
+ ```
275
+
276
+ `judge_scored_pairs(...)` runs the same stage on its own, for hosts that run the judge
277
+ as a separate step.
278
+
279
+ ## Normalisers
280
+
281
+ `hsds_entity_resolution.normalize` holds pure, idempotent functions on plain strings —
282
+ `normalize_phone` (digits plus extension), `normalize_email`, `normalize_url`,
283
+ `normalize_address_component`, `normalize_postal_code` — usable outside the engine.
284
+
285
+ ## Migrating from `hsds-record-matcher` 1.x
286
+
287
+ - **Install name.** The distribution is now `hsds-entity-resolution`, matching the
288
+ import name `hsds_entity_resolution`. Uninstall `hsds-record-matcher` first.
289
+ - **ML scoring section removed (1.2.0).** `scoring.ml`, `scoring.calibration`,
290
+ `scoring.ml_section_weight` and pair-rule `ml_section_weight` / `ml_gate_threshold`
291
+ now fail validation. `deterministic_section_weight + nlp_section_weight` must equal
292
+ 1.0; to keep your scores, divide each by their old sum. Scored pairs no longer carry
293
+ `ml_section_score`, `legacy_confidence_score`, `shadow_confidence_score`,
294
+ `shadow_log_odds` or `calibration_version`, and `run_incremental` no longer takes
295
+ `taxonomy_embeddings`.
296
+ - **Judge stage (2.0.0).** `run_incremental` accepts `judge` and `source_profiles`;
297
+ without a judge every output is unchanged.
298
+ - **Logging.** Core modules log through `logging.getLogger("hsds_entity_resolution...")`
299
+ instead of Dagster's logger. Add `hsds_entity_resolution` to your Dagster
300
+ `python_logs.managed_python_loggers` to see them in the Dagster UI.
301
+
302
+ ## Links
303
+
304
+ - **Source:** [github.com/211-Connect/hsds-entity-resolution](https://github.com/211-Connect/hsds-entity-resolution)
305
+ - **Issues:** [github.com/211-Connect/hsds-entity-resolution/issues](https://github.com/211-Connect/hsds-entity-resolution/issues)
306
+ - **PyPI:** [pypi.org/project/hsds-entity-resolution](https://pypi.org/project/hsds-entity-resolution/)