gridpin-pro
A closed build on the same engine for teams that keep an address database. It finds the rows that name one address, says what changed, decides which rows to accept, check or fix, and writes pages you can hand on. It runs on your machine, on the same country sheets, and never connects to the network.
Get started
1. Open your order page (api.gridpin.dev/access, with the key from the payment), accept the gridpin-pro EULA, then download the archive for your system and the licence file gridpin-pro.lic. Unpack the archive.
2. Put gridpin-pro.lic next to the program, in the unpacked folder, or at ~/.config/gridpin/pro.lic. On macOS, the first time only, allow the program to start:
xattr -d com.apple.quarantine gridpin-pro
3. Check that the licence is found: --about prints your organisation and the date the licence runs to.
./gridpin-pro --about
4. Run a command on a country sheet. These are the same .bin files the free gridpin reads; your subscription also brings fresh sheets every month. Each command below has an example, and ./gridpin-pro --help lists all of them.
./gridpin-pro dedupe spain.bin customers.csv -o groups.csv
How you get it
gridpin-pro comes with a Starter or Pro subscription, monthly or yearly. After you pay, your order page offers the program for macOS, Linux and Windows and a licence file for your organisation. Every command of the free engine stays free; gridpin-pro adds the commands on this page. Plans side by side: Free, Starter and Pro, feature by feature.
Every command reads CSV with commas, in UTF-8. A file Excel saved with semicolons (the default in much of Europe) or in a legacy encoding is not read: save it as “CSV UTF-8 (comma delimited)” first. The free gridpin batch takes --delimiter; the Pro commands do not.
Available from release 0.9.0.
The licence file
The program reads its licence from --license FILE, the variable GRIDPIN_PRO_LICENSE, a file gridpin-pro.lic next to the program or ~/.config/gridpin/pro.lic, and checks it on your machine. gridpin-pro --about prints whom the build serves and until when. When the subscription ends, the program keeps working for 30 days with a warning, then refuses to run until the licence is renewed.
dedupe: which rows name one address
Find the rows of a customer base that name one address, however they are written: before a mailing, a migration, a count of customers or a merge of two bases.
What you need
A CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | Gran Via 1, Madrid |
| other columns | no | kept as they are | id |
id,address 1,"Gran Via 1, Madrid" 2,GRAN VIA 1 MADRID 3,"Calle Gran Vía, 1, Madrid" 4,"Gran Via 3, Madrid"
Run it
1. Group the rows; --level street groups by street instead of house.
$ gridpin-pro dedupe spain.bin customers.csv -o groups.csv 4 rows, 2 groups, 2 rows merged into another, 0 rows without an answer
What you get
id,address,gp_group,gp_group_size,gp_canonical,gp_reason (gp_lat … left out) 1,"Gran Via 1, Madrid",1,3,1, 2,GRAN VIA 1 MADRID,1,3,1,"same street, number and commune; neither answer is weak" 3,"Calle Gran Vía, 1, Madrid",1,3,1,"same street, number and commune; neither answer is weak" 4,"Gran Via 3, Madrid",2,1,4,
| Column | Values | Meaning |
|---|---|---|
gp_group | number | rows with one number name one address |
gp_group_size | number | how many rows the group has |
gp_canonical | row number | the row that stands for the group (the first one) |
gp_reason | text | why the row joined the group; empty for the row that stands for it |
How it works
Every row is geocoded once; rows whose answers are the same house (street, number and town) join one group. A weak answer never joins a group at house level, so a guess does not merge two customers. --level street groups by street, weak answers included.
Limits
- Two flats of one house are one group: the flat is not compared.
- A row the sheet does not find stays alone, with no group to join.
- A row without a town takes the town the engine finds, and can join the group of a full address in another town: check groups whose rows name no town.
All its keys: command reference.
compare: what changed between two runs
See what changed between two geocoded files: before switching to a new sheet or engine, or each month, to know which customers got other coordinates.
What you need
Two files written by gridpin batch
| Field | Required | What it holds | Example |
|---|---|---|---|
| the gp_ columns | yes | the answers gridpin batch adds | gp_lat, gp_housenumber |
| a key column | for --key | matches rows by it instead of by order | id |
Run it
1. Geocode both files with gridpin batch.
$ gridpin batch spain.bin march.csv march_g.csv $ gridpin batch spain.bin april.csv april_g.csv
2. Compare them.
$ gridpin-pro compare march_g.csv april_g.csv --key id -o changes.csv
{"rows": 3, "unchanged": 2, "changed": 1, "moved": 1, "fields_only": 0, "answer_to_empty": 0, …}
What you get
key,change,distance_m,fields,a_street,a_housenumber,b_street,b_housenumber (trimmed) 3,moved,2018.6,gp_housenumber,Paseo Castellana,100,Paseo Castellana,200
| Column | Values | Meaning |
|---|---|---|
change | moved, field, gained, lost | the point moved; only fields changed; an answer appeared; an answer was lost |
distance_m | metres | how far the point moved |
fields | names | which answer fields differ |
a_…, b_… | values | the answer before and after |
How it works
Rows are matched by the key (or by order) and their answers compared: a point that moved farther than --threshold-m (150 m by default) is a move, other differences are field changes. Rows whose key is in one file only are counted in the summary (only_in_a, only_in_b).
Limits
- It compares answers, not the reasons for them: a changed input and a changed sheet look the same.
- Without --key the two files must have the same rows in the same order.
All its keys: command reference.
batch --verdict and --report: an action for every row
Decide for every row whether to accept the address, confirm it with the customer or fix it, and hand over one page that shows it: for an order flow, a CRM import, a data-quality report.
What you need
A CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | 350 Fifth Avenue, New York, NY 10118 |
id,address 1,"1600 Pennsylvania Ave NW, Washington, DC 20500" 2,"1023 Fourth Ave, San Diego, CA 92101" 3,"350 Fifth Avenue, New York, NY 10118"
Run it
1. Judge the rows; --report also writes a page that opens without a network.
$ gridpin-pro batch usa.bin orders.csv orders_checked.csv --verdict --report orders_report.html
What you get
id,gp_match_level,gp_verdict_street,gp_verdict_number,gp_verdict_postcode,gp_verdict_city,gp_action 1,PointAddress,confirmed,confirmed,confirmed,confirmed,ACCEPT 2,StreetAddressExt,confirmed,replaced,confirmed,confirmed,CONFIRM 3,PointAddress,confirmed,confirmed,confirmed,confirmed,ACCEPT
| Column | Values | Meaning |
|---|---|---|
gp_verdict_street, _number, _postcode, _city | confirmed, corrected, inferred, replaced, unresolved, absent | what happened to each field of the address |
gp_action | ACCEPT, CONFIRM, UNMATCHED | what to do with the row |
How it works
Each field of the input is compared with the answer. ACCEPT: every field as asked or only filled in (a postcode the query lacked), and the place pinned by a confirmed town or postcode. CONFIRM with a person: see the list below. UNMATCHED: no answer, or the street or the number not found; check the address, then place the point by hand.
Limits
- CONFIRM when a field is corrected or replaced: no house 1023, a house nearby instead; house 5 for “5v”.
- CONFIRM when a town or a postcode the address names is not in the answer, or the engine chose the town because the address names none: the same street exists in many towns.
- CONFIRM when the engine calls the answer weak.
- A district of the named town (“Paris 11e Arrondissement” for “75011 Paris”) counts as that town.
- An address that is real but not where the customer lives is still ACCEPT: the check is of the address.
- A number inside the street name (“Bulevar 12. februara 80”, “2 de Mayo”) can make a found house
replacedand the row CONFIRM. - The rules are the same for every country; a field a country does not use is absent.
All its keys: command reference. Related: a page to check rows by hand.
audit: one page about an address base
One page about the health of an address base, to show a manager or a client: how much of it is usable, what repeats, what is missing and which rows to look at first.
What you need
A CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | Gran Via 1, Madrid |
id,address 1,"Gran Via 1, Madrid" 2,GRAN VIA 1 MADRID
Run it
1. Write the page.
$ gridpin-pro audit spain.bin customers.csv -o audit.html 4 rows, 2 repeat an earlier address, 0 without an answer, 0 answered but ambiguous; …
What you get
audit.html: Summary · Where the answers are · Distributions · Rows that name one address ·
Answered, but the street is in several towns · No answer, by reason ·
What happened to each field · Not in this country's file · Rows to look at
How it works
Every row is geocoded and judged as with --verdict; repeats are found as with dedupe. Rows the engine answers but doubts (the same street in towns far apart) are counted apart from rows without an answer. The page is one HTML file with everything inside: no network, no outside scripts.
Limits
- It describes the base as the sheet sees it; an address the sheet lacks reads as a problem of the base.
- Large bases: the page shows counts for all rows and examples for a sample.
All its keys: command reference. Related: dedupe, --verdict.
iso20022-check: the postal addresses of a payment message
Check the postal addresses of a payment message against the sheet and write a copy with better ones: for a payments team moving to structured addresses in ISO 20022 (pain.001, pacs.008).
What you need
A payment message
| Field | Required | What it holds | Example |
|---|---|---|---|
| an XML message | yes | every PstlAdr in it, any party | payments.xml |
Run it
1. Read every address, report each one and write a copy where the sheet confirms what the payer wrote.
$ gridpin-pro iso20022-check france.bin payments.xml --csv report.csv --rewrite payments_checked.xml payments.xml: 3 postal addresses as written: unstructured 3 the sheet makes: hybrid 1, structured 2
What you get
ordinal,party,form,ctry,line,mode,checks,exists,action 1,Dbtr,unstructured,FR,"20 rue de la Paix, 75002 Paris",structured,,yes,rewritten 2,Cdtr,unstructured,FR,"5 avenue Anatole France, 75007 Paris",structured,street_corrected,yes,kept_unsure 3,Cdtr,unstructured,FR,"12 rue Inventee, 75001 Paris",hybrid,street_unknown,no,rewritten
| Column | Values | Meaning |
|---|---|---|
mode | structured, hybrid, none | the form the sheet can make, as in gridpin format --out iso20022 |
action | rewritten, kept_unsure, kept_postcode, kept_number, kept_town | what the copy holds, and why an address was kept |
checks | names | what the answer changed or could not confirm |
How it works
An address is rewritten only when the sheet's answer keeps what the payer wrote: the postcode, the house number with its letter and the town. Otherwise it stays as written and the report says why; every other byte of the message stays. A street the sheet does not know becomes the hybrid form: town and country in their elements, the rest of the line as written.
Limits
- Tested on pain.001 messages we built, not yet on a bank's; pacs.008, encodings other than UTF-8 and XSD validation are not tested yet.
- A town the sheet keeps by districts (Berlin) and a country whose sheet has no postcodes (Serbia) are kept as written: the copy needs the written town and postcode.
All its keys: command reference.
batch --append census: United States census geography
Add to every United States address its census tract, voting district and school district: for research, elections, school planning and grant reporting.
What you need
A CSV of United States addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | 350 Fifth Avenue, New York, NY 10118 |
The census layer folder
| Field | Required | What it holds | Example |
|---|---|---|---|
| us_tract.bin, us_vtd.bin, us_school.bin | yes | the Census Bureau's TIGER/Line outlines | comes with the build |
Run it
1. Name the folder with --layers and the layer with --append census.
$ gridpin-pro batch usa.bin voters.csv voters_census.csv --append census --layers census/
What you get
address,gp_street,gp_housenumber,gp_tract,gp_vtd,gp_school_district (trimmed) "1600 Pennsylvania Ave NW, Washington, DC 20500",PENNSYLVANIA Avenue Northwest,1600,11001980000,1100102-002;1100102-002,1100030
| Column | Values | Meaning |
|---|---|---|
gp_tract | 11 digits | the census tract (state, county, tract) |
gp_vtd | codes | the voting district; two when the point lies on a boundary |
gp_school_district | 7 digits | the school district |
How it works
The address is geocoded and its point looked up in the TIGER/Line outlines of the Census Bureau.
Limits
- United States only.
- Outlines are those of the TIGER/Line release named in the folder.
- The voting district is empty for about 7 % of addresses: the layer of this build lacks the voting districts of 261 counties.
All its keys: command reference.
sheet-diff: what changed between two releases of a country
See what changed between two releases of a country's sheet in your area: for a team that keeps addresses of a city or a delivery zone and wants to know what appeared, moved or disappeared.
What you need
Two sheets of one country
Your area (optional)
| Field | Required | What it holds | Example |
|---|---|---|---|
| a GeoJSON of polygons | for --zone | the area to keep | my_city.geojson |
Run it
1. Compare the releases, keep your area and write a short feed of the changes.
$ gridpin-pro sheet-diff serbia_old.bin serbia_new.bin -o changes.csv --zone my_city.geojson --feed changes.md 5093 added, 4923 removed, 10059 changed of 2537639 addresses
What you get
kind,street,housenumber,commune,old_lat,old_lon,old_postcode,new_lat,new_lon,new_postcode,moved_m moved,1. Maj,32,Babušnica,43.0694943,22.4119994,,43.0714943,22.4119994,,222.4
| Column | Values | Meaning |
|---|---|---|
kind | added, removed, moved, postcode | what happened to the address |
old_…, new_… | values | the address in each release |
moved_m | metres | how far it moved |
How it works
Addresses are matched by street, number and town. A match farther apart than --threshold (50 m) is a move; a match with another postcode is a postcode change.
Limits
- A renamed street reads as removed and added: the name is the key.
- The example compares a Serbian sheet with a copy in which rows were changed on purpose.
All its keys: command reference.
batch --prove: is the address in a document the declared one
Check that the address written in a document (a utility bill, a bank statement, a lease) is the address the customer gave you. For onboarding and know-your-customer checks, where a person looks at every document today.
What you need
A CSV with one row per customer
| Field | Required | What it holds | Example |
|---|---|---|---|
| a text column (any name) | yes | the document's text as OCR read it, or any text with the address in it | Factura: Calle Gran Via 1, 28013 Madrid |
| a declared column (any name) | yes | the address the customer typed | Gran Via 1 Madrid |
| other columns | no | kept as they are | id |
id,doc,address 1,"Factura: Calle Gran Via 1, 28013 Madrid","Gran Via 1 Madrid" 2,"Factura: Calle de Alcalá 42, Madrid","Gran Via 1 Madrid"
The country sheet
Run it
1. Name the two columns after --prove: text= the document, declared= the customer's address.
$ gridpin-pro batch spain.bin documents.csv checked.csv --prove text=doc,declared=address
What you get
id,doc,address,gp_prove,gp_prove_text_address,gp_prove_m (other gp_ columns left out) 1,"Factura: Calle Gran Via 1, 28013 Madrid",Gran Via 1 Madrid,same,"Calle Gran Via 1, 28013 Madrid",0.0 2,"Factura: Calle de Alcalá 42, Madrid",Gran Via 1 Madrid,different,"Calle de Alcalá 42, Madrid",150.8
| Column | Values | Meaning |
|---|---|---|
gp_prove | same, different, not_found_in_text, declared_unresolved | the verdict; same only when both name one house and neither answer is weak |
gp_prove_text_address | text | the address found in the document, as written there |
gp_prove_m | metres | the distance between the two answers |
How it works
The document's text is searched for addresses as gridpin extract does, and every address found is compared with the declared one by the rule of gridpin same: one house, neither answer weak. The row is same when any of them is the declared house, so the supplier's address in the heading of a bill does not hide the customer's below it; otherwise gp_prove_text_address is the nearest one.
Limits
- It reads text, not images: run OCR first.
- It is slow next to
batch: every address of the text is geocoded, about a second of processor time per document of a page. - It checks that the address is real and the same, not that the document is genuine.
- Two flats of one house are the same house: the flat number is not compared.
All its keys: command reference. Related: addresses in free text.
batch --risk and risk-calibrate: the chance a point is wrong
Give every row the chance that its point is wrong, the reason and what to ask the customer: to call only the customers whose address is doubtful, before a delivery, a visit or a contract.
What you need
A CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | Gran Via 1, Madrid |
id,address 1,Gran Via 1 Madrid 2,"Calle Mayor 300, Madrid" 3,"Calle Inventada 9, Madrid"
Your own outcomes (optional, for risk-calibrate)
| Field | Required | What it holds | Example |
|---|---|---|---|
| delivered | this or lat, lon | 1 if the delivery reached the address, 0 if not | 1 |
| lat, lon | this or delivered | where the address really is | 40.4192, -3.6981 |
id,address,delivered 1,Gran Via 1 Madrid,1 2,"Calle Mayor 300, Madrid",0
Run it
1. Add the risk with the built-in table.
$ gridpin-pro batch spain.bin orders.csv orders_risk.csv --risk
2. Optionally measure the table on your own outcomes and use it.
$ gridpin-pro risk-calibrate spain.bin delivered.csv > my_bins.json 3 rows in 2 bins $ gridpin-pro batch spain.bin orders.csv orders_risk.csv --risk --risk-table my_bins.json
What you get
id,address,gp_risk,gp_risk_reason,gp_ask 1,Gran Via 1 Madrid,0.039,the house is in the sheet, 2,"Calle Mayor 300, Madrid",0.185,the number is not in the sheet: a point between neighbours,the house number 3,"Calle Inventada 9, Madrid",1.000,no answer in the sheet,"the full address: street, number, town"
| Column | Values | Meaning |
|---|---|---|
gp_risk | 0 to 1 | the share of wrong points (farther than 150 m) among answers of this kind |
gp_risk_reason | text | why the answer is of this kind |
gp_ask | text | what to ask the customer; empty when nothing is needed |
How it works
Each answer falls in a bin: its level (house, a point between houses, street, town), whether the engine calls it weak, and its confidence band. The risk of a bin is the share of wrong points measured on known address points of eight countries. risk-calibrate measures the bins on your file instead: with delivered the risk becomes the share of failed deliveries.
Limits
- It is the risk of a wrong point, not of a failed delivery, unless you calibrate on deliveries.
- A bin needs at least 20 rows of your file; a smaller one takes the built-in rate of its level.
- An answer the engine calls weak never gets a risk below 0.6, whatever the table says.
- The output is the input with the three risk columns, without the point: for the point run
gridpin batchor--verdictas well. - Without an answer the risk is 1.
All its keys: command reference. Related: --verdict, --review.
batch --layers: region, building and nearby places for each address
Add to each address the region, the building and the nearest places of a kind: for territory planning, pricing by area, risk and site selection.
What you need
Your CSV
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column | Zmaj Jovina 5, Novi Sad |
id,address 1,"Bulevar oslobođenja 10, Novi Sad" 2,"Zmaj Jovina 5, Novi Sad"
The layers folder
| Field | Required | What it holds | Example |
|---|---|---|---|
| layers.json | yes | the registry: which files belong to which country | comes with the build |
| <cc>/<cc>_region.bin, _county.bin | for regions | region and county outlines | rs/rs_county.bin |
| <cc>/<cc>_buildings.bin | for building | building outlines with kind and floors | |
| <cc>/<cc>_poi_cat.csv.gz | for --near | places with a category |
Run it
1. See which categories the places layer of your country has.
$ gridpin-pro categories layers/rs/rs_poi_cat.csv.gz | head -3 restaurant,7209 personal_or_beauty_service,4308 fashion_and_apparel_store,2869
2. Name the layers to add with --append and the places with --near category:metres.
$ gridpin-pro batch serbia.bin clients.csv out.csv --layers layers/layers.json \
--append regions:region,county,building --near hospital:1000,elementary_school:500
What you get
address,gp_region,gp_county,gp_building_subtype,gp_levels,gp_rdi,gp_near_hospital_m (trimmed) "Bulevar oslobođenja 10, Novi Sad",RS-VO|Војводина,RS-VO|Јужнобачки управни округ,residential,6,residential,152 "Zmaj Jovina 5, Novi Sad",RS-VO|Војводина,RS-VO|Јужнобачки управни округ,commercial,2,commercial,283
| Column | Values | Meaning |
|---|---|---|
gp_<layer> | code|name | the outline the point lies in, for each region layer named |
gp_building_id, _subtype, gp_levels, gp_height_m | text, numbers | the building the point lies in |
gp_rdi | residential, commercial, mixed, unknown | the kind of address, from the building and nearby places |
gp_near_<category>, _m | name, metres | the nearest place of the category within the radius |
How it works
The address is geocoded, then its point is looked up in each layer named: a point takes the names of the outlines that hold it. Layers are files beside the sheets, built from Overture Maps and national statistics.
Limits
- Region layers exist for every country of the release; buildings and places for some cities first.
- A point on the edge of two outlines takes both.
- Places come from open data: a missing hospital in the data is missing in the answer.
All its keys: command reference.
batch --review and apply-review: a page to check rows by hand
Check by hand only the rows that need a person, on one page, and put the decisions back into the file: for an operator who cleans an address base without a database tool.
What you need
A CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | Gran Via 1, Madrid |
id,address 1,Gran Via 1 Madrid 2,"Calle Mayor 300, Madrid" 3,"Calle Inventada 9, Madrid"
decisions.csv, saved by the page
| Field | Required | What it holds | Example |
|---|---|---|---|
| row | yes | the row of the input | 2 |
| chosen_rank | yes | the candidate chosen on the page; 0 for not found; empty: not decided | 1 |
| note | no | the operator's note; it stays in this file | confirmed by phone |
# sheet sha256=c671b9a4… row,chosen_rank,note 2,1,"confirmed by phone" 3,0,""
Run it
1. Judge the rows and write the page.
$ gridpin-pro batch spain.bin orders.csv checked.csv --review review.html
2. Open review.html in a browser, choose a candidate or “not found” for each row, press Export decisions. The browser saves decisions.csv.
3. Put the decisions into the rows, with the same sheet.
$ gridpin-pro apply-review spain.bin decisions.csv checked.csv -o final.csv 3 rows
What you get
review.html: 2 CONFIRM: Calle Mayor 300, Madrid ( ) Calle Mayor 92, 28013 Madrid (near) ( ) not found
3 UNMATCHED: Calle Inventada 9, Madrid ( ) not found
final.csv:
id,address,gp_housenumber,gp_action,gp_review
1,Gran Via 1 Madrid,1,ACCEPT,kept
2,"Calle Mayor 300, Madrid",92,CONFIRM,1
3,"Calle Inventada 9, Madrid",,UNMATCHED,not_found
| Column | Values | Meaning |
|---|---|---|
gp_review | kept, unreviewed, a candidate number, not_found | kept: the row was not on the page; unreviewed: it was, and nobody chose; a number: the candidate chosen |
How it works
The page lists the rows whose action is CONFIRM or UNMATCHED, with up to five candidates each. It is one HTML file that opens without a network and sends nothing: the decisions leave only when you press Export. apply-review asks the sheet again and puts the chosen candidate into the row.
Limits
- Nothing is chosen in advance: a row left alone is exported undecided and comes back
unreviewed. - Decisions made on another sheet are refused: the first line of decisions.csv names the sheet. A row number past the end of the file, a rank the sheet has no candidate for, or a row decided twice is refused too, and nothing is written.
- The note is kept in decisions.csv, not copied into the output.
All its keys: command reference. Related: --verdict.
second-opinion: another geocoder's answers against ours
Compare the answers of another geocoder you already paid for with ours, row by row, without sending anything anywhere: before switching providers, or to find the rows where two geocoders disagree.
What you need
Their answers, a CSV
| Field | Required | What it holds | Example |
|---|---|---|---|
| the address | yes | address (generic), formatted_address (google), place_name (mapbox), label (here); or --column | Calle de Alcalá 42, Madrid |
| the point | yes | lat,lon (generic); lat,lng (google, here); latitude,longitude (mapbox); empty when they found nothing | 40.4250, -3.6800 |
address,lat,lon "Gran Via 1, Madrid",40.4193,-3.6983 "Calle de Alcalá 42, Madrid",40.4250,-3.6800 "Calle Mayor 5, Madrid",, "Calle Inventada 9, Madrid",40.4100,-3.7000
Run it
1. Compare; --format names whose columns the file has (generic by default).
$ gridpin-pro second-opinion spain.bin their.csv -o second.html --disagreements disagree.csv
{"agree":1,"agree_m":150.0,"disagree":1,"neither":0,"rows":4,"they_only":1,"we_only":1}
What you get
disagree.csv: address,bin,metres,our_lat,our_lon,their_lat,their_lon "Calle de Alcalá 42, Madrid",disagree,1585,40.4183835,-3.6965851,40.4250000,-3.6800000 "Calle Mayor 5, Madrid",we_only,,40.4163621,-3.7054973,, "Calle Inventada 9, Madrid",they_only,,,,40.4100000,-3.7000000
| Column | Values | Meaning |
|---|---|---|
bin | disagree, we_only, they_only, neither | farther apart than 150 m; only we answered; only they did; nobody did (agreeing rows are not listed) |
metres | metres | the distance between the two points |
How it works
Each address of their file is geocoded with the sheet and the two points compared. The page (second.html) shows the counts and examples; the CSV lists every row that does not agree.
Limits
- It says where two geocoders differ, not which one is right: look at the disagreements.
- Keep their terms of use in mind: some providers do not allow storing their answers.
All its keys: command reference.
registry-audit: defects of an address registry
List the defects of the address registry behind a sheet, to hand to whoever keeps it: a city's address office, a cadastre, a utility that keeps its own list of service addresses.
What you need
The country sheet, or a sheet built from your registry
Run it
1. Count the defects; --page also writes a page, -o the summary to a file.
$ gridpin-pro registry-audit monaco.bin --page registry.html
What you get
{"houses": 1772, "streets": 1175,
"counts": {"duplicate": 85, "form": 20, "gap": 2, "outlier": 0, "postcodes": 0},
"examples": {
"duplicate": [{"street": "Avenue Albert II", "number": "25", "commune": "Monaco", "apart_m": 78.0}, …],
"form": [{"commune": "Monaco", "names": ["Albert 1er", "Boulevard Albert 1er"]}, …],
"gap": [{"street": "Avenue Princesse Grace", "from": 1, "to": 40, "numbers": 15, "missing_share": 0.615}, …]},
"thresholds": {"duplicate_m": 50.0, "gap_min_houses": 20, "gap_share": 0.3, "outlier_m": 2000.0, "postcodes_min": 3}}
| Column | Values | Meaning |
|---|---|---|
duplicate | street, number, apart_m | one address twice, more than 50 m apart |
form | names | one name written with and without a type word, or with two, in one town |
gap | from, to, missing_share | a street of 20 houses or more with over 30 % of its numbers missing |
postcodes | street, codes | three postcodes or more on one street |
outlier | street, number, metres | a house more than 2 km from the middle of its street |
How it works
It walks every street and house of the sheet once and applies the five rules with the thresholds printed in the summary; gaps are counted per side, odd and even. Up to 50 examples per class; the page is one HTML file with the counts and the examples.
Limits
- A defect is a hint, not a verdict: two schools named “1” on one long avenue may both be right. Expect noise: on the Serbian sheet 1.8 % of houses are outliers (rural “Reon” numbering), and
formpairs a street with a square of the same name (“Trg …”). - On sheets that also hold named places as streets (the Monaco example: “BNP Paribas 1”) those show up as duplicates.
- It checks the registry inside itself; to compare it with the ground, use your own points.
All its keys: command reference.
propose-number: a number for a new building
Suggest the house number of a new building from the houses already on its street: for an address office, a developer or a utility that has to name a new connection.
What you need
The country sheet
The new building
| Field | Required | What it holds | Example |
|---|---|---|---|
| --street | yes | the street, as people write it | boulevard des moulins |
| --city | yes | the town | monaco |
| --lat, --lon | yes | where the entrance is | 43.7430, 7.4287 |
Run it
1. Ask for a number.
$ gridpin-pro propose-number monaco.bin --street "boulevard des moulins" --city monaco --lat 43.7430 --lon 7.4287
What you get
{"street": "Boulevard des Moulins in Monaco", "candidates": ["22", "20"], "parity": "even",
"before": {"number": "18", "metres": 78.6}, "after": {"number": "24", "metres": 26.9},
"confidence": "high", "reason": "between two neighbours of the same side within 100 m",
"axis_deg": 31.4, "axis_m": 40.8, "houses_on_side": 5}
| Column | Values | Meaning |
|---|---|---|
candidates | numbers | the free numbers between the neighbours, best first; “12A” when none is free |
parity | even, odd | the parity of the side the point is on |
before, after | number, metres | the neighbours on the same side |
confidence | high, medium, low | high: two neighbours within 100 m; medium: one farther, or the end of the street; low: fewer than 3 houses on that side, or the point within 4 m of the axis |
axis_m | metres | how far the point is from the axis of the street; closer than 4 m the side, and so the parity, is a guess |
How it works
The houses of the street give its axis and the direction in which numbers grow. The point is placed on the axis and on a side; the side's usual parity is kept, and a free number between the two neighbours is proposed, nearest to where the point falls between them. A number any house of the street already has, on either side, is never proposed.
Limits
- It knows only the numbers in the sheet: a house missing from the data looks like a free number.
- It assumes numbers grow along the street with one parity per side; a street numbered otherwise gets a wrong proposal, so read the neighbours it names.
All its keys: command reference.
noise-profile: how your users write addresses
Measure how your own users write addresses (missing commas, no accents, abbreviations) and make test addresses with the same mix of mistakes: for testing a form, a CRM or a geocoder on realistic input.
What you need
A file of addresses as your users typed them
| Field | Required | What it holds | Example |
|---|---|---|---|
| q (or query) | yes | one address as typed | c/ Alcala, 42, 28014 Madrid, España |
{"q": "CALLE GRAN VIA 1 MADRID"}
{"q": "c/ Alcala, 42, 28014 Madrid, España"}
{"q": "Gran Vía nº 1"}
Run it
1. Measure the mix (here three lines; use a few hundred).
$ gridpin-pro noise-profile es --sets typed.jsonl > es_profile.json
2. Make test addresses with that mix in Python.
import json
from gridpin.messy import messy
profile = json.load(open("es_profile.json"))
messy("Calle de Alcalá 42, 28014 Madrid", profile=profile["classes"], seed=1)
('Calle de Alcalá 42 28014 Madrid', 'comma_removal_whitespace')
What you get
{"cc": "es", "classes": {"comma_removal_whitespace": 0.6667, "diacritics_stripped": 0.3333,
"case_lowered": 0.0, "street_type_abbreviation": 0.0, …},
"not_measured": ["street_type_dropped", "tail_locality_first", "typo_transposition"]}
| Column | Values | Meaning |
|---|---|---|
classes | share 0–1 | the share of lines with each kind of mistake |
not_measured | names | kinds that leave no mark on one line and are not counted |
How it works
Each line is checked for the marks of each kind of mistake that gridpin.messy makes. The shares become the weights of messy, so generated addresses carry the same mix.
Limits
- A share is a floor: a dropped street word leaves no mark and is not counted, and a street type is counted as abbreviated only with a dot (“St.”, not “St”), so for the United States that share is far below the truth.
- It needs your own typed addresses; the more lines, the steadier the shares.
gridpin.messyadds the same flat and phone noise in every country (“apt 2”, “+00 10 …”), transliterates only Serbian Cyrillic, can move a capital letter inside a word with a typo (“aPris”), and with the last segment first moves the country to the front when the address ends with it. An empty line comes back with noise; a value that is not text is an error.
All its keys: command reference. Related: negatives: addresses that do not exist.
negatives: addresses that do not exist
Make a test set of addresses that look real but do not exist, to check that your form, validator or model refuses them instead of inventing a place.
What you need
The country sheet
Run it
1. Write the addresses; -n is how many, --seed makes the set repeatable.
$ gridpin-pro negatives spain.bin -n 5 -o not_real.csv 5 addresses that do not exist
What you get
query,way,gp_expected "Diseminados Diseminado 6, Beamud",other_commune,street "Calle Iglesia De La 6, 15855 Petín",other_postcode,none "Calle Guarnicioneros (de Los) 82, Morón de la Frontera",past_range,street "Calle Consul Lucio Cornelio Balbo 4, Tribaldos",other_commune,none "Calle Norte 4, 34844 Munera",other_postcode,none
| Column | Values | Meaning |
|---|---|---|
way | past_range, other_commune, other_postcode | a number past the street's last; a real street and number in another town; another postcode |
gp_expected | none, street, city | what GridPin answers to it: nothing, the street, the town |
How it works
Each address is made from a real one by one change, then asked of the engine; it is kept only when the engine does not find a house with that number, so none of them exists in the sheet.
Limits
- “Does not exist” means not in the sheet: a house built last month may be real.
- The street names keep the sheet's spelling, which can look odd (“Iglesia De La”).
All its keys: command reference. Related: noise-profile.
synth: synthetic households on real addresses
Place synthetic households on real addresses, with no names and no personal data: a starting population for transport, epidemic or service-area models when real records cannot be used.
What you need
The country sheet
Weights (optional)
| Field | Required | What it holds | Example |
|---|---|---|---|
| area | yes | a town of the sheet | Madrid |
| weight | yes | its share of the households, any scale | 3 |
area,weight Madrid,3 Sevilla,1
Run it
1. Make the households; --seed makes the run repeatable.
$ gridpin-pro synth spain.bin --households 1000 --weights weights.csv -o households.csv 1000 households, 2170 people
What you get
household,person,household_size,age_band,sex,street,housenumber,postcode,commune,lat,lon 1,1,2,35-64,M,Calle Lopez De Gomara,24,41010,Sevilla,37.3768820,-6.0065101 1,2,2,65+,F,Calle Lopez De Gomara,24,41010,Sevilla,37.3768820,-6.0065101 2,1,1,35-64,F,Calle Pacheco Y Nuñez De Prado,51,41002,Sevilla,37.4022677,-5.9933294 … (of the 1000 households: Madrid 739, Sevilla 261)
| Column | Values | Meaning |
|---|---|---|
household, person | numbers | one row per person |
age_band | 0-17, 18-34, 35-64, 65+ | drawn with shares 20, 25, 40 and 15 % |
sex | F, M | drawn half and half |
| street … lon | values | a real address of the sheet |
How it works
Each household is a real address drawn from the sheet, from the towns in the weights in proportion, or from all addresses alike. Its size is 1 to 5 people with shares 35, 30, 15, 12 and 8 %; age and sex are drawn per person.
Limits
- The shares of size, age and sex are stated assumptions, not census figures of any country.
- One address can hold two households; a flat is not chosen.
All its keys: command reference.
id-map and lifecycle: addresses across releases
Keep a stable id for every address across releases of the sheet, so a database is updated by the changes instead of geocoded again: for a customer base, a registry mirror or a delivery system.
What you need
Two or more releases of one country's sheet
Your rows with ids (optional)
Run it
1. Give your rows the id of their house (only rows found to the house get one).
$ gridpin-pro batch spain.bin orders.csv orders_id.csv --append id id,address,…,gp_address_id 1,Gran Via 1 Madrid,…,fdabea3525ce66dc
2. When a new release comes, list what changed.
$ gridpin-pro id-map monaco_old.bin monaco_new.bin -o ids.csv
{"added":411,"moved":2,"same":1019}
3. Across several releases, in version order: when each address appeared, left and moved.
$ gridpin-pro lifecycle monaco_2026-09.bin monaco_2026-10.bin monaco_2026-11.bin -o life.csv
What you get
old_id,new_id,kind,street,number,commune,moved_m 003816b2be95fcb4,003816b2be95fcb4,same,Le Parador II,1,Monaco,0.0 cd35a6eb630412eb,cd35a6eb630412eb,moved,Route de la Moyenne Corniche,1,Monaco,302.0 ,002839f3c1ba269d,added,Boulevard d'Italie,44,Monaco, life.csv: address_id,first_seen,last_seen,moves,status
| Column | Values | Meaning |
|---|---|---|
kind | same, moved, removed, added | moved: the same id more than 50 m away |
first_seen, last_seen | releases | the first and last release that has the id |
moves | number | how many times the point moved |
How it works
The id is a hash of the country, town, street and number, each written one way: two releases give one id to one address as long as its name stays the same.
Limits
- A renamed street reads as removed and added: nothing here guesses that two names are one street.
- The example compares a Monaco sheet with a cut of the same sheet (the 411 houses outside the cut).
All its keys: command reference. Related: sheet-diff.
match-keys and match-intersect: common customers through an intermediary
Find the customers two companies share, through an intermediary, without either company handing its address list to anyone: for a co-marketing deal, a merger check or a partner programme.
What you need
Each side: its own CSV of addresses
| Field | Required | What it holds | Example |
|---|---|---|---|
| an address column | yes | the address; address, query or q, or name it with --column (several columns: --column street,city,postcode) | Gran Via 1, Madrid |
id,address 1,"4 Avenue de la Madone, Monaco" 2,"4 av. de la Madone 98000 Monaco" 3,"Boulevard des Moulins 18, Monaco"
A secret both sides share
| Field | Required | What it holds | Example |
|---|---|---|---|
| --secret | yes | at least 32 hex digits, agreed privately; never sent to the intermediary | openssl rand -hex 32 |
One release of the sheet
Run it
1. Both sides agree on a secret and a sheet release.
$ openssl rand -hex 32
2. Each side, on its own machine, turns its rows into keys.
$ gridpin-pro match-keys monaco.bin customers.csv --secret $SECRET -o keys_a.csv 3 rows
3. Each side sends only its key file to the intermediary, who compares them without the secret.
$ gridpin-pro match-intersect keys_a.csv keys_b.csv -o common.csv
{"a":2,"b":2,"common":1,"common_houses":1}
4. The intermediary returns common.csv; each side finds its own rows by key.
What you get
keys_a.csv: # gridpin match-keys sheet country=mc release=2026-11-06 row,key,level 1,ff15cf14c3790f6e8e7fd4a38cbbac6c8adae28af698c0cd0b19c7c3eafc7e0d,house 2,ff15cf14c3790f6e8e7fd4a38cbbac6c8adae28af698c0cd0b19c7c3eafc7e0d,house 3,ed3177ec8156595d206b087627b7b072c7f499edd5afd9fa56ba27c3f0915d28,house common.csv: key,level ff15cf14c3790f6e8e7fd4a38cbbac6c8adae28af698c0cd0b19c7c3eafc7e0d,house
| Column | Values | Meaning |
|---|---|---|
row | number | the row of your file; the key file holds no address and no id of yours |
key | 64 hex digits | HMAC-SHA256 of the address id with the secret |
level | house, street | a row found only to the street gets a street key; one not found, none |
How it works
Each row is geocoded and its address id taken (country, town, street and number written one way, hashed with BLAKE2b); the key is that id passed through HMAC-SHA256 with the secret. Two spellings of one house give one key (rows 1 and 2 above). Files made on different sheet releases are refused, because one house would get two ids.
Limits
- Without the secret a key says nothing about the address; the intermediary learns the size of each file and of the overlap, and nothing else.
- The list of all addresses is public (it is the sheet): whoever holds the secret and the other side's key file can read that side's whole list. So key files go only to the intermediary, and the secret never does.
- A street key says which street a customer lives on: leave street rows out when that is already too much.
- Comparing directly, with neither side able to read the other, needs private set intersection; this is not that.
All its keys: command reference.
verify-chain: check a folder of receipts
Prove later that a geocoded file is the one a run produced and that no run in a folder was changed or dropped: for audits, regulated reporting and research that must be repeatable.
What you need
A folder of outputs written with gridpin batch --receipt
| Field | Required | What it holds | Example |
|---|---|---|---|
| <output>.receipt.json | yes | written beside each output by --receipt | april_out.csv.receipt.json |
Run it
1. Write a receipt with every run (the free engine does it).
$ gridpin batch spain.bin march.csv march_out.csv --receipt $ gridpin batch spain.bin april.csv april_out.csv --receipt
2. Check the folder.
$ gridpin-pro verify-chain . chain holds: 2 receipts
What you get
after an output was edited by hand:
Error: chain broken after 1 receipts: ./march_out.csv: the output differs from its receipt ./march_out.csv.receipt.json
april_out.csv.receipt.json:
{"command": ["gridpin", "batch", "spain.bin", "april.csv", "april_out.csv", "--receipt"],
"engine": "0.8.0", "input": {"file": "april.csv", "sha256": "7f4b6a23…"},
"output": {"file": "april_out.csv", "sha256": "fb1dbac4…"}, "prev": "22888a1c…", "rows": 1,
"sheet": {"file": "es.bin", "sha256": "c671b9a4…"}, …}
| Column | Values | Meaning |
|---|---|---|
prev | sha256 | the hash of the previous receipt of the folder: the link of the chain |
input, output, sheet | file, sha256 | what the run read and wrote |
How it works
Each receipt names the engine, the sheet, the input, the output and the command with their hashes, and the hash of the receipt before it. verify-chain walks the receipts in order, checks every link and hashes every output again; it names the first break and exits with 1.
Limits
- It catches a changed or missing file, not a forged chain: whoever can write the folder can write new receipts.
- Keep the sheet file: the receipt names its hash, not its content.
All its keys: command reference. Related: cite and --receipt.
serve: the server with keys, limits and metrics
Run the geocoding server for a team: a key per user or system, a daily limit each, metrics for monitoring, a request log, and batch jobs for files.
What you need
keys.csv
| Field | Required | What it holds | Example |
|---|---|---|---|
| key | yes | the secret the client sends, 16 characters or more | ops-7f3a9c41d2e8b605 |
| name | yes | who it is, as shown in metrics and the log | ops team |
| daily_limit | yes | requests per UTC day, a job counting its rows; 0: no limit | 2 |
key,name,daily_limit ops-7f3a9c41d2e8b605,ops team,2 batch-91c2e7a04b3f6d18,nightly batch,0
Run it
1. Start the server; --jobs-dir enables jobs, --log writes one line per request.
$ gridpin-pro serve monaco.bin --keys keys.csv --log requests.jsonl --jobs-dir jobs/ listening on http://127.0.0.1:8080
2. Ask with a key: the third request of a limit of 2 is refused.
$ curl -H "Authorization: Bearer ops-7f3a9c41d2e8b605" "http://127.0.0.1:8080/v1/geocode?q=4+avenue+de+la+madone+monaco" 200 200 429 (and 401 without a key)
3. Send a file as a job and fetch the result.
$ curl -H "Authorization: Bearer batch-91c2e7a04b3f6d18" -H "X-Address-Column: address" --data-binary @job.csv http://127.0.0.1:8080/v1/jobs
{"id":"106a1d2a632b80073be2d7da43ea7bb7"}
4. Another key asks for that job: it is not its own.
$ curl -H "Authorization: Bearer ops-7f3a9c41d2e8b605" http://127.0.0.1:8080/v1/jobs/106a1d2a632b80073be2d7da43ea7bb7/result 404 no such job
What you get
$ curl http://127.0.0.1:8080/metrics
gridpin_requests_total{key="-",status="401"} 1
gridpin_requests_total{key="ops team",status="200"} 2
gridpin_requests_total{key="ops team",status="429"} 1
$ curl … /v1/jobs/106a1d2a632b80073be2d7da43ea7bb7
{"column":"address","created":1794427813,"done_rows":2,"id":"106a1d2a632b80073be2d7da43ea7bb7","result_url":"/v1/jobs/106a1d2a632b80073be2d7da43ea7bb7/result","rows":2,"state":"done","webhook":null}
$ curl … /v1/jobs/106a1d2a632b80073be2d7da43ea7bb7/result
id,address,gp_lat,gp_lon,gp_precision,gp_confidence,gp_weak,gp_street,gp_housenumber,gp_postcode,gp_commune,gp_region,gp_match_level,gp_match_status
1,4 avenue de la madone monaco,43.7409352,7.4279184,house,0.94,false,Avenue de la Madone,4,98000,Monaco,,PointAddress,M
requests.jsonl:
{"key":"-","ms":0.56,"path":"/metrics","status":200,"t":1794239974}
How it works
It is the free server with a gate in front: the routes and the answers are the same, the gate checks the key, counts the day and writes the log. Jobs live in the jobs folder and survive a restart; one worker answers them in order and, with X-Webhook: http://…, calls your address when done.
Limits
/metricsneeds no key: keep the server on 127.0.0.1 or behind your proxy.- The log has no query text unless you add
--log-queries, and then only theqparameter of GET requests: POST bodies, structured fields, Google'saddressand/v1/extractare not logged. - There is no TLS: put it behind your own proxy; webhooks are plain http on your network.
- Daily counts live in memory: a restart starts the day again.
- A job belongs to the key that sent it: another key gets 404. The result has the
gp_columns ofgridpin batch,gp_weakincluded.
All its keys: command reference. Related: the free server.
learn-grammar: how the addresses of a country are built
Set up an address form, a validator or a parser for a new country from how its addresses are really written. Example: a shop opening in France checks house numbers with ^[0-9]+$; 7.7 % of French houses are written like “1bis” or “44b”, and the form would refuse those customers.
What you need
The country sheet
Run it
1. Count the forms over every house of the sheet (France: 26 million houses, 15 seconds).
$ gridpin-pro learn-grammar france.bin -o fr.grammar.json 26097974 houses, 2194468 streets: type first 1726288, last 8209, none 459971
What you get
{"house_number": {"position": "after_street", "forms": [
{"regex": "^[0-9]+$", "share": 0.9217, "examples": ["1", "2", "5"]},
{"regex": "^[0-9]+ ?[A-Za-z]{1,4}$", "share": 0.0772, "examples": ["44b", "1bis", "7bis"]},
{"regex": ".+", "share": 0.0011, "examples": ["1droite", "2droite", "108lot3"]}]},
"postcode": {"patterns": [{"regex": "^[0-9]{5}$", "share": 1.0}]},
"street": {"type_position": "first", "no_type_share": 0.2096,
"types": [{"word": "rue", "share": 0.3518}, {"word": "chemin", "share": 0.1361}, …],
"particles": [{"word": "de", "share": 0.311}, {"word": "des", "share": 0.1773}, …]},
"locality": {"hyphen_share": 0.3939, …}, …}
| Column | Values | Meaning |
|---|---|---|
house_number.forms | pattern, share, examples | how house numbers are written: the patterns to accept |
postcode.patterns | pattern, share | the forms of the postcode |
street.type_position, types | first, last; words | where the street type stands (Rue …, … Street) and which types there are |
street.particles | word, share | small words inside names: what a parser must not drop |
locality.hyphen_share | share | town names with a hyphen (Saint-Rémy-de-Provence) |
How it works
It walks every house and street of the sheet and counts the forms by kind of character, in the open schema of gridpin grammar, which prints the rules a sheet declares without counting. --limit counts about that many houses, taken evenly over the sheet.
Limits
- It describes the data of the sheet, not how people type: for that, see noise-profile.
- Shares of streets count each street once; shares of numbers count houses.
All its keys: command reference. Related: gridpin grammar, noise-profile.
An offline bundle for networks without internet
For a network with no internet access we put together one folder: the engine for your systems, the sheets you choose, the licences, the signed receipt of the sheets, checksums of every file and the commands to verify them, and, if you need it, a record of the program's network calls. Ask at support@gridpin.dev.