Addresses
rigour.addresses
This module provides a set of tools for handling postal/geographic addresses. It includes functions for comparing addresses, normalising them, and for formatting addresses given in parts for display as a single string.
Address comparison
Score whether two address strings (or two sets of them) denote the same place:
from rigour.addresses import compare_address, match_addresses
score = compare_address("Bahnhofstr. 10, Augsburg", "Bahnhofstrasse 10, 86150 Augsburg")
match = match_addresses(
["Bahnhofstrasse 10, Augsburg"],
["Bahnhofstrase 10, 86150 Augsburg", "P.O. Box 71, Augsburg"],
)
# match.result == "Bahnhofstrase 10, 86150 Augsburg"
# match.detail == "bahnhofstrasse~bahnhofstrase 10 augsburg +86150"
compare_address_many is the same pairing returning only the score.
The comparison runs in native code over analyzed tokens — see compare_address for the mechanics and score semantics.
The same analysis backs a keying surface: use address_fingerprint to reduce equivalent renderings of an address to one deterministic string for deduplication or graph node identity:
from rigour.addresses import address_fingerprint
key = address_fingerprint("Main Boulevard 5, Syrian Arab Republic")
# "main blvd 5 sy" — same key as for "Main Blvd. 5, Syria"
Postal address formatting
This set of helpers is designed to help with the processing of real-world addresses, including composing an address from individual parts, and cleaning it up.
from rigour.addresses import format_address_line
address = {
"road": "Bahnhofstr.",
"house_number": "10",
"postcode": "86150",
"city": "Augsburg",
"state": "Bayern",
"country": "Germany",
}
address_text = format_address_line(address, country="DE")
Acknowledgements
The address formatting database contained in rigour/data/addresses/formats.yml is
derived from worldwide.yml in the OpenCageData address-formatting
repository. It is used to
format addresses according to customs in the country that is been encoded.
AddressMatch
The best pair out of a list×list address comparison, with the evidence that produced it.
__doc__ = 'The best pair out of a list×list address comparison, with the\nevidence that produced it.'
class-attribute
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.
__module__ = 'rigour._core'
class-attribute
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.
detail
property
One-line alignment summary over comparable token forms:
aligned tokens in query order (berlin when identical,
boulevard~blvd when aligned by edit distance or class
equivalence), then -tok for query-only and +tok for
result-only tokens.
query
property
Query-side address of the winning pair, as supplied.
result
property
Result-side address of the winning pair, as supplied.
score
property
Similarity of the winning pair in [0.0, 1.0].
__repr__()
method descriptor
Return repr(self).
address_fingerprint(text)
Serialize an address string into a deterministic key for deduplication and graph node identity.
The address is analyzed into classed tokens and each token is
reduced to its most canonical form: numbers become plain digit
strings (№17, 17. and 17 all key as 17), address
keywords become their canonical short form (Boulevard and
blvd. both key as blvd), unambiguous territory names in any
supported language become their territory code (Syria, Syrian
Arab Republic and Сирия all key as sy), and free text is
transliterated where a narrow, systematic romanization exists.
Token order is preserved: addresses that differ only by
transposed numbers (д. 17 стр. 1 versus д. 1 стр. 17) must
not key identically, so differently ordered renderings of one
address also produce different keys — collapsing those is left
to fuzzier machinery. Use this function to key or deduplicate address
records; to score how similar two addresses are, use
compare_address,
which shares the same analysis (and its cache) but aligns tokens
order-independently and fuzzily.
The output is lowercase, space-separated, and ASCII except for
free-text tokens in scripts without a systematic romanization
(Chinese, Arabic, ...), which pass through in their native
script. Callers that require pure-ASCII identifiers should
slug-encode the result (e.g. normality.slugify). Fingerprints
are stable within one version of rigour but may change between
versions as the underlying resources grow; do not persist them
across upgrades without re-keying.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
An address, as one full string. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The fingerprint string, or |
str | None
|
contains nothing analyzable (only punctuation). |
Source code in rigour/addresses/compare.py
clean_address(full)
compare_address(query, result)
Compare two address strings, scoring how likely they denote the same place.
Both strings are analyzed into classed tokens (numbers, keyword
signifiers like str./ул., territory names, free text) and
greedily aligned: numbers must match exactly, keywords match
across alias forms (boulevard/blvd), territory names match
across languages via their code (Syria/Сирия), and free text
matches by edit distance over transliterated forms. The score is
the length-weighted share of aligned tokens, and a pair of
numbers where each side asserts a value the other lacks (a
differing house or unit number) is penalized far beyond its
length. Comparison is order-independent, so differently arranged
address parts do not lower the score.
Score semantics, measured on the labelled benchmark corpus in
contrib/address_bench: equivalent renderings of one address
score 1.0; transliterated or partly translated matches typically
land between 0.5 and 0.9; an address and its less specific
prefix (street dropped, city kept) around 0.6; pairs with
conflicting house or unit numbers are pushed toward 0.0. The
accuracy-optimal decision threshold on that corpus is ~0.3 —
substantially lower than typical name-similarity calibrations.
Analysis results are memoized in a process-wide cache, so comparing one address against many candidates re-analyzes the repeated side only once. The GIL is released while comparing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
An address, as one full string. |
required |
result
|
str
|
The address to compare against, as one full string. |
required |
Returns:
| Type | Description |
|---|---|
float
|
A similarity score between 0.0 and 1.0. Empty or |
float
|
punctuation-only input scores 0.0 against everything. |
Source code in rigour/addresses/compare.py
compare_address_many(queries, results)
Compare two sets of address strings and return the best pairwise score.
The score-only form of match_addresses: same pairing, same analysis cache, without the match object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[str]
|
Addresses of one entity, each as one full string. |
required |
results
|
list[str]
|
Addresses of the other entity, each as one full string. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The highest pairwise similarity score between 0.0 and 1.0; |
float
|
0.0 when either list is empty or nothing is comparable. |
Source code in rigour/addresses/compare.py
format_address(address, country=None)
Format the given address part into a multi-line string that matches the conventions of the country of the given address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
dict[str, str | None]
|
The address parts to be combined. Common parts include: summary: A short description of the address. po_box: The PO box/mailbox number. street: The street or road name. house: The descriptive name of the house. house_number: The number of the house on the street. postal_code: The postal code or ZIP code. city: The city or town name. county: The county or district name. state: The state or province name. state_district: The state or province district name. state_code: The state or province code. country: The name of the country (words, not ISO code). country_code: A pre-normalized country code. |
required |
country
|
str | None
|
ISO code for the country of the address. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A single-line string with the formatted address. |
Source code in rigour/addresses/format.py
format_address_line(address, country=None)
Format the given address part into a single-line string that matches the conventions of the country of the given address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
dict[str, str | None]
|
The address parts to be combined. Common parts include: summary: A short description of the address. po_box: The PO box/mailbox number. street: The street or road name. house: The descriptive name of the house. house_number: The number of the house on the street. postal_code: The postal code or ZIP code. city: The city or town name. county: The county or district name. state: The state or province name. state_district: The state or province district name. state_code: The state or province code. country: The name of the country (words, not ISO code). country_code: A pre-normalized country code. |
required |
country
|
str | None
|
ISO code for the country of the address. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A single-line string with the formatted address. |
Source code in rigour/addresses/format.py
match_addresses(queries, results)
Compare two sets of address strings and return the best pairwise match with the evidence behind it.
Scores every query against every result with
compare_address and
returns the winning pair as an
AddressMatch: its score, the two
input strings that produced it (query, result), and a
one-line detail describing how the tokens aligned. Each
distinct string is analyzed only once, so the list×list loop is
substantially cheaper than the equivalent pairwise calls.
The detail line lists analyzed tokens (lowercased, narrowly
transliterated) separated by spaces: aligned tokens first, in
query order, then the leftovers of each side.
| form | meaning |
|---|---|
berlin |
aligned, identical on both sides |
boulevard~blvd |
aligned by edit distance, keyword alias or territory code (query left, result right) |
-10115 |
only in the query address |
+la |
only in the result address |
A line without ~, - or + is an exact match; leftovers on
one side only mark a subset relation; a -5 +7 pair of numbers
is the penalized house- or unit-number conflict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[str]
|
Addresses of one entity, each as one full string. |
required |
results
|
list[str]
|
Addresses of the other entity, each as one full string. |
required |
Returns:
| Type | Description |
|---|---|
AddressMatch | None
|
The best-scoring pair, or |
AddressMatch | None
|
contains nothing analyzable (only punctuation). A pair with |
AddressMatch | None
|
nothing in common is still returned, with a score of 0.0. |
Source code in rigour/addresses/compare.py
normalize_address(address, latinize=False, min_length=4)
Build a comparison key from an address.
Casefolds, replaces punctuation/symbols with whitespace,
tokenises on Unicode general-category, and rejoins with
single-space separators. The output is a flat lowercase token
sequence suitable for substring matching, equality keys, or
feeding :func:shorten_address_keywords /
:func:remove_address_keywords — not a display form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
The address to normalise. |
required |
latinize
|
bool
|
When |
False
|
min_length
|
int
|
Reject the result as |
4
|
Returns:
| Type | Description |
|---|---|
str | None
|
Normalised address, or |
str | None
|
than |
Source code in rigour/addresses/normalize.py
remove_address_keywords(address, latinize=False, replacement=WS)
Strip common address keywords from a normalised address.
Deprecated
Use compare_address to compare addresses instead of comparing keyword-stripped strings. This function will be removed in a future version.
Removes recognised forms ("street", "road", "south",
territory names, ordinals, …) by substituting each match with
replacement. Consecutive matches produce consecutive
replacement runs — whitespace is not collapsed, so the
output may contain multi-space gaps. Use
normality.squash_spaces afterwards if a single-space
output is wanted.
Input must already be normalised with :func:normalize_address
using the same latinize flag — the alias table is built
against that normalised form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
A pre-normalised address string. |
required |
latinize
|
bool
|
Must match the flag passed to
:func: |
False
|
replacement
|
str
|
String substituted in place of each match. Defaults to a single ASCII space. |
WS
|
Returns:
| Type | Description |
|---|---|
str | None
|
The address with recognised keywords removed. |
Source code in rigour/addresses/normalize.py
shorten_address_keywords(address, latinize=False)
Shorten common address keywords in a normalised address.
Deprecated
Use address_fingerprint, which reduces keywords to the same canonical short forms as part of a full keying serialization. This function will be removed in a future version.
Replaces recognised forms with their canonical short form
("street" → "st", "avenue" → "av", "united arab
emirates" → "ae", …). Multi-token forms beat single-token
components via longest-form-first ordering in the alias
pattern, so country names win over their constituent words.
Input must already be normalised with :func:normalize_address
using the same latinize flag — the alias table is built
against that normalised form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
A pre-normalised address string. |
required |
latinize
|
bool
|
Must match the flag passed to
:func: |
False
|
Returns:
| Type | Description |
|---|---|
str | None
|
The address with recognised keywords shortened. Tokens |
str | None
|
that don't match any alias pass through unchanged. |
Source code in rigour/addresses/normalize.py
rigour.addresses.compare
Compare postal address strings for referring to the same place.
AddressMatch
The best pair out of a list×list address comparison, with the evidence that produced it.
__doc__ = 'The best pair out of a list×list address comparison, with the\nevidence that produced it.'
class-attribute
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.
__module__ = 'rigour._core'
class-attribute
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.
detail
property
One-line alignment summary over comparable token forms:
aligned tokens in query order (berlin when identical,
boulevard~blvd when aligned by edit distance or class
equivalence), then -tok for query-only and +tok for
result-only tokens.
query
property
Query-side address of the winning pair, as supplied.
result
property
Result-side address of the winning pair, as supplied.
score
property
Similarity of the winning pair in [0.0, 1.0].
__repr__()
method descriptor
Return repr(self).
address_fingerprint(text)
Serialize an address string into a deterministic key for deduplication and graph node identity.
The address is analyzed into classed tokens and each token is
reduced to its most canonical form: numbers become plain digit
strings (№17, 17. and 17 all key as 17), address
keywords become their canonical short form (Boulevard and
blvd. both key as blvd), unambiguous territory names in any
supported language become their territory code (Syria, Syrian
Arab Republic and Сирия all key as sy), and free text is
transliterated where a narrow, systematic romanization exists.
Token order is preserved: addresses that differ only by
transposed numbers (д. 17 стр. 1 versus д. 1 стр. 17) must
not key identically, so differently ordered renderings of one
address also produce different keys — collapsing those is left
to fuzzier machinery. Use this function to key or deduplicate address
records; to score how similar two addresses are, use
compare_address,
which shares the same analysis (and its cache) but aligns tokens
order-independently and fuzzily.
The output is lowercase, space-separated, and ASCII except for
free-text tokens in scripts without a systematic romanization
(Chinese, Arabic, ...), which pass through in their native
script. Callers that require pure-ASCII identifiers should
slug-encode the result (e.g. normality.slugify). Fingerprints
are stable within one version of rigour but may change between
versions as the underlying resources grow; do not persist them
across upgrades without re-keying.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
An address, as one full string. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The fingerprint string, or |
str | None
|
contains nothing analyzable (only punctuation). |
Source code in rigour/addresses/compare.py
compare_address(query, result)
Compare two address strings, scoring how likely they denote the same place.
Both strings are analyzed into classed tokens (numbers, keyword
signifiers like str./ул., territory names, free text) and
greedily aligned: numbers must match exactly, keywords match
across alias forms (boulevard/blvd), territory names match
across languages via their code (Syria/Сирия), and free text
matches by edit distance over transliterated forms. The score is
the length-weighted share of aligned tokens, and a pair of
numbers where each side asserts a value the other lacks (a
differing house or unit number) is penalized far beyond its
length. Comparison is order-independent, so differently arranged
address parts do not lower the score.
Score semantics, measured on the labelled benchmark corpus in
contrib/address_bench: equivalent renderings of one address
score 1.0; transliterated or partly translated matches typically
land between 0.5 and 0.9; an address and its less specific
prefix (street dropped, city kept) around 0.6; pairs with
conflicting house or unit numbers are pushed toward 0.0. The
accuracy-optimal decision threshold on that corpus is ~0.3 —
substantially lower than typical name-similarity calibrations.
Analysis results are memoized in a process-wide cache, so comparing one address against many candidates re-analyzes the repeated side only once. The GIL is released while comparing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
An address, as one full string. |
required |
result
|
str
|
The address to compare against, as one full string. |
required |
Returns:
| Type | Description |
|---|---|
float
|
A similarity score between 0.0 and 1.0. Empty or |
float
|
punctuation-only input scores 0.0 against everything. |
Source code in rigour/addresses/compare.py
compare_address_many(queries, results)
Compare two sets of address strings and return the best pairwise score.
The score-only form of match_addresses: same pairing, same analysis cache, without the match object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[str]
|
Addresses of one entity, each as one full string. |
required |
results
|
list[str]
|
Addresses of the other entity, each as one full string. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The highest pairwise similarity score between 0.0 and 1.0; |
float
|
0.0 when either list is empty or nothing is comparable. |
Source code in rigour/addresses/compare.py
match_addresses(queries, results)
Compare two sets of address strings and return the best pairwise match with the evidence behind it.
Scores every query against every result with
compare_address and
returns the winning pair as an
AddressMatch: its score, the two
input strings that produced it (query, result), and a
one-line detail describing how the tokens aligned. Each
distinct string is analyzed only once, so the list×list loop is
substantially cheaper than the equivalent pairwise calls.
The detail line lists analyzed tokens (lowercased, narrowly
transliterated) separated by spaces: aligned tokens first, in
query order, then the leftovers of each side.
| form | meaning |
|---|---|
berlin |
aligned, identical on both sides |
boulevard~blvd |
aligned by edit distance, keyword alias or territory code (query left, result right) |
-10115 |
only in the query address |
+la |
only in the result address |
A line without ~, - or + is an exact match; leftovers on
one side only mark a subset relation; a -5 +7 pair of numbers
is the penalized house- or unit-number conflict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[str]
|
Addresses of one entity, each as one full string. |
required |
results
|
list[str]
|
Addresses of the other entity, each as one full string. |
required |
Returns:
| Type | Description |
|---|---|
AddressMatch | None
|
The best-scoring pair, or |
AddressMatch | None
|
contains nothing analyzable (only punctuation). A pair with |
AddressMatch | None
|
nothing in common is still returned, with a score of 0.0. |