Price List Normalizer — source and results

Source code — v4

price-list-normalizer.zip — 68,808 bytes, 38 files

sha256:
1e33c317ebe689a9118fbda30e522efd3678b0ad6c65101bed26746732462450

.sha256 sidecar

Extract and run python3 tests/test_review_cases.py, python3 tests/test_parser.py and python3 tests/test_v3_regressions.py from the extracted directory. No token, account or network needed.

Test results

SuiteResult
s01-s11 review layouts (yours)34/34
test_parser.py (mine)31/31
test_v3_regressions.py (yours, 14 checks)14/14 — v3 scored 6/14
n01-n10 new layoutsall parse

Fixed in v4 — the k/M regression

The magnitude suffix I added in v3 matched the first letter of the NEXT WORD, so a bare number followed by a word starting with k, M or m became a magnitude:

Inputv3 (wrong)v4
Crew of 2-3 movers $120/hrrange 2–3000000$120/hr
Chain-link fence 10m run $45010,000,000$450
4K TV wall mounting $1494000$149
Tree removal 20-30 ft $450range 20–30$450

A magnitude suffix may no longer be followed by a letter, and it requires a currency symbol — 10m run is a length, not ten million.

A range also always beat a real price. A range that is not itself money and has a currency-marked figure outside it is a quantity or a size, not a price: the currency-marked figure now wins and the range is kept as range_present.

Descriptions only strip a stranded symbol, so minimum $85 keeps its $.

Still in from v3

Schemas validate; _NoRedirect forces every redirect hop through validation (11/11 probes blocked); billing only on rows carrying a price, range or "call for price"; LLM event gated on APIFY_USER_IS_PAYING.

Pricing

$0.20 per price list, up to 1,000 rows. Per list, not per row — a price list is one job whether it has fifteen rows or nine hundred.

Nothing is deployed. Publishing, ID check and payout stay with John and Goji.