Skip to content

Commit 0512e15

Browse files
committed
Add release readiness docs and checks
1 parent d258b8a commit 0512e15

5 files changed

Lines changed: 187 additions & 7 deletions

File tree

.github/workflows/tests.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@ jobs:
2121
with:
2222
python-version: ${{ matrix.python-version }}
2323

24+
- name: Set up Node
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: "22"
28+
2429
- name: Compile source
2530
run: |
26-
python -m py_compile src/address_dataset_generator.py src/address_resolver.py src/resolver_app.py
31+
python -m py_compile src/*.py
32+
33+
- name: Check browser JavaScript syntax
34+
run: |
35+
node --check src/static/app.js
2736
2837
- name: Run tests
2938
run: |

DATA_SOURCES.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Data Sources
2+
3+
Ady Resolver is built around real address records, but no free public source
4+
tested here proves that every current Mississippi address is present. Treat the
5+
public feeds as strong baselines and use authoritative or licensed data when
6+
you need true production coverage.
7+
8+
## Supported Source Types
9+
10+
- **MS811/MARIS county point-address ZIPs**: best configured route for
11+
authoritative Mississippi point-address coverage when obtained through the
12+
MARIS distribution process. Use `--real-address-format maris` and keep
13+
`--require-ms-county-coverage` enabled.
14+
- **Public MARIS Point Addressing ZIPs**: easiest public point-address source.
15+
It is useful, but public availability is county-limited and should not be
16+
presented as exhaustive.
17+
- **Public MARIS parcel service**: broad statewide public fallback using
18+
parcel situs fields. It covers all county names in the configured service,
19+
but parcel situs rows are not equivalent to authoritative point addresses.
20+
- **OpenAddresses processed extracts**: useful supplemental and development
21+
source. Current Mississippi extracts are not exhaustive and many records lack
22+
strong locality fields.
23+
- **OpenAddresses direct ESRI source catalog**: queries current configured
24+
source services and caches normalized CSVs. This is useful supplemental data
25+
when source conform files expose situs locality fields.
26+
- **USDOT National Address Database**: parser support exists, but the tested
27+
Mississippi rows were not useful for this project’s current reference cache.
28+
- **Manual verified supplement**: local CSV/XLSX additions for confirmed
29+
missing addresses. The app can import these rows and update the live resolver
30+
reference index.
31+
32+
## Coverage Expectations
33+
34+
- The generator samples from loaded real source records and fails when the real
35+
pool is too small instead of inventing replacement addresses.
36+
- `--require-ms-county-coverage` checks for all 82 Mississippi county names in
37+
source file paths. That is a coverage guard, not proof that every address in
38+
each county is present.
39+
- ZIP-to-city enrichment only adds conservative derived variants when a ZIP has
40+
at least 25 real records and one city has at least a 98% share.
41+
- Manual verified additions should be used for known misses from public feeds.
42+
They are tracked separately in
43+
`datasets/source_cache/manual_verified_ms/verified_addresses.csv`.
44+
45+
## Recommended Strategies
46+
47+
- **Best public baseline**: merge MARIS parcels, public MARIS Point Addressing,
48+
OpenAddresses processed extracts, OpenAddresses direct ESRI CSVs, and the
49+
manual verified supplement.
50+
- **Best Mississippi address-point route**: obtain the local/state NG9-1-1 or
51+
full MS811/MARIS county point-address distribution.
52+
- **Best postal deliverability route**: use a licensed USPS/CASS/DPV-capable
53+
source or API. That is validation-grade for mail delivery, but it is not the
54+
same thing as a free downloadable address list.
55+
56+
Generated `datasets/` and `runs/` directories are ignored because public-source
57+
caches and full reference builds can be several GB. Rebuild them with the
58+
README commands instead of committing them.

Makefile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
PYTHON ?= python3
2+
APP_HOST ?= 127.0.0.1
3+
APP_PORT ?= 8765
4+
SMOKE_OUTPUT_DIR ?= /tmp/ady_resolver_demo_smoke
5+
6+
.PHONY: compile js-check unit test smoke app
7+
8+
compile:
9+
$(PYTHON) -m py_compile src/*.py
10+
11+
js-check:
12+
node --check src/static/app.js
13+
14+
unit:
15+
$(PYTHON) -m unittest discover -s tests -v
16+
17+
test: compile js-check unit
18+
19+
smoke:
20+
rm -rf "$(SMOKE_OUTPUT_DIR)"
21+
$(PYTHON) src/address_resolver.py \
22+
--mode predict \
23+
--eval-dataset-dir examples/demo_reference \
24+
--model-path models/stage2_model.json \
25+
--output-dir "$(SMOKE_OUTPUT_DIR)" \
26+
--compare-variants \
27+
--jobs 1
28+
29+
app:
30+
$(PYTHON) src/resolver_app.py --host "$(APP_HOST)" --port "$(APP_PORT)"

README.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ cd Ady-Resolver
5656
python3 -m unittest discover -s tests -v
5757
```
5858

59+
Common development commands are also available through `make`:
60+
61+
```bash
62+
make test # compile Python, check browser JS syntax, run unit tests
63+
make smoke # run the checked-in demo resolver accuracy smoke
64+
make app # start the local browser app on 127.0.0.1:8765
65+
```
66+
5967
Run the local app after building or restoring a reference cache:
6068

6169
```bash
@@ -66,13 +74,23 @@ Then open `http://127.0.0.1:8765`.
6674

6775
## Repository Contents
6876

69-
- `src/address_dataset_generator.py` - source downloading, cache handling,
70-
parsing, cleanup, and training/evaluation dataset generation.
71-
- `src/address_resolver.py` - Stage 1 resolver, Stage 2 model, metrics, and
72-
CLI entry points.
73-
- `src/resolver_app.py` - local web app and reference-cache builder.
77+
- `src/address_dataset_generator.py` - compatibility CLI/facade for dataset
78+
generation. Source loading, noise generation, and dataset assembly live in
79+
focused modules such as `address_source_*`, `address_openaddresses.py`,
80+
`address_maris.py`, `address_noise.py`, and `address_dataset_build.py`.
81+
- `src/address_resolver.py` - compatibility CLI/facade for the resolver. Shared
82+
models, parsing, reference loading, and Stage 2 training/scoring live in
83+
`resolver_models.py`, `resolver_parsing.py`, `resolver_reference.py`, and
84+
`resolver_stage2.py`.
85+
- `src/resolver_app.py` - local web app entrypoint. App config, persistence,
86+
service logic, HTTP routing, reference-cache building, batch file handling,
87+
and static UI assets are split across `resolver_app_*`,
88+
`resolver_reference_cache.py`, `resolver_batch_io.py`, `resolver_http.py`,
89+
and `src/static/`.
7490
- `src/train_from_addresses.py` - one-command dataset generation and model
7591
training for custom address CSVs.
92+
- `DATA_SOURCES.md` - concise source and coverage guidance for supported public,
93+
authoritative, licensed, and manual address data paths.
7694
- `models/` - small checked-in Stage 2 model JSON artifacts.
7795
- `examples/` - a small demo reference set and custom-address CSV example for
7896
fresh clones.
@@ -98,6 +116,9 @@ The generator now supports real address sources for Mississippi. For exhaustive
98116
Mississippi coverage, use the MS811/MARIS county shapefile ZIP set and keep the
99117
county-coverage guard enabled.
100118

119+
See [DATA_SOURCES.md](DATA_SOURCES.md) for a shorter source-quality and
120+
coverage guide.
121+
101122
- MS811/MARIS full county shapefile ZIPs: production source for all 82
102123
Mississippi counties when obtained through the MARIS distribution agreement.
103124
- Public MARIS Mississippi Point Addressing ZIPs: easiest public Mississippi
@@ -413,10 +434,23 @@ flag, match ID, stage, and the top three candidate addresses.
413434
## Tests
414435

415436
```bash
416-
python3 -m py_compile src/address_dataset_generator.py src/address_resolver.py src/resolver_app.py
437+
python3 -m py_compile src/*.py
438+
node --check src/static/app.js
417439
python3 -m unittest discover -s tests -v
418440
```
419441

442+
Or run the same local check set with:
443+
444+
```bash
445+
make test
446+
```
447+
448+
Run the checked-in demo accuracy smoke with:
449+
450+
```bash
451+
make smoke
452+
```
453+
420454
## License
421455

422456
Ady Resolver is open source under the [MIT License](LICENSE).

tests/test_demo_accuracy_smoke.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import sys
2+
import unittest
3+
from pathlib import Path
4+
5+
6+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
7+
SRC_DIR = PROJECT_ROOT / "src"
8+
sys.path.insert(0, str(SRC_DIR))
9+
10+
from address_resolver import ( # noqa: E402
11+
Resolver,
12+
build_city_lookup,
13+
choose_combined_resolution,
14+
load_model,
15+
load_queries,
16+
load_reference,
17+
)
18+
19+
20+
class DemoAccuracySmokeTests(unittest.TestCase):
21+
def test_demo_hard_cases_resolve_as_expected(self) -> None:
22+
dataset_dir = PROJECT_ROOT / "examples" / "demo_reference"
23+
reference_rows, _reference_by_id = load_reference(dataset_dir / "reference_addresses.csv")
24+
resolver = Resolver(reference_rows, build_city_lookup(reference_rows))
25+
model, accept_threshold, review_threshold, _metadata = load_model(
26+
PROJECT_ROOT / "models" / "stage2_model.json",
27+
resolver,
28+
)
29+
30+
for query in load_queries(dataset_dir / "queries.csv"):
31+
with self.subTest(query_id=query.query_id, query_address=query.query_address):
32+
parsed = resolver.parse(query.query_address)
33+
stage1 = resolver.resolve_stage1(parsed, review_threshold=review_threshold)
34+
stage2 = model.resolve(
35+
parsed,
36+
accept_threshold=accept_threshold,
37+
review_threshold=review_threshold,
38+
)
39+
combined = choose_combined_resolution(stage1, stage2)
40+
41+
if query.label:
42+
self.assertEqual(query.true_match_id, combined.predicted_match_id)
43+
self.assertEqual(query.canonical_address, combined.predicted_canonical_address)
44+
else:
45+
self.assertEqual("", combined.predicted_match_id)
46+
47+
48+
if __name__ == "__main__":
49+
unittest.main()

0 commit comments

Comments
 (0)