@mountainpass/addressr 2.2.0 → 2.4.0
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.
|
@@ -111,6 +111,14 @@ async function initIndex(esClient, clear, synonyms) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
|
+
// ADR 026: range-number address expansion. Multi-valued text field
|
|
115
|
+
// populated with one expanded address per in-range number for range-
|
|
116
|
+
// numbered G-NAF docs (span cap 20). Absent on non-range docs
|
|
117
|
+
// (asymmetric population). No .raw sub-field — never sorted on.
|
|
118
|
+
sla_range_expanded: {
|
|
119
|
+
type: 'text',
|
|
120
|
+
analyzer: 'my_analyzer'
|
|
121
|
+
},
|
|
114
122
|
confidence: {
|
|
115
123
|
type: 'integer'
|
|
116
124
|
},
|
|
@@ -30,6 +30,7 @@ var _unzipStream = _interopRequireDefault(require("unzip-stream"));
|
|
|
30
30
|
var _elasticsearch = require("../client/elasticsearch");
|
|
31
31
|
var _streamDown = _interopRequireDefault(require("../utils/stream-down"));
|
|
32
32
|
var _setLinkOptions = require("./set-link-options");
|
|
33
|
+
var _rangeExpansion = require("./range-expansion");
|
|
33
34
|
var _keyv = require("keyv");
|
|
34
35
|
var _keyvFile = require("keyv-file");
|
|
35
36
|
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
|
|
@@ -683,14 +684,26 @@ function mapAddressDetails(d, context, index, count) {
|
|
|
683
684
|
rval.smla = mapToShortMla(rval.structured);
|
|
684
685
|
rval.ssla = mapToSla(rval.smla);
|
|
685
686
|
}
|
|
687
|
+
// ADR 026: asymmetric population of sla_range_expanded — one expanded
|
|
688
|
+
// alias per in-range number for range-numbered records up to SPAN_CAP.
|
|
689
|
+
// Non-range docs leave the field absent. expandRangeAliases returns []
|
|
690
|
+
// for span>cap or invalid inputs; in that case we keep the field absent
|
|
691
|
+
// rather than storing an empty array so OpenSearch _source stays clean.
|
|
692
|
+
if (rval.structured.number?.last?.number !== undefined) {
|
|
693
|
+
const s = rval.structured;
|
|
694
|
+
const streetType = s.street.type ? ` ${s.street.type.name}` : '';
|
|
695
|
+
const streetSuffix = s.street.suffix ? ` ${s.street.suffix.name}` : '';
|
|
696
|
+
rval.sla_range_expanded = (0, _rangeExpansion.expandRangeAliases)(Number(s.number.number), Number(s.number.last.number), `${s.street.name}${streetType}${streetSuffix}`, `${s.locality.name} ${s.state.abbreviation} ${s.postcode}`);
|
|
697
|
+
if (rval.sla_range_expanded.length === 0) {
|
|
698
|
+
delete rval.sla_range_expanded;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
686
701
|
if (count) {
|
|
687
702
|
if (index % Math.ceil(count / 100) === 0) {
|
|
688
|
-
logger('addr', JSON.stringify(rval, undefined, 2));
|
|
689
703
|
logger(`${index / count * 100}%`);
|
|
690
704
|
}
|
|
691
705
|
} else {
|
|
692
706
|
if (index % 10_000 === 0) {
|
|
693
|
-
logger('addr', JSON.stringify(rval, undefined, 2));
|
|
694
707
|
logger(`${index} rows`);
|
|
695
708
|
}
|
|
696
709
|
}
|
|
@@ -849,7 +862,13 @@ async function searchForAddress(searchString, p, pageSize = PAGE_SIZE) {
|
|
|
849
862
|
multi_match: {
|
|
850
863
|
fields: ['sla', 'ssla'],
|
|
851
864
|
query: searchString,
|
|
852
|
-
|
|
865
|
+
// ADR 027: AUTO:5,8 (not default AUTO / AUTO:3,6) so that
|
|
866
|
+
// 3-4 digit street numbers and postcodes require exact
|
|
867
|
+
// match. Default AUTO lets `138` fuzzy-match `137`, `135`
|
|
868
|
+
// etc., which tf-inflates adjacent-number docs above the
|
|
869
|
+
// actual target (P026). 5+ char tokens still get 1 edit
|
|
870
|
+
// (Muray → Murray), 8+ char tokens get 2 edits.
|
|
871
|
+
fuzziness: 'AUTO:5,8',
|
|
853
872
|
type: 'bool_prefix',
|
|
854
873
|
lenient: true,
|
|
855
874
|
auto_generate_synonyms_phrase_query: false,
|
|
@@ -857,7 +876,14 @@ async function searchForAddress(searchString, p, pageSize = PAGE_SIZE) {
|
|
|
857
876
|
}
|
|
858
877
|
}, {
|
|
859
878
|
multi_match: {
|
|
860
|
-
|
|
879
|
+
// ADR 026: sla_range_expanded added HERE ONLY (not in the
|
|
880
|
+
// bool_prefix clause above). phrase_prefix uses best_fields
|
|
881
|
+
// max with tie_breaker default 0.0, so an absent field on
|
|
882
|
+
// non-range docs contributes 0 to the max — no P007-shape
|
|
883
|
+
// asymmetry. Adding sla_range_expanded to the bool_prefix
|
|
884
|
+
// fields would reintroduce the summation asymmetry ADR 025
|
|
885
|
+
// resolved. DO NOT move this field into the clause above.
|
|
886
|
+
fields: ['sla', 'ssla', 'sla_range_expanded'],
|
|
861
887
|
query: searchString,
|
|
862
888
|
// fuzziness: 'AUTO',
|
|
863
889
|
type: 'phrase_prefix',
|
|
@@ -1594,20 +1620,27 @@ async function getAddress(addressId) {
|
|
|
1594
1620
|
};
|
|
1595
1621
|
} catch (error_) {
|
|
1596
1622
|
error('error getting record from elastic search', error_);
|
|
1597
|
-
if (error_.body.found === false) {
|
|
1623
|
+
if (error_.body && error_.body.found === false) {
|
|
1598
1624
|
return {
|
|
1599
1625
|
statusCode: 404,
|
|
1600
1626
|
json: {
|
|
1601
1627
|
error: 'not found'
|
|
1602
1628
|
}
|
|
1603
1629
|
};
|
|
1604
|
-
} else if (error_.body.error.type === 'index_not_found_exception') {
|
|
1630
|
+
} else if (error_.body && error_.body.error && error_.body.error.type === 'index_not_found_exception') {
|
|
1605
1631
|
return {
|
|
1606
1632
|
statusCode: 503,
|
|
1607
1633
|
json: {
|
|
1608
1634
|
error: 'service unavailable'
|
|
1609
1635
|
}
|
|
1610
1636
|
};
|
|
1637
|
+
} else if (error_.displayName === 'RequestTimeout') {
|
|
1638
|
+
return {
|
|
1639
|
+
statusCode: 504,
|
|
1640
|
+
json: {
|
|
1641
|
+
error: 'gateway timeout'
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1611
1644
|
} else {
|
|
1612
1645
|
return {
|
|
1613
1646
|
statusCode: 500,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.expandRangeAliases = expandRangeAliases;
|
|
7
|
+
// ADR 028 (supersedes ADR 026): endpoint-only range-address expansion.
|
|
8
|
+
// Pure helper — no I/O, no OpenSearch dependency. Invoked by `mapToMla` in
|
|
9
|
+
// `service/address-service.js` when a G-NAF address has NUMBER_LAST set.
|
|
10
|
+
//
|
|
11
|
+
// For a G-NAF range like `103-107 GAZE RD` (NUMBER_FIRST=103, NUMBER_LAST=107),
|
|
12
|
+
// emit exactly two aliases — the first and last endpoints — and nothing else.
|
|
13
|
+
// Mid-range numbers (104, 105, 106) do NOT match the range document:
|
|
14
|
+
//
|
|
15
|
+
// - Even-indexed mid-range numbers (104, 106) typically belong to properties
|
|
16
|
+
// on the opposite side of the street under Australian addressing convention.
|
|
17
|
+
// - Odd-indexed mid-range numbers (105) could be separate properties the
|
|
18
|
+
// range record absorbed, or a single contiguous frontage — G-NAF does not
|
|
19
|
+
// tell us which. Returning a range doc for a mid-range query would be a
|
|
20
|
+
// false positive either way.
|
|
21
|
+
//
|
|
22
|
+
// See ADR 028 and P015 / #367 reporter comments 2022-06-24 (`138-144`) and
|
|
23
|
+
// 2022-07-10 (`225-245`). The earlier full-interpolation approach from ADR 026
|
|
24
|
+
// shipped in v2.3.0 produced these false positives and is superseded.
|
|
25
|
+
|
|
26
|
+
function expandRangeAliases(first, last, streetPart, localityPart) {
|
|
27
|
+
if (!Number.isInteger(first) || !Number.isInteger(last)) return [];
|
|
28
|
+
if (first <= 0 || last <= 0) return [];
|
|
29
|
+
if (first >= last) return [];
|
|
30
|
+
return [`${first} ${streetPart}, ${localityPart}`, `${last} ${streetPart}, ${localityPart}`];
|
|
31
|
+
}
|
|
@@ -999,6 +999,12 @@ function startRest2Server() {
|
|
|
999
999
|
}],
|
|
1000
1000
|
headers: {
|
|
1001
1001
|
etag: `"${_version.version}"`,
|
|
1002
|
+
// Long-lived by design (P018 parked). New rels are added
|
|
1003
|
+
// infrequently and every client page load fetches this for
|
|
1004
|
+
// HATEOAS discovery; a short TTL would cost an origin
|
|
1005
|
+
// round-trip per request. When the rel set does change,
|
|
1006
|
+
// request a RapidAPI CF purge (natural expiry up to 7 days
|
|
1007
|
+
// per P017 close notes).
|
|
1002
1008
|
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
1003
1009
|
}
|
|
1004
1010
|
};
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mountainpass/addressr",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Australian Address Validation, Search and Autocomplete",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mountain Pass",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"cover:cli:geo": "nyc --report-dir coverage/cli --temp-dir coverage/cli/.nyc_output npm run test:cli:nogeo",
|
|
109
109
|
"test:mcp:smoke": "node --test test/mcp/smoke.test.mjs",
|
|
110
110
|
"test:precommit": "node --test test/precommit/*.test.mjs",
|
|
111
|
+
"test:js": "node --test test/js/__tests__/*.test.mjs",
|
|
111
112
|
"test:nodejs:QLD:nogeo": "PORT=$npm_package_config_localport ES_INDEX_NAME=test COVERED_STATES=QLD DEBUG=error,api,express:*,swagger-tools*,test,es TEST_PROFILE=default cucumber-js -p default -- --harmony_async_iteration",
|
|
112
113
|
"test:nodejs:QLD:geo": "PORT=$npm_package_config_localport ADDRESSR_ENABLE_GEO=1 ES_INDEX_NAME=test-geo COVERED_STATES=QLD DEBUG=error,api,express:*,swagger-tools*,test,es TEST_PROFILE=default NODE_OPTIONS=--max_old_space_size=8196 cucumber-js -p default -- --harmony_async_iteration",
|
|
113
114
|
"prebuildX": "npm run genversion && cat ./templates/LICENSE.md | envsubst '${PRODUCT},${VERSION},${COMPANY},${YEAR}' > ./LICENSE.md",
|
|
@@ -123,7 +124,7 @@
|
|
|
123
124
|
"docker:push": "docker push \"mountainpass/addressr:${npm_package_version}\"",
|
|
124
125
|
"postdocker:push": "docker push \"mountainpass/addressr:latest\"",
|
|
125
126
|
"check-licenses": "license-checker --production --onlyAllow 'MIT;Apache-2.0;ISC;Custom: http://github.com/substack/node-bufferlist;Unlicense;BSD-2-Clause;BSD-3-Clause;WTFPL;0BSD;MIT*;Python-2.0;MPL-2.0;BlueOak-1.0.0' --summary",
|
|
126
|
-
"pre-commit": "lint-staged && npm run check-licenses && npm run check:not-cli2-tags",
|
|
127
|
+
"pre-commit": "lint-staged && npm run check-licenses && npm run check:not-cli2-tags && npm run test:js",
|
|
127
128
|
"check:not-cli2-tags": "node scripts/check-not-cli2-tags.mjs",
|
|
128
129
|
"check-deps": "dry-aged-deps --check",
|
|
129
130
|
"test:performance": "k6 run --out csv=target/stress.csv test/k6/script.js",
|