@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
- fields: ['sla', 'ssla'],
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
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = void 0;
7
7
  // Generated by genversion.
8
- const version = exports.version = '2.2.0';
8
+ const version = exports.version = '2.3.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mountainpass/addressr",
3
- "version": "2.2.0",
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",