@mountainpass/addressr 2.2.0 → 2.3.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
|
}
|
|
@@ -857,7 +870,14 @@ async function searchForAddress(searchString, p, pageSize = PAGE_SIZE) {
|
|
|
857
870
|
}
|
|
858
871
|
}, {
|
|
859
872
|
multi_match: {
|
|
860
|
-
|
|
873
|
+
// ADR 026: sla_range_expanded added HERE ONLY (not in the
|
|
874
|
+
// bool_prefix clause above). phrase_prefix uses best_fields
|
|
875
|
+
// max with tie_breaker default 0.0, so an absent field on
|
|
876
|
+
// non-range docs contributes 0 to the max — no P007-shape
|
|
877
|
+
// asymmetry. Adding sla_range_expanded to the bool_prefix
|
|
878
|
+
// fields would reintroduce the summation asymmetry ADR 025
|
|
879
|
+
// resolved. DO NOT move this field into the clause above.
|
|
880
|
+
fields: ['sla', 'ssla', 'sla_range_expanded'],
|
|
861
881
|
query: searchString,
|
|
862
882
|
// fuzziness: 'AUTO',
|
|
863
883
|
type: 'phrase_prefix',
|
|
@@ -1594,20 +1614,27 @@ async function getAddress(addressId) {
|
|
|
1594
1614
|
};
|
|
1595
1615
|
} catch (error_) {
|
|
1596
1616
|
error('error getting record from elastic search', error_);
|
|
1597
|
-
if (error_.body.found === false) {
|
|
1617
|
+
if (error_.body && error_.body.found === false) {
|
|
1598
1618
|
return {
|
|
1599
1619
|
statusCode: 404,
|
|
1600
1620
|
json: {
|
|
1601
1621
|
error: 'not found'
|
|
1602
1622
|
}
|
|
1603
1623
|
};
|
|
1604
|
-
} else if (error_.body.error.type === 'index_not_found_exception') {
|
|
1624
|
+
} else if (error_.body && error_.body.error && error_.body.error.type === 'index_not_found_exception') {
|
|
1605
1625
|
return {
|
|
1606
1626
|
statusCode: 503,
|
|
1607
1627
|
json: {
|
|
1608
1628
|
error: 'service unavailable'
|
|
1609
1629
|
}
|
|
1610
1630
|
};
|
|
1631
|
+
} else if (error_.displayName === 'RequestTimeout') {
|
|
1632
|
+
return {
|
|
1633
|
+
statusCode: 504,
|
|
1634
|
+
json: {
|
|
1635
|
+
error: 'gateway timeout'
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1611
1638
|
} else {
|
|
1612
1639
|
return {
|
|
1613
1640
|
statusCode: 500,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SPAN_CAP = void 0;
|
|
7
|
+
exports.expandRangeAliases = expandRangeAliases;
|
|
8
|
+
// ADR 026: range-number address expansion via multi-valued text alias field.
|
|
9
|
+
// Pure helper — no I/O, no OpenSearch dependency. Invoked by `mapToMla` in
|
|
10
|
+
// `service/address-service.js` when a G-NAF address has NUMBER_LAST set.
|
|
11
|
+
// See docs/problems/015-range-number-addresses-not-searchable-by-base-number.open.md
|
|
12
|
+
// for the range-distribution measurements that justify SPAN_CAP = 20.
|
|
13
|
+
|
|
14
|
+
const SPAN_CAP = exports.SPAN_CAP = 20;
|
|
15
|
+
function expandRangeAliases(first, last, streetPart, localityPart) {
|
|
16
|
+
if (!Number.isInteger(first) || !Number.isInteger(last)) return [];
|
|
17
|
+
if (first <= 0 || last <= 0) return [];
|
|
18
|
+
if (first >= last) return [];
|
|
19
|
+
if (last - first > SPAN_CAP) return [];
|
|
20
|
+
const aliases = [];
|
|
21
|
+
for (let n = first; n <= last; n++) {
|
|
22
|
+
aliases.push(`${n} ${streetPart}, ${localityPart}`);
|
|
23
|
+
}
|
|
24
|
+
return aliases;
|
|
25
|
+
}
|
|
@@ -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.3.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",
|