@unmap/geocoding 0.12.0 → 0.13.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.
- package/dist/index.d.ts +130 -1
- package/dist/index.js +91 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -573,6 +573,135 @@ declare function parseRuralRoute(q: string): RuralRouteQuery | null;
|
|
|
573
573
|
*/
|
|
574
574
|
declare function looksLikeRuralRoute(q: string): boolean;
|
|
575
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Typed client for the Places API (gateway `/places/*`).
|
|
578
|
+
*
|
|
579
|
+
* It lives in `@unmap/geocoding` rather than in a package of its own because places ARE the
|
|
580
|
+
* geocoding corpus: the same rows, the same container, the same taxonomy. A twelfth npm package
|
|
581
|
+
* would also mean a twelfth entry in the changesets fixed group and a twelfth trusted publisher
|
|
582
|
+
* to configure, which is real founder work for no boundary that exists in the code.
|
|
583
|
+
*
|
|
584
|
+
* `Geocoder.nearby()` is untouched and still answers in the geocoder's own shape. This is the
|
|
585
|
+
* Place shape over the same rows.
|
|
586
|
+
*/
|
|
587
|
+
/** A Canadian point of interest, as `/places/*` returns it. */
|
|
588
|
+
interface Place {
|
|
589
|
+
/** `um:place:<id>`. Opaque: a corpus rebuild may renumber the tail, so do not parse it. */
|
|
590
|
+
id: string;
|
|
591
|
+
/** The corpus id, for `POST /places/{id}/feedback`. */
|
|
592
|
+
source_id: string;
|
|
593
|
+
name: string;
|
|
594
|
+
/** Every language and alternate spelling the corpus holds, including syllabics where recorded. */
|
|
595
|
+
names: Record<string, string>;
|
|
596
|
+
/** Taxonomy id, e.g. `finance.bank`. */
|
|
597
|
+
category: string | null;
|
|
598
|
+
/** The part before the dot, e.g. `finance`. */
|
|
599
|
+
category_group: string | null;
|
|
600
|
+
/** `[lng, lat]`, WGS84. */
|
|
601
|
+
coordinates: [number, number];
|
|
602
|
+
address: {
|
|
603
|
+
housenumber?: string;
|
|
604
|
+
street?: string;
|
|
605
|
+
city?: string;
|
|
606
|
+
region?: string;
|
|
607
|
+
postcode?: string;
|
|
608
|
+
country?: string;
|
|
609
|
+
};
|
|
610
|
+
/** Metres from the anchor. Only on `nearby()`. */
|
|
611
|
+
distance?: number;
|
|
612
|
+
/** Registry id of the dataset, for provenance. */
|
|
613
|
+
source: string;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* The envelope `/places/*` answers in.
|
|
617
|
+
*
|
|
618
|
+
* `meta` is deliberately surfaced rather than unwrapped away: a caller that cannot see
|
|
619
|
+
* `attribution` cannot comply with the licence the data came under, and one that cannot see
|
|
620
|
+
* `status` cannot tell fresh data from stale.
|
|
621
|
+
*/
|
|
622
|
+
interface PlacesResponse<T> {
|
|
623
|
+
data: T;
|
|
624
|
+
meta: {
|
|
625
|
+
status: "current" | "stale" | "partial" | "unknown";
|
|
626
|
+
attribution: string;
|
|
627
|
+
unmap_updated_at: string;
|
|
628
|
+
source?: string;
|
|
629
|
+
source_updated_at?: string | null;
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
interface PlacesOptions {
|
|
633
|
+
key: string;
|
|
634
|
+
gateway?: string;
|
|
635
|
+
fetch?: typeof fetch;
|
|
636
|
+
}
|
|
637
|
+
interface PlaceSearchOptions {
|
|
638
|
+
limit?: number;
|
|
639
|
+
lang?: string;
|
|
640
|
+
bbox?: [number, number, number, number];
|
|
641
|
+
signal?: AbortSignal;
|
|
642
|
+
}
|
|
643
|
+
interface PlaceNearbyOptions {
|
|
644
|
+
/** `[lng, lat]`. Exactly one of `near` or `bbox`. */
|
|
645
|
+
near?: [number, number];
|
|
646
|
+
bbox?: [number, number, number, number];
|
|
647
|
+
/** Metres, clamped server-side to 50..50000. */
|
|
648
|
+
radius?: number;
|
|
649
|
+
limit?: number;
|
|
650
|
+
lang?: string;
|
|
651
|
+
signal?: AbortSignal;
|
|
652
|
+
}
|
|
653
|
+
declare class PlacesError extends Error {
|
|
654
|
+
readonly status: number;
|
|
655
|
+
readonly code?: string | undefined;
|
|
656
|
+
constructor(status: number, message: string, code?: string | undefined);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Canadian places.
|
|
660
|
+
*
|
|
661
|
+
* const places = new Places({ key: "um_live_..." });
|
|
662
|
+
* const { data } = await places.nearby("bakery", { near: [-114.07, 51.05] });
|
|
663
|
+
*
|
|
664
|
+
* ## Fields this does not carry
|
|
665
|
+
*
|
|
666
|
+
* No `phone`, `website`, `opening_hours`, `accessibility` or `operator`. The corpus does not hold
|
|
667
|
+
* them, so they are ABSENT from the response rather than null: an absent key is a statement about
|
|
668
|
+
* unmap's coverage and is true, while `"phone": null` would be a statement about the place and
|
|
669
|
+
* usually a false one. The types above reflect that rather than declaring optional fields nothing
|
|
670
|
+
* ever populates.
|
|
671
|
+
*/
|
|
672
|
+
declare class Places {
|
|
673
|
+
private readonly key;
|
|
674
|
+
private readonly base;
|
|
675
|
+
private readonly fetchImpl;
|
|
676
|
+
constructor(opts: PlacesOptions);
|
|
677
|
+
/**
|
|
678
|
+
* Search places by name.
|
|
679
|
+
*
|
|
680
|
+
* Unlike `Geocoder.search()`, which returns whatever best matches and for "Calgary" is the city
|
|
681
|
+
* itself, this returns places.
|
|
682
|
+
*/
|
|
683
|
+
search(q: string, opts?: PlaceSearchOptions): Promise<PlacesResponse<Place[]>>;
|
|
684
|
+
/**
|
|
685
|
+
* Places of a category near a point.
|
|
686
|
+
*
|
|
687
|
+
* `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French
|
|
688
|
+
* (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `"boulangerie"` works.
|
|
689
|
+
*/
|
|
690
|
+
nearby(category: string | readonly string[], opts?: PlaceNearbyOptions): Promise<PlacesResponse<Place[]>>;
|
|
691
|
+
/**
|
|
692
|
+
* One place by its unmap id.
|
|
693
|
+
*
|
|
694
|
+
* Throws `PlacesError` with status 404 when it does not exist. A malformed id is also a 404,
|
|
695
|
+
* because from a caller's side "does not exist" and "is not a well-formed id" are the same
|
|
696
|
+
* answer; the gateway's message distinguishes them as far as it honestly can.
|
|
697
|
+
*/
|
|
698
|
+
get(id: string, opts?: {
|
|
699
|
+
lang?: string;
|
|
700
|
+
signal?: AbortSignal;
|
|
701
|
+
}): Promise<PlacesResponse<Place>>;
|
|
702
|
+
private request;
|
|
703
|
+
}
|
|
704
|
+
|
|
576
705
|
interface GeocoderOptions {
|
|
577
706
|
key: string;
|
|
578
707
|
gateway?: string;
|
|
@@ -746,4 +875,4 @@ declare class Geocoder {
|
|
|
746
875
|
private query;
|
|
747
876
|
}
|
|
748
877
|
|
|
749
|
-
export { AMBIGUOUS_SCORE_RATIO, CIVIC_SEARCH_LAYERS, type CivicSearchLayer, CsvParser, type CsvProblem, type CsvRecord, DEFAULT_IDENTIFY_LAYERS, DEFAULT_QUERY_LIMIT, ENERGY_IDENTIFY_LAYER_IDS, ENERGY_SEARCH_LAYERS, type EnergyDetails, type EnergyQuery, type EnergyQueryKind, type EnergySearchLayer, type FocusParse, type GeocodeLayer, type GeocodeMatchReason, type GeocodeMatchType, type GeocodePrecision, type GeocodeResult, type GeocodeSource, Geocoder, GeocoderError, type GeocoderOptions, IDENTIFY_LAYER_IDS, type IdentifyLayerId, type IdentifyOptions, type IdentifyResponse, type IdentifyResult, LAND_IDENTIFY_LAYER_IDS, type LegalLandComponents, type LegalLandDetails, type LegalLandLayerSystem, type LegalLandParse, type LegalLandSystem, type LegalLandUnit, MAX_QUERY_LIMIT, MINING_IDENTIFY_LAYER_IDS, type NearbyOptions, OUTPUT_COLUMNS, OUTPUT_PREFIX, type OutputColumn, type ParcelQuery, QUERY_LAYER_IDS, type QueryBbox, type QueryLayerId, type QueryOptions, type QueryResponse, REGIONS, REGION_CODES, REGION_KEYS, type ReviewReason, type RowStatus, type RuralRouteKind, type RuralRouteQuery, STRUCTURED_FIELDS, type SearchLayersParse, type SearchOptions, type StructuredOptions, type StructuredParseResult, type StructuredQuery, alignRow, classify, encodeField, encodeRow, energySearchRequested, escapeForSpreadsheet, isEnergyIdentifyLayer, isEnergySearchSource, isIdentifyLayer, isLandIdentifyLayer, knownIdentifyLayers, knownQueryLayers, knownSearchLayers, looksLikeEnergyQuery, looksLikeFormula, looksLikeLegalLand, looksLikeParcel, looksLikeRuralRoute, outputValues, parseEnergyQuery, parseEnergySearchLayers, parseFocus, parseIdentifyAt, parseIdentifyLayers, parseLegalLand, parseParcel, parseQueryBbox, parseQueryLayer, parseQueryLimit, parseRegion, parseRuralRoute, parseSearchLayers, parseStructuredQuery, regionForms, resolveOutputHeader, toLegalLandDetails };
|
|
878
|
+
export { AMBIGUOUS_SCORE_RATIO, CIVIC_SEARCH_LAYERS, type CivicSearchLayer, CsvParser, type CsvProblem, type CsvRecord, DEFAULT_IDENTIFY_LAYERS, DEFAULT_QUERY_LIMIT, ENERGY_IDENTIFY_LAYER_IDS, ENERGY_SEARCH_LAYERS, type EnergyDetails, type EnergyQuery, type EnergyQueryKind, type EnergySearchLayer, type FocusParse, type GeocodeLayer, type GeocodeMatchReason, type GeocodeMatchType, type GeocodePrecision, type GeocodeResult, type GeocodeSource, Geocoder, GeocoderError, type GeocoderOptions, IDENTIFY_LAYER_IDS, type IdentifyLayerId, type IdentifyOptions, type IdentifyResponse, type IdentifyResult, LAND_IDENTIFY_LAYER_IDS, type LegalLandComponents, type LegalLandDetails, type LegalLandLayerSystem, type LegalLandParse, type LegalLandSystem, type LegalLandUnit, MAX_QUERY_LIMIT, MINING_IDENTIFY_LAYER_IDS, type NearbyOptions, OUTPUT_COLUMNS, OUTPUT_PREFIX, type OutputColumn, type ParcelQuery, type Place, type PlaceNearbyOptions, type PlaceSearchOptions, Places, PlacesError, type PlacesOptions, type PlacesResponse, QUERY_LAYER_IDS, type QueryBbox, type QueryLayerId, type QueryOptions, type QueryResponse, REGIONS, REGION_CODES, REGION_KEYS, type ReviewReason, type RowStatus, type RuralRouteKind, type RuralRouteQuery, STRUCTURED_FIELDS, type SearchLayersParse, type SearchOptions, type StructuredOptions, type StructuredParseResult, type StructuredQuery, alignRow, classify, encodeField, encodeRow, energySearchRequested, escapeForSpreadsheet, isEnergyIdentifyLayer, isEnergySearchSource, isIdentifyLayer, isLandIdentifyLayer, knownIdentifyLayers, knownQueryLayers, knownSearchLayers, looksLikeEnergyQuery, looksLikeFormula, looksLikeLegalLand, looksLikeParcel, looksLikeRuralRoute, outputValues, parseEnergyQuery, parseEnergySearchLayers, parseFocus, parseIdentifyAt, parseIdentifyLayers, parseLegalLand, parseParcel, parseQueryBbox, parseQueryLayer, parseQueryLimit, parseRegion, parseRuralRoute, parseSearchLayers, parseStructuredQuery, regionForms, resolveOutputHeader, toLegalLandDetails };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { DEFAULT_GATEWAY } from "@unmap/core";
|
|
2
|
+
import { DEFAULT_GATEWAY as DEFAULT_GATEWAY2 } from "@unmap/core";
|
|
3
3
|
|
|
4
4
|
// src/legal-land.ts
|
|
5
5
|
var SEP = String.raw`(?:\s*-\s*|\s+)`;
|
|
@@ -860,6 +860,91 @@ function looksLikeRuralRoute(q) {
|
|
|
860
860
|
return parseRuralRoute(q) != null;
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
// src/places.ts
|
|
864
|
+
import { DEFAULT_GATEWAY } from "@unmap/core";
|
|
865
|
+
var PlacesError = class extends Error {
|
|
866
|
+
constructor(status, message, code) {
|
|
867
|
+
super(message);
|
|
868
|
+
this.status = status;
|
|
869
|
+
this.code = code;
|
|
870
|
+
this.name = "PlacesError";
|
|
871
|
+
}
|
|
872
|
+
status;
|
|
873
|
+
code;
|
|
874
|
+
};
|
|
875
|
+
async function errorFrom(res, path) {
|
|
876
|
+
const fallback = `places ${res.status} on ${path}`;
|
|
877
|
+
let body;
|
|
878
|
+
try {
|
|
879
|
+
body = await res.json();
|
|
880
|
+
} catch {
|
|
881
|
+
body = void 0;
|
|
882
|
+
}
|
|
883
|
+
const reason = typeof body?.error === "string" ? body.error : void 0;
|
|
884
|
+
const code = typeof body?.code === "string" ? body.code : void 0;
|
|
885
|
+
return new PlacesError(res.status, reason ? `${reason} (${fallback})` : fallback, code);
|
|
886
|
+
}
|
|
887
|
+
var Places = class {
|
|
888
|
+
key;
|
|
889
|
+
base;
|
|
890
|
+
fetchImpl;
|
|
891
|
+
constructor(opts) {
|
|
892
|
+
this.key = opts.key;
|
|
893
|
+
this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
|
|
894
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Search places by name.
|
|
898
|
+
*
|
|
899
|
+
* Unlike `Geocoder.search()`, which returns whatever best matches and for "Calgary" is the city
|
|
900
|
+
* itself, this returns places.
|
|
901
|
+
*/
|
|
902
|
+
search(q, opts = {}) {
|
|
903
|
+
return this.request("/places/search", {
|
|
904
|
+
q,
|
|
905
|
+
limit: opts.limit != null ? String(opts.limit) : void 0,
|
|
906
|
+
lang: opts.lang,
|
|
907
|
+
bbox: opts.bbox?.join(",")
|
|
908
|
+
}, opts.signal);
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Places of a category near a point.
|
|
912
|
+
*
|
|
913
|
+
* `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French
|
|
914
|
+
* (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `"boulangerie"` works.
|
|
915
|
+
*/
|
|
916
|
+
nearby(category, opts = {}) {
|
|
917
|
+
return this.request("/places/nearby", {
|
|
918
|
+
category: Array.isArray(category) ? category.join(",") : String(category),
|
|
919
|
+
near: opts.near?.join(","),
|
|
920
|
+
bbox: opts.bbox?.join(","),
|
|
921
|
+
radius: opts.radius != null ? String(opts.radius) : void 0,
|
|
922
|
+
limit: opts.limit != null ? String(opts.limit) : void 0,
|
|
923
|
+
lang: opts.lang
|
|
924
|
+
}, opts.signal);
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* One place by its unmap id.
|
|
928
|
+
*
|
|
929
|
+
* Throws `PlacesError` with status 404 when it does not exist. A malformed id is also a 404,
|
|
930
|
+
* because from a caller's side "does not exist" and "is not a well-formed id" are the same
|
|
931
|
+
* answer; the gateway's message distinguishes them as far as it honestly can.
|
|
932
|
+
*/
|
|
933
|
+
get(id, opts = {}) {
|
|
934
|
+
return this.request(`/places/${encodeURIComponent(id)}`, { lang: opts.lang }, opts.signal);
|
|
935
|
+
}
|
|
936
|
+
async request(path, params, signal) {
|
|
937
|
+
const q = new URLSearchParams({ key: this.key });
|
|
938
|
+
for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
|
|
939
|
+
const res = await this.fetchImpl(`${this.base}${path}?${q}`, {
|
|
940
|
+
signal,
|
|
941
|
+
headers: { accept: "application/json" }
|
|
942
|
+
});
|
|
943
|
+
if (!res.ok) throw await errorFrom(res, path);
|
|
944
|
+
return await res.json();
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
|
|
863
948
|
// src/index.ts
|
|
864
949
|
var GeocoderError = class extends Error {
|
|
865
950
|
constructor(status, message, code) {
|
|
@@ -871,7 +956,7 @@ var GeocoderError = class extends Error {
|
|
|
871
956
|
status;
|
|
872
957
|
code;
|
|
873
958
|
};
|
|
874
|
-
async function
|
|
959
|
+
async function errorFrom2(res, path) {
|
|
875
960
|
const fallback = `geocode ${res.status} on ${path}`;
|
|
876
961
|
let body;
|
|
877
962
|
try {
|
|
@@ -889,7 +974,7 @@ var Geocoder = class {
|
|
|
889
974
|
fetchImpl;
|
|
890
975
|
constructor(opts) {
|
|
891
976
|
this.key = opts.key;
|
|
892
|
-
this.base = (opts.gateway ??
|
|
977
|
+
this.base = (opts.gateway ?? DEFAULT_GATEWAY2).replace(/\/+$/, "");
|
|
893
978
|
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
894
979
|
}
|
|
895
980
|
/** Full-text search. */
|
|
@@ -997,7 +1082,7 @@ var Geocoder = class {
|
|
|
997
1082
|
signal,
|
|
998
1083
|
headers: { accept: "application/json" }
|
|
999
1084
|
});
|
|
1000
|
-
if (!res.ok) throw await
|
|
1085
|
+
if (!res.ok) throw await errorFrom2(res, path);
|
|
1001
1086
|
return res.json();
|
|
1002
1087
|
}
|
|
1003
1088
|
async query(path, params, signal) {
|
|
@@ -1032,6 +1117,8 @@ export {
|
|
|
1032
1117
|
MINING_IDENTIFY_LAYER_IDS,
|
|
1033
1118
|
OUTPUT_COLUMNS,
|
|
1034
1119
|
OUTPUT_PREFIX,
|
|
1120
|
+
Places,
|
|
1121
|
+
PlacesError,
|
|
1035
1122
|
QUERY_LAYER_IDS,
|
|
1036
1123
|
REGIONS,
|
|
1037
1124
|
REGION_CODES,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/legal-land.ts","../src/energy.ts","../src/identify.ts","../src/batch/csv.ts","../src/batch/output.ts","../src/search-layers.ts","../src/regions.ts","../src/structured.ts","../src/parcel.ts","../src/query.ts","../src/rural-route.ts"],"sourcesContent":["import { DEFAULT_GATEWAY } from \"@unmap/core\";\nimport type { LegalLandDetails } from \"./legal-land\";\nimport type { IdentifyOptions, IdentifyResponse } from \"./identify\";\nimport type { QueryOptions, QueryResponse } from \"./query\";\nimport type { StructuredOptions } from \"./structured\";\n\nexport {\n looksLikeLegalLand,\n parseLegalLand,\n toLegalLandDetails,\n type LegalLandComponents,\n type LegalLandDetails,\n type LegalLandLayerSystem,\n type LegalLandParse,\n type LegalLandSystem,\n type LegalLandUnit,\n} from \"./legal-land\";\n\nexport {\n ENERGY_SEARCH_LAYERS,\n energySearchRequested,\n isEnergySearchSource,\n looksLikeEnergyQuery,\n parseEnergyQuery,\n parseEnergySearchLayers,\n type EnergyDetails,\n type EnergyQuery,\n type EnergyQueryKind,\n type EnergySearchLayer,\n} from \"./energy\";\n\nexport {\n DEFAULT_IDENTIFY_LAYERS,\n ENERGY_IDENTIFY_LAYER_IDS,\n IDENTIFY_LAYER_IDS,\n LAND_IDENTIFY_LAYER_IDS,\n MINING_IDENTIFY_LAYER_IDS,\n isEnergyIdentifyLayer,\n isIdentifyLayer,\n isLandIdentifyLayer,\n knownIdentifyLayers,\n parseIdentifyAt,\n parseIdentifyLayers,\n type IdentifyLayerId,\n type IdentifyOptions,\n type IdentifyResponse,\n type IdentifyResult,\n} from \"./identify\";\n\nexport {\n CsvParser,\n alignRow,\n encodeField,\n encodeRow,\n escapeForSpreadsheet,\n looksLikeFormula,\n type CsvProblem,\n type CsvRecord,\n} from \"./batch/csv\";\n\nexport {\n AMBIGUOUS_SCORE_RATIO,\n OUTPUT_COLUMNS,\n OUTPUT_PREFIX,\n classify,\n outputValues,\n resolveOutputHeader,\n type OutputColumn,\n type ReviewReason,\n type RowStatus,\n} from \"./batch/output\";\n\nexport {\n CIVIC_SEARCH_LAYERS,\n knownSearchLayers,\n parseFocus,\n parseSearchLayers,\n type CivicSearchLayer,\n type FocusParse,\n type SearchLayersParse,\n} from \"./search-layers\";\n\nexport {\n REGIONS,\n REGION_CODES,\n REGION_KEYS,\n parseRegion,\n regionForms,\n} from \"./regions\";\n\nexport {\n STRUCTURED_FIELDS,\n parseStructuredQuery,\n type StructuredOptions,\n type StructuredParseResult,\n type StructuredQuery,\n} from \"./structured\";\n\nexport {\n looksLikeParcel,\n parseParcel,\n type ParcelQuery,\n} from \"./parcel\";\n\nexport {\n DEFAULT_QUERY_LIMIT,\n MAX_QUERY_LIMIT,\n QUERY_LAYER_IDS,\n knownQueryLayers,\n parseQueryBbox,\n parseQueryLayer,\n parseQueryLimit,\n type QueryBbox,\n type QueryLayerId,\n type QueryOptions,\n type QueryResponse,\n} from \"./query\";\n\nexport interface GeocoderOptions {\n key: string;\n gateway?: string;\n /** Override the fetch implementation (tests, non-global environments). */\n fetch?: typeof fetch;\n}\n\nexport type GeocodeLayer =\n | \"address\"\n | \"street\"\n | \"locality\"\n | \"region\"\n | \"poi\"\n | \"legal_land\"\n | \"well\"\n | \"facility\"\n | \"parcel\";\n\n/**\n * Which dataset the returned record came from. Conflation\n * (`pipeline/geocoding/conflate.sql`) keeps one survivor per place and deletes the rest, so\n * this is the SURVIVING record's source, not every dataset that agreed on the place. See\n * `docs/services/geocoding-sources.md`.\n */\nexport type GeocodeSource =\n | \"osm\"\n | \"openaddresses\"\n | \"statcan_nar\"\n | \"nrcan_cgndb\"\n | \"wof\"\n | \"geonames\";\n\n/**\n * How well the result answers what was asked, as a category — never a probability, and never\n * derived from `score` (which stays raw BM25 relevance).\n *\n * - `exact` — every component the query supplied and the corpus supports was matched.\n * - `partial` — something supplied did not match, or could not be verified.\n * - `fallback` — the result is COARSER than the query asked for: a civic number was requested\n * and the result carries none. A street or locality standing in for an address is always\n * this, never `exact`.\n * - `unknown` — input-match diagnostics do not apply (reverse, nearby, autocomplete).\n */\nexport type GeocodeMatchType = \"exact\" | \"partial\" | \"fallback\" | \"unknown\";\n\n/**\n * Positional resolution the SOURCE supports, separate from how well the input matched.\n *\n * `point` means a discrete point record in the source dataset — an address point or a POI. It\n * is **not** a rooftop-accuracy claim, and it never means interpolated: the engine performs no\n * interpolation. `locality` and `region` are centroids.\n */\nexport type GeocodePrecision = \"point\" | \"street\" | \"locality\" | \"region\" | \"unknown\";\n\n/** Stable reason codes explaining a non-`exact` `match_type`. Additive; never renamed. */\nexport type GeocodeMatchReason =\n | \"housenumber_not_matched\"\n | \"housenumber_differs\"\n | \"unit_not_verified\"\n | \"unit_differs\"\n | \"postalcode_not_verified\"\n | \"postalcode_differs\"\n | \"region_not_verified\"\n | \"region_differs\"\n | \"terms_unmatched\"\n | \"fuzzy_match\";\n\nexport interface GeocodeResult {\n id: string;\n name: string;\n layer: GeocodeLayer;\n lng: number;\n lat: number;\n score?: number;\n /** Originating dataset of the returned record. */\n source?: GeocodeSource | string;\n /** That dataset's own identifier for the record, when it published one. */\n source_id?: string;\n /** How well the result answers the query. `unknown` where input matching does not apply. */\n match_type?: GeocodeMatchType;\n /** Positional resolution the source supports. Not an accuracy figure. */\n precision?: GeocodePrecision;\n /** Why `match_type` is not `exact`. Empty or absent when it is. */\n match_reasons?: GeocodeMatchReason[];\n /** Canonical category id (e.g. \"health.pharmacy\") when the feature carries one. */\n category?: string;\n /** Spheroidal metres from the query anchor; only `/geocode/nearby` results carry it. */\n distance?: number;\n names?: Record<string, string>;\n address?: {\n housenumber?: string;\n street?: string;\n city?: string;\n region?: string;\n postcode?: string;\n country?: string;\n /** Unit/suite, where the source published one. Most of the corpus does not. */\n unit?: string;\n };\n /** Structured legal-land parse; only `layer: \"legal_land\"` results carry it. */\n legal_land?: LegalLandDetails;\n /** Well / facility extras; only `layer: \"well\" | \"facility\"` results carry them. */\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n /** ParcelMap BC PID; only `layer: \"parcel\"` results carry it. */\n pid?: string;\n /** Survey-cell bounds `[minLng, minLat, maxLng, maxLat]` when the lookup row has a geom. */\n bbox?: [number, number, number, number];\n /** Survey polygon when `?geometry=true` and the lookup row has one. */\n geometry?: {\n type: \"Polygon\" | \"MultiPolygon\";\n coordinates: number[][][] | number[][][][];\n };\n}\n\nexport interface SearchOptions {\n limit?: number;\n lang?: string;\n /** [minLng, minLat, maxLng, maxLat] hard filter. Excludes; see `focus` to merely prefer. */\n bbox?: [number, number, number, number];\n /** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */\n focus?: [number, number];\n /** Include the survey polygon on a legal-land hit when the table has one. */\n geometry?: boolean;\n /**\n * Which result types may come back. Civic values (`address`, `street`, `locality`, `region`,\n * `poi`) filter within the civic corpus; energy values (`well`, `facility`) select the energy\n * corpus instead and need the energy add-on. The two sets cannot be mixed.\n */\n layers?: string;\n /** Province or territory, any accepted spelling. Excludes rows in another province. */\n region?: string;\n signal?: AbortSignal;\n}\n\nexport interface NearbyOptions {\n /** [lng, lat] anchor to sort by distance from. Exactly one of `near` or `bbox`. */\n near?: [number, number];\n /** [minLng, minLat, maxLng, maxLat]; the API anchors at its centre. */\n bbox?: [number, number, number, number];\n /** Search radius in metres (API default 5000, clamped to [50, 50000]). */\n radius?: number;\n limit?: number;\n lang?: string;\n signal?: AbortSignal;\n}\n\nexport class GeocoderError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n /** The gateway's machine-readable `code`, when the response carried one (e.g. `unauthorized`). */\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"GeocoderError\";\n }\n}\n\n/**\n * The gateway answers every error with a JSON body whose `error` says what went wrong\n * (\"mode must be one of auto, car, bicycle, pedestrian, truck\", \"q required\"). Put that in the\n * message so a caller who logs the thrown error reads the reason, not just the status.\n */\nasync function errorFrom(res: { status: number; json(): Promise<unknown> }, path: string): Promise<GeocoderError> {\n const fallback = `geocode ${res.status} on ${path}`;\n let body: { error?: unknown; code?: unknown } | undefined;\n try {\n body = (await res.json()) as { error?: unknown; code?: unknown };\n } catch {\n body = undefined;\n }\n const reason = typeof body?.error === \"string\" ? body.error : undefined;\n const code = typeof body?.code === \"string\" ? body.code : undefined;\n return new GeocoderError(res.status, reason ? `${reason} (${fallback})` : fallback, code);\n}\n\n/** Typed client for the unmap geocoding API (gateway `/geocode/*`). */\nexport class Geocoder {\n private readonly key: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: GeocoderOptions) {\n this.key = opts.key;\n this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n // Bound to globalThis: stored on an instance and called as `this.fetchImpl(...)`, a bare\n // `fetch` reference is invoked with the Geocoder as its receiver, and browsers reject that\n // with \"Illegal invocation\". Node is lenient, so this only ever surfaces in the browser.\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** Full-text search. */\n search(q: string, opts: SearchOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\"/geocode/search\", { q, ...toParams(opts) }, opts.signal);\n }\n\n /** Type-ahead suggestions for partial input. */\n autocomplete(q: string, opts: SearchOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\"/geocode/autocomplete\", { q, ...toParams(opts) }, opts.signal);\n }\n\n /**\n * Forward geocoding from address components rather than one string.\n *\n * `address` and `city` decide which rows are candidates and which wins. `region` and\n * `postalcode` narrow: they drop rows that contradict them and keep rows that are silent\n * about them, which come back with `region_not_verified` / `postalcode_not_verified` in\n * `match_reasons`. Needs at least one of `address`, `city` or `postalcode`.\n */\n structured(opts: StructuredOptions): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/structured\",\n {\n address: opts.address,\n city: opts.city,\n region: opts.region,\n postalcode: opts.postalcode,\n country: opts.country,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n bbox: opts.bbox ? opts.bbox.join(\",\") : undefined,\n focus: opts.focus ? opts.focus.join(\",\") : undefined,\n layers: opts.layers,\n },\n opts.signal,\n );\n }\n\n /**\n * Category search: POIs of a kind near a point, sorted by distance (metres, in each\n * result's `distance`). `category` takes canonical taxonomy ids (\"health.pharmacy\") or\n * plain words in English or French (\"pharmacy\", \"pharmacie\") — the gateway resolves\n * aliases, so this client passes the value through untouched.\n */\n nearby(category: string | string[], opts: NearbyOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/nearby\",\n {\n category: Array.isArray(category) ? category.join(\",\") : category,\n near: opts.near ? opts.near.join(\",\") : undefined,\n bbox: opts.bbox ? opts.bbox.join(\",\") : undefined,\n radius: opts.radius != null ? String(opts.radius) : undefined,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n },\n opts.signal,\n );\n }\n\n /** Reverse geocode a coordinate. */\n reverse(\n lng: number,\n lat: number,\n opts: { lang?: string; signal?: AbortSignal } = {},\n ): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/reverse\",\n { lon: String(lng), lat: String(lat), lang: opts.lang },\n opts.signal,\n );\n }\n\n /**\n * Which catalog features contain a point. Default layer is `municipalities`\n * (core). `survey.dls` / `survey.nts` need the `legal-land` add-on.\n * `energy.*` need the `energy` add-on. `mining.*` need the `mining` add-on\n * and identify via Land extra tables. Empty until the matching PostGIS table\n * is loaded. No mining search grammar — claim numbers stay `energy.mineral-tenure`.\n */\n identify(opts: IdentifyOptions): Promise<IdentifyResponse> {\n return this.requestJson(\n \"/identify\",\n {\n at: opts.at.join(\",\"),\n layers: opts.layers?.length ? opts.layers.join(\",\") : undefined,\n geometry: opts.geometry ? \"true\" : undefined,\n },\n opts.signal,\n ) as Promise<IdentifyResponse>;\n }\n\n /**\n * Features of one catalog layer that intersect a bbox. Same PostGIS tables as\n * `identify`. Tile-only catalog ids are a 501. Survey layers need `legal-land`;\n * `energy.*` need `energy`; `survey.parcels` needs `agriculture`; mining\n * layers need `mining`. Empty until the matching table is loaded.\n */\n queryLayer(opts: QueryOptions): Promise<QueryResponse> {\n return this.requestJson(\n \"/data/query\",\n {\n layer: opts.layer,\n bbox: opts.bbox.join(\",\"),\n limit: opts.limit != null ? String(opts.limit) : undefined,\n geometry: opts.geometry ? \"true\" : undefined,\n },\n opts.signal,\n ) as Promise<QueryResponse>;\n }\n\n private async requestJson(\n path: string,\n params: Record<string, string | undefined>,\n signal?: AbortSignal,\n ): Promise<unknown> {\n const q = new URLSearchParams({ key: this.key });\n for (const [k, v] of Object.entries(params)) if (v != null && v !== \"\") q.set(k, v);\n const res = await this.fetchImpl(`${this.base}${path}?${q}`, {\n signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) throw await errorFrom(res, path);\n return res.json();\n }\n\n private async query(\n path: string,\n params: Record<string, string | undefined>,\n signal?: AbortSignal,\n ): Promise<GeocodeResult[]> {\n const body = (await this.requestJson(path, params, signal)) as\n | { results?: GeocodeResult[] }\n | GeocodeResult[];\n return Array.isArray(body) ? body : (body.results ?? []);\n }\n}\n\nfunction toParams(o: SearchOptions): Record<string, string | undefined> {\n return {\n limit: o.limit != null ? String(o.limit) : undefined,\n lang: o.lang,\n bbox: o.bbox ? o.bbox.join(\",\") : undefined,\n focus: o.focus ? o.focus.join(\",\") : undefined,\n region: o.region,\n geometry: o.geometry ? \"true\" : undefined,\n layers: o.layers,\n };\n}\n\nexport {\n looksLikeRuralRoute,\n parseRuralRoute,\n type RuralRouteKind,\n type RuralRouteQuery,\n} from \"./rural-route\";\n","/**\n * Framework-free port of Township Canada's legal-land regex grammar\n * (`api/packages/shared/regex_rules.js` + `helpers.processInput`).\n *\n * A successful parse is a structured candidate, not a geocoder hit. Coordinates,\n * civic addresses, and place names return null — those stay on the civic path.\n */\n\nexport type LegalLandSystem = \"DLS\" | \"NTS\" | \"GTS\" | \"FPS\" | \"RIVER_LOT\";\n\nexport type LegalLandUnit =\n | \"lsd\"\n | \"qtr\"\n | \"half\"\n | \"sec\"\n | \"twp\"\n | \"nts\"\n | \"nts_sheet\"\n | \"lot\"\n | \"con\"\n | \"grid\"\n | \"section\"\n | \"unit\"\n | \"river_lot\";\n\nexport type LegalLandComponents = {\n meridian?: string;\n range?: string;\n township?: string;\n section?: string;\n quarter?: string;\n half?: string;\n lsd?: string;\n quarterUnit?: string;\n ntsUnit?: string;\n block?: string;\n series?: string;\n mapArea?: string;\n mapSheet?: string;\n fpsUnit?: string;\n fpsSection?: string;\n latDeg?: string;\n latMin?: string;\n lonDeg?: string;\n lonMin?: string;\n lot?: string;\n concession?: string;\n townshipName?: string;\n kind?: string;\n parts?: string[];\n};\n\nexport type LegalLandParse = {\n system: LegalLandSystem;\n canonical: string;\n unit: LegalLandUnit;\n components: LegalLandComponents;\n province?: string;\n};\n\nconst SEP = String.raw`(?:\\s*-\\s*|\\s+)`;\nconst SEP_OPT = String.raw`(?:\\s*-\\s*|\\s+)?`;\n\nconst LSD_N = String.raw`(?<lsd>0?[1-9]|1[0-6])`;\nconst SEC_N = String.raw`(?<section>(?:0?[1-9]|[1-2][0-9]|3[0-6])a?)`;\nconst TWP_N = String.raw`(?<township>(?:(?:00|0)?[1-9]|0?[1-9][0-9]|1[0-2][0-9])a?)`;\nconst RGE_N = String.raw`(?<range>(?:0?[1-9]|[1-8][0-9]|9[0-3])a?)`;\nconst MER_N = String.raw`(?<meridian>[Ww][1-6]|[Ee][1-2])[Mm]?`;\n\nconst DLS_LSD = new RegExp(`^${LSD_N}${SEP}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_SEC = new RegExp(`^${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_TWP = new RegExp(`^${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_QTR_MATCH = new RegExp(`^([ns][ew])(1/4)?(?:\\\\s*-\\\\s*|\\\\s+).*$`, \"i\");\nconst DLS_QTR = new RegExp(\n `^(?<quarter>[ns][ew])(?:[\\\\s-]*1/4)?${SEP_OPT}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`,\n \"i\",\n);\nconst DLS_HALF_MATCH = new RegExp(`^([nsew])(1/2)?(?:\\\\s*-\\\\s*|\\\\s+).*$`, \"i\");\nconst DLS_HALF = new RegExp(\n `^(?<half>[nsew])(?:[\\\\s-]*1/2)?${SEP_OPT}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`,\n \"i\",\n);\n\nconst NTS = new RegExp(\n `^(?<quarterUnit>[abcd])${SEP_OPT}(?<ntsUnit>(?:00|0)?[1-9]|0?[1-9][0-9]|100)${SEP_OPT}(?<block>[a-l])\\\\s*/?\\\\s*(?<series>0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])${SEP_OPT}(?<mapArea>[a-p])${SEP_OPT}(?<mapSheet>0?[1-9]|1[0-6])$`,\n \"i\",\n);\nconst NTS_SHEET = new RegExp(\n `^(?<series>0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])(?<mapArea>[a-p])\\\\s*/?\\\\s*(?<mapSheet>0?[1-9]|1[0-6])$`,\n \"i\",\n);\n\nconst FPS_UNIT = /^([a-p])\\s+(\\d{1,2})\\s+(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\nconst FPS_SECTION = /^(\\d{1,2})\\s+(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\nconst FPS_GRID = /^(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\n\nconst ON_LOT = /^lot\\s+(\\d+)(?:\\s+(.*))?$/i;\nconst ON_CON = /^(?:con|concession)\\s+(\\d+)\\s+([\\w ]+)$/i;\nconst ON_CON_IN_REST = /^(?:con|concession)\\s+(\\d+)\\s*(.*)$/i;\nconst ON_LOT_IN_REST = /^lot\\s+(\\d+)\\s*(.*)$/i;\n\nconst RIVER = /^(pl|rl|wl|lx|sl|ot)(?:[\\s-]+[a-z0-9]+)+$/i;\n\nfunction sanitize(q: string): string {\n return q.trim().replace(/['\"]/g, \"\").replace(/[;\\\\]/g, \"\");\n}\n\n/** Users often glue the quarter/half onto the section: `NW25-24-1-W5`. Leave `N1/2` / `NW1/4` intact. */\nfunction insertDirectionSeparator(q: string): string {\n return q.replace(/^([NS][EW]|[NSEW])(?!1\\/[24]\\b)(\\d)/i, \"$1-$2\");\n}\n\nfunction cleanPart(value: string): string {\n return value.replace(/^0+(?=\\d)/, \"\").replace(/\\/0+/, \"/\").toUpperCase();\n}\n\nfunction dlsProvince(meridian: string): string | undefined {\n const m = meridian.toUpperCase();\n if (m === \"W5\") return \"AB\";\n if (m === \"W1\" || m === \"E1\" || m === \"E2\") return \"MB\";\n return undefined;\n}\n\nfunction titleCase(value: string): string {\n return value.replace(/\\w\\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());\n}\n\nfunction groups(re: RegExp, q: string): Record<string, string> | null {\n const match = re.exec(q);\n if (!match?.groups) return null;\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(match.groups)) {\n if (value === undefined || value === \"\") continue;\n out[key] = cleanPart(value);\n }\n return out;\n}\n\nfunction dlsFrom(unit: \"lsd\" | \"qtr\" | \"half\" | \"sec\" | \"twp\", g: Record<string, string>): LegalLandParse {\n const meridian = g.meridian!;\n const components: LegalLandComponents = { meridian, range: g.range, township: g.township };\n const pieces: string[] = [];\n if (unit === \"lsd\") {\n components.lsd = g.lsd;\n components.section = g.section;\n pieces.push(g.lsd!, g.section!);\n } else if (unit === \"qtr\") {\n components.quarter = g.quarter;\n components.section = g.section;\n pieces.push(g.quarter!, g.section!);\n } else if (unit === \"half\") {\n components.half = g.half;\n components.section = g.section;\n pieces.push(g.half!, g.section!);\n } else if (unit === \"sec\") {\n components.section = g.section;\n pieces.push(g.section!);\n }\n pieces.push(g.township!, g.range!, meridian);\n const parsed: LegalLandParse = {\n system: \"DLS\",\n canonical: pieces.join(\"-\"),\n unit,\n components,\n };\n const province = dlsProvince(meridian);\n if (province) parsed.province = province;\n return parsed;\n}\n\nfunction parseDls(q: string): LegalLandParse | null {\n const candidate = insertDirectionSeparator(q);\n const lsd = groups(DLS_LSD, candidate);\n if (lsd) return dlsFrom(\"lsd\", lsd);\n if (DLS_QTR_MATCH.test(candidate)) {\n const qtr = groups(DLS_QTR, candidate);\n if (qtr) return dlsFrom(\"qtr\", qtr);\n }\n if (DLS_HALF_MATCH.test(candidate)) {\n const half = groups(DLS_HALF, candidate);\n if (half) return dlsFrom(\"half\", half);\n }\n const sec = groups(DLS_SEC, candidate);\n if (sec) return dlsFrom(\"sec\", sec);\n const twp = groups(DLS_TWP, candidate);\n if (twp) return dlsFrom(\"twp\", twp);\n return null;\n}\n\nfunction parseNts(q: string): LegalLandParse | null {\n const full = groups(NTS, q);\n if (full) {\n return {\n system: \"NTS\",\n canonical: `${full.quarterUnit}-${full.ntsUnit}-${full.block}/${full.series}-${full.mapArea}-${full.mapSheet}`,\n unit: \"nts\",\n components: {\n quarterUnit: full.quarterUnit,\n ntsUnit: full.ntsUnit,\n block: full.block,\n series: full.series,\n mapArea: full.mapArea,\n mapSheet: full.mapSheet,\n },\n };\n }\n const sheet = groups(NTS_SHEET, q);\n if (!sheet) return null;\n return {\n system: \"NTS\",\n canonical: `${sheet.series}${sheet.mapArea}/${sheet.mapSheet}`,\n unit: \"nts_sheet\",\n components: {\n series: sheet.series,\n mapArea: sheet.mapArea,\n mapSheet: sheet.mapSheet,\n },\n };\n}\n\nfunction parseFps(q: string): LegalLandParse | null {\n const unit = FPS_UNIT.exec(q);\n if (unit) {\n const components = {\n fpsUnit: unit[1]!.toUpperCase(),\n fpsSection: unit[2]!,\n latDeg: unit[3]!,\n latMin: unit[4]!,\n lonDeg: unit[5]!,\n lonMin: unit[6]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.fpsUnit} ${components.fpsSection} ${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"unit\",\n components,\n };\n }\n const section = FPS_SECTION.exec(q);\n if (section) {\n const components = {\n fpsSection: section[1]!,\n latDeg: section[2]!,\n latMin: section[3]!,\n lonDeg: section[4]!,\n lonMin: section[5]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.fpsSection} ${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"section\",\n components,\n };\n }\n const grid = FPS_GRID.exec(q);\n if (!grid) return null;\n const components = {\n latDeg: grid[1]!,\n latMin: grid[2]!,\n lonDeg: grid[3]!,\n lonMin: grid[4]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"grid\",\n components,\n };\n}\n\nfunction parseGts(q: string): LegalLandParse | null {\n const lot = ON_LOT.exec(q);\n if (lot) {\n const components: LegalLandComponents = { lot: cleanPart(lot[1]!) };\n const rest = (lot[2] ?? \"\").trim();\n const con = ON_CON_IN_REST.exec(rest);\n const nameBits: string[] = [];\n if (con) {\n components.concession = cleanPart(con[1]!);\n const after = (con[2] ?? \"\").trim();\n if (after) components.townshipName = titleCase(after);\n nameBits.push(`Con ${components.concession}`);\n if (components.townshipName) nameBits.push(components.townshipName);\n } else if (rest) {\n components.townshipName = titleCase(rest);\n nameBits.push(components.townshipName);\n }\n return {\n system: \"GTS\",\n canonical: [`Lot ${components.lot}`, ...nameBits].join(\" \"),\n unit: \"lot\",\n components,\n province: \"ON\",\n };\n }\n const con = ON_CON.exec(q);\n if (!con) return null;\n const components: LegalLandComponents = { concession: cleanPart(con[1]!) };\n const rest = con[2]!.trim();\n const nestedLot = ON_LOT_IN_REST.exec(rest);\n if (nestedLot) {\n components.lot = cleanPart(nestedLot[1]!);\n const after = (nestedLot[2] ?? \"\").trim();\n if (after) components.townshipName = titleCase(after);\n } else {\n components.townshipName = titleCase(rest);\n }\n const tail = [\n components.lot ? `Lot ${components.lot}` : \"\",\n components.townshipName ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n return {\n system: \"GTS\",\n canonical: [`Con ${components.concession}`, tail].filter(Boolean).join(\" \"),\n unit: components.lot ? \"lot\" : \"con\",\n components,\n province: \"ON\",\n };\n}\n\nfunction parseRiverLot(q: string): LegalLandParse | null {\n if (!RIVER.test(q)) return null;\n const tokens = q.split(/[\\s-]+/).filter(Boolean).map((t) => t.toUpperCase());\n const kind = tokens[0]!;\n const parts = tokens.slice(1);\n return {\n system: \"RIVER_LOT\",\n canonical: tokens.join(\"-\"),\n unit: \"river_lot\",\n components: { kind, parts },\n province: \"MB\",\n };\n}\n\nexport function parseLegalLand(q: string): LegalLandParse | null {\n if (typeof q !== \"string\") return null;\n const raw = sanitize(q);\n if (!raw) return null;\n\n return parseFps(raw) ?? parseGts(raw) ?? parseRiverLot(raw) ?? parseDls(raw) ?? parseNts(raw);\n}\n\n/** Public `legal_land.system` values on a geocode result (LOCATION-MODEL). */\nexport type LegalLandLayerSystem = \"dls\" | \"nts\" | \"fps\" | \"on\" | \"river_lot\";\n\nconst LAYER_SYSTEM: Record<LegalLandSystem, LegalLandLayerSystem> = {\n DLS: \"dls\",\n NTS: \"nts\",\n GTS: \"on\",\n FPS: \"fps\",\n RIVER_LOT: \"river_lot\",\n};\n\n/** True when `q` is a well-formed legal-land description, not a civic query. */\nexport function looksLikeLegalLand(q: string): boolean {\n return parseLegalLand(q) != null;\n}\n\nexport interface LegalLandDetails {\n system: LegalLandLayerSystem;\n province?: string;\n canonical: string;\n unit: LegalLandUnit;\n components: LegalLandComponents;\n}\n\n/** Map a parse onto the public result block. Does not invent coordinates. */\nexport function toLegalLandDetails(parsed: LegalLandParse): LegalLandDetails {\n return {\n system: LAYER_SYSTEM[parsed.system],\n ...(parsed.province ? { province: parsed.province } : {}),\n canonical: parsed.canonical,\n unit: parsed.unit,\n components: parsed.components,\n };\n}\n","/**\n * Energy-query detect for wells / facilities.\n *\n * UWI grammars are Township Canada's `web/server/utils/regex.js` (`uwi`, `bc_uwi`).\n * Well licences are only treated as energy when they carry a clear prefix (`W`,\n * `WL`, `LIC` / `LICENCE` / `LICENSE`). Bare digits stay civic.\n * Facility names are too generic: they are energy only when the caller asks\n * (`?layers=well|facility` or `profile`/`source=energy`).\n */\n\nexport const ENERGY_SEARCH_LAYERS = [\"well\", \"facility\"] as const;\nexport type EnergySearchLayer = (typeof ENERGY_SEARCH_LAYERS)[number];\n\nexport type EnergyQueryKind = \"uwi\" | \"licence\";\n\nexport interface EnergyQuery {\n kind: EnergyQueryKind;\n /** Trimmed original. */\n raw: string;\n /** Lookup key: UWI uppercased, or licence digits. */\n key: string;\n}\n\n/**\n * AER ST37 (`00/…`, `F1/…`) and Petrinex/IHS (`102/…`) DLS UWI.\n * Format: LocationExceptionCode/LSD-Section-Township-Range-Meridian/EventSequence\n */\nconst DLS_UWI =\n /^\\s*(?:[A-Z0-9]{2}|\\d{3})\\/(1[0-6]|0?[1-9])-(3[0-6]|[12][0-9]|0?[1-9])-(1[0-2][0-9]|0?[1-9][0-9]|00[1-9])-(([1-8][0-9]|9[0-3]|0?[1-9])\\s*(w[1-6]|e[1-2]|[1-6]))\\s*m?\\/?(\\w{1,2})?\\s*$/i;\n\n/**\n * BC NTS UWI: `200/B-039-H/094-B-08/00`\n * Format: LocationExceptionCode/Unit-Block-Quadrant/MapArea-Group-Sheet/EventSequence\n */\nconst BC_UWI =\n /^\\s*\\d{3}\\/(a|b|c|d)(\\s*-\\s*|\\s+)?((00|0)?[1-9]|0?[1-9][0-9]|100)(\\s*-\\s*|\\s+)?(a|b|c|d|e|f|g|h|i|j|k|l)\\s*?\\/\\s*(0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])(\\s*-\\s*|\\s+)?(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p)(\\s*-\\s*|\\s+)?(1[0-6]|0?[1-9])\\/\\d{1,2}\\s*$/i;\n\n/** Prefixed well licence. Bare 5–7 digit strings are not distinctive enough. */\nconst WELL_LICENCE = /^\\s*(?:w|wl|lic(?:\\.|ence|ense)?)\\s*[-#]?\\s*(\\d{5,7})\\s*$/i;\n\nexport function parseEnergyQuery(q: string): EnergyQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (!raw) return null;\n if (BC_UWI.test(raw) || DLS_UWI.test(raw)) {\n return { kind: \"uwi\", raw, key: raw.replace(/\\s+/g, \"\").toUpperCase() };\n }\n const licence = WELL_LICENCE.exec(raw);\n if (licence?.[1]) {\n return { kind: \"licence\", raw, key: licence[1] };\n }\n return null;\n}\n\n/** True when `q` is a UWI or a prefixed well licence, not a civic address. */\nexport function looksLikeEnergyQuery(q: string): boolean {\n return parseEnergyQuery(q) != null;\n}\n\nexport function parseEnergySearchLayers(raw: string | undefined): EnergySearchLayer[] {\n if (!raw) return [];\n const out: EnergySearchLayer[] = [];\n const seen = new Set<EnergySearchLayer>();\n for (const token of raw.split(\",\")) {\n const id = token.trim();\n if (id === \"well\" || id === \"facility\") {\n if (!seen.has(id)) {\n seen.add(id);\n out.push(id);\n }\n }\n }\n return out;\n}\n\nexport function isEnergySearchSource(raw: string | undefined): boolean {\n return raw?.trim() === \"energy\";\n}\n\n/**\n * Whether this search should take the energy branch (addon gate + table lookup).\n * Civic strings stay civic unless the caller asked for energy layers/source.\n */\nexport function energySearchRequested(\n q: string,\n opts: { layers?: string; profile?: string; source?: string } = {},\n): boolean {\n if (looksLikeEnergyQuery(q)) return true;\n if (parseEnergySearchLayers(opts.layers).length > 0) return true;\n return isEnergySearchSource(opts.profile) || isEnergySearchSource(opts.source);\n}\n\nexport interface EnergyDetails {\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n}\n","/**\n * Point-in-polygon identify: which catalog features contain a coordinate.\n *\n * Identify-capable layers are a subset of the gateway catalog. `parks`,\n * `rail`, `aboriginal-lands`, `energy.ccs`, and `energy.geothermal` are\n * catalogued for tiles/overlays but have no identify table, so they 400 here\n * rather than return a silent miss. Energy tables are empty-safe: a missing\n * relation is `[]` for that layer, not a 500.\n */\n\nexport const LAND_IDENTIFY_LAYER_IDS = [\n \"municipalities\",\n \"survey.dls\",\n \"survey.nts\",\n \"survey.parcels\",\n] as const;\n\nexport const ENERGY_IDENTIFY_LAYER_IDS = [\n \"energy.wells\",\n \"energy.pipelines\",\n \"energy.facilities\",\n] as const;\n\n/** Routed through the Land container as extra tables. Not a fourth container. */\nexport const MINING_IDENTIFY_LAYER_IDS = [\n \"mining.occurrences\",\n \"mining.prospectivity\",\n \"mining.sk-potential\",\n] as const;\n\nexport const IDENTIFY_LAYER_IDS = [\n ...LAND_IDENTIFY_LAYER_IDS,\n ...ENERGY_IDENTIFY_LAYER_IDS,\n \"energy.fields\",\n \"energy.petroleum-tenure\",\n \"energy.mineral-tenure\",\n ...MINING_IDENTIFY_LAYER_IDS,\n] as const;\n\nexport type IdentifyLayerId = (typeof IDENTIFY_LAYER_IDS)[number];\n\n/** Default when `layers` is omitted: core only, so an account key without add-ons can identify. */\nexport const DEFAULT_IDENTIFY_LAYERS: readonly IdentifyLayerId[] = [\"municipalities\"];\n\nexport function isIdentifyLayer(id: string): id is IdentifyLayerId {\n return (IDENTIFY_LAYER_IDS as readonly string[]).includes(id);\n}\n\nexport function isLandIdentifyLayer(id: IdentifyLayerId): boolean {\n return (\n (LAND_IDENTIFY_LAYER_IDS as readonly string[]).includes(id) ||\n (MINING_IDENTIFY_LAYER_IDS as readonly string[]).includes(id)\n );\n}\n\nexport function isEnergyIdentifyLayer(id: IdentifyLayerId): boolean {\n return id.startsWith(\"energy.\");\n}\n\nexport function knownIdentifyLayers(): string {\n return IDENTIFY_LAYER_IDS.join(\", \");\n}\n\nexport function parseIdentifyLayers(raw: string | undefined): { layers: IdentifyLayerId[] } | { error: string } {\n const ids = (raw ?? \"\")\n .split(\",\")\n .map((id) => id.trim())\n .filter((id) => id.length > 0);\n if (ids.length === 0) {\n return { layers: [...DEFAULT_IDENTIFY_LAYERS] };\n }\n const layers: IdentifyLayerId[] = [];\n const seen = new Set<string>();\n for (const id of ids) {\n if (!isIdentifyLayer(id)) {\n return { error: `unknown identify layer \"${id}\". known: ${knownIdentifyLayers()}` };\n }\n if (!seen.has(id)) {\n seen.add(id);\n layers.push(id);\n }\n }\n return { layers };\n}\n\nexport function parseIdentifyAt(\n at: string | undefined,\n lon: string | undefined,\n lat: string | undefined,\n): { at: [number, number] } | { error: string } {\n const rawAt = at?.trim();\n if (rawAt) {\n const pieces = rawAt.split(\",\");\n if (pieces.length !== 2 || pieces.some((p) => p.trim() === \"\")) {\n return { error: \"at must be lng,lat\" };\n }\n const lng = Number(pieces[0]);\n const latN = Number(pieces[1]);\n if (!Number.isFinite(lng) || !Number.isFinite(latN)) {\n return { error: \"at must be lng,lat\" };\n }\n return { at: [lng, latN] };\n }\n if (lon != null && lon !== \"\" && lat != null && lat !== \"\") {\n const lng = Number(lon);\n const latN = Number(lat);\n if (!Number.isFinite(lng) || !Number.isFinite(latN)) {\n return { error: \"lon and lat must be numbers\" };\n }\n return { at: [lng, latN] };\n }\n return { error: \"at (lng,lat) or lon and lat required\" };\n}\n\nexport interface IdentifyOptions {\n /** `[lng, lat]` in WGS84 degrees. */\n at: [number, number];\n /** Identify-capable catalog ids. Default `municipalities`. Survey layers need `legal-land`. Energy layers need `energy`. `survey.parcels` needs `agriculture`. Mining layers need `mining`. */\n layers?: readonly string[];\n /** Include polygons when the row has one. */\n geometry?: boolean;\n signal?: AbortSignal;\n}\n\nexport interface IdentifyResult {\n layer: IdentifyLayerId;\n id: string;\n name: string;\n province?: string;\n unit?: string;\n survey_system?: string;\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n pid?: string;\n geometry?: {\n type: \"Polygon\" | \"MultiPolygon\";\n coordinates: number[][][] | number[][][][];\n };\n}\n\nexport interface IdentifyResponse {\n at: [number, number];\n results: IdentifyResult[];\n}\n","/**\n * RFC 4180 CSV, streaming, with the Canadian-address-file details that break naive splitters.\n *\n * Hand-written rather than a dependency for two reasons that both matter here. `@unmap/cli` is\n * deliberately a zero-runtime-dependency Node tool, and the hosted batch consumer is a\n * Cloudflare Worker — one codec that runs in both, with no bundler shims, is worth the ~150\n * lines. The other reason is that the failure modes below are the whole job: a parser that gets\n * them wrong corrupts a customer's file silently, which is worse than refusing it.\n *\n * What it handles, and why each one is here:\n *\n * - **A UTF-8 BOM.** Excel writes one. Left in place it becomes part of the first header name,\n * so `--query-column address` fails to find `address` and the error blames the user.\n * - **Quoted commas and quoted newlines.** \"Gatineau, QC\" and a multi-line note field are\n * ordinary in address exports. A record can therefore span several lines, which is why a\n * hosted job cannot chunk a CSV at arbitrary newlines.\n * - **Escaped quotes** (`\"\"` inside a quoted field).\n * - **CRLF, LF and a lone CR** as record separators, mixed within one file.\n * - **Leading zeros** — postal codes and civic numbers. Everything stays a string, always.\n * - **Ragged rows.** A row with fewer or more fields than the header is a fact about the file,\n * not a crash: it is reported as an error for that row and the rest of the file continues.\n *\n * The parser is a state machine over chunks, so a 50 MiB file never has to be a 50 MiB string.\n */\n\nexport interface CsvRecord {\n /** 1-based index among data records, excluding the header. Stable across resumes. */\n index: number;\n /** Field values in header order, exactly as they appeared. Never coerced. */\n values: string[];\n}\n\nexport interface CsvProblem {\n index: number;\n code: \"too_few_fields\" | \"too_many_fields\";\n message: string;\n}\n\nconst QUOTE = 34; // \"\nconst COMMA = 44; // ,\nconst CR = 13;\nconst LF = 10;\n\n/**\n * Incremental parser. Feed it strings; it yields complete records.\n *\n * Deliberately not an async iterator: the caller owns the read loop, which is what lets the CLI\n * and the Worker use their own very different stream APIs over the same state machine.\n */\nexport class CsvParser {\n private field = \"\";\n private row: string[] = [];\n private inQuotes = false;\n /** A quote seen inside a quoted field: either an escape or the close. */\n private pendingQuote = false;\n /** A CR seen outside quotes: end of record, and a following LF is part of the same break. */\n private pendingCr = false;\n private started = false;\n private rowHasContent = false;\n\n /**\n * Feed one chunk. Returns the records completed by it.\n *\n * A record that ends exactly at a chunk boundary is returned here; a partial one is held until\n * the next chunk, or until `end()`.\n */\n write(chunk: string): string[][] {\n const out: string[][] = [];\n // The BOM is only a BOM at the very start of the stream. Anywhere else U+FEFF is a\n // zero-width no-break space and part of somebody's data.\n if (!this.started) {\n this.started = true;\n if (chunk.charCodeAt(0) === 0xfeff) chunk = chunk.slice(1);\n }\n\n for (let i = 0; i < chunk.length; i++) {\n const ch = chunk.charCodeAt(i);\n\n if (this.pendingCr) {\n this.pendingCr = false;\n // CRLF is one break; a lone CR was already the break, so an LF here is consumed.\n if (ch === LF) continue;\n }\n\n if (this.inQuotes) {\n if (this.pendingQuote) {\n this.pendingQuote = false;\n if (ch === QUOTE) {\n this.field += '\"';\n continue;\n }\n this.inQuotes = false;\n // Fall through: this character closes the field or the record.\n } else if (ch === QUOTE) {\n this.pendingQuote = true;\n continue;\n } else {\n this.field += chunk[i];\n continue;\n }\n }\n\n if (ch === QUOTE && this.field === \"\") {\n this.inQuotes = true;\n this.rowHasContent = true;\n continue;\n }\n if (ch === COMMA) {\n this.row.push(this.field);\n this.field = \"\";\n this.rowHasContent = true;\n continue;\n }\n if (ch === CR) {\n this.pendingCr = true;\n out.push(this.finishRow());\n continue;\n }\n if (ch === LF) {\n out.push(this.finishRow());\n continue;\n }\n this.field += chunk[i];\n this.rowHasContent = true;\n }\n return out.filter((r) => r !== EMPTY);\n }\n\n /** Flush a final record with no trailing newline. */\n end(): string[][] {\n if (this.inQuotes && this.pendingQuote) this.inQuotes = false;\n if (this.field !== \"\" || this.row.length > 0 || this.rowHasContent) {\n const row = this.finishRow();\n return row === EMPTY ? [] : [row];\n }\n return [];\n }\n\n /** True when the stream ended inside an unterminated quoted field. */\n get unterminated(): boolean {\n return this.inQuotes && !this.pendingQuote;\n }\n\n private finishRow(): string[] {\n this.row.push(this.field);\n const row = this.row;\n this.field = \"\";\n this.row = [];\n const hadContent = this.rowHasContent;\n this.rowHasContent = false;\n // A blank line between records is whitespace, not a row of one empty field. A line that is\n // genuinely one empty QUOTED field (\"\") has rowHasContent set and survives.\n if (!hadContent && row.length === 1 && row[0] === \"\") return EMPTY;\n return row;\n }\n}\n\n/** Sentinel for \"this line was blank\", distinguishable from a real one-empty-field row. */\nconst EMPTY: string[] = [];\n\n/**\n * Align a raw row against the header.\n *\n * Ragged rows are reported, not repaired and not thrown: a short row is padded so the caller can\n * still carry its original values into the output, and a long row keeps its extra fields for the\n * same reason. Discarding either would lose customer data in a file we were only asked to\n * annotate.\n */\nexport function alignRow(\n header: readonly string[],\n values: readonly string[],\n index: number,\n): { record: CsvRecord; problem?: CsvProblem } {\n if (values.length === header.length) return { record: { index, values: [...values] } };\n if (values.length < header.length) {\n return {\n record: { index, values: [...values, ...Array(header.length - values.length).fill(\"\")] },\n problem: {\n index,\n code: \"too_few_fields\",\n message: `row ${index} has ${values.length} fields, header has ${header.length}`,\n },\n };\n }\n return {\n record: { index, values: [...values] },\n problem: {\n index,\n code: \"too_many_fields\",\n message: `row ${index} has ${values.length} fields, header has ${header.length}`,\n },\n };\n}\n\n/**\n * Quote a value for output: only when it has to be, and always the same way.\n *\n * The tab is in the set even though RFC 4180 does not require it. It is there for\n * `escapeForSpreadsheet` below, which neutralises a formula by prefixing one: unquoted, a\n * leading tab is read inconsistently across spreadsheet apps, and quoted it is unambiguous\n * everywhere. A tab anywhere else in a field is rare enough that quoting it costs nothing.\n */\nexport function encodeField(value: string): string {\n if (value === \"\") return \"\";\n if (/[\",\\r\\n\\t]/.test(value)) return `\"${value.replace(/\"/g, '\"\"')}\"`;\n return value;\n}\n\n/**\n * Whether a value would be read as a formula by a spreadsheet.\n *\n * Excel, Sheets and LibreOffice all evaluate a cell beginning `=`, `+`, `-`, `@`, and CSV is the\n * classic delivery vehicle for that (CSV injection). It matters here because a geocoder's output\n * is a file people open in a spreadsheet, and because Canadian address data really does contain\n * values that start this way — a negative latitude, \"-123 Main St\" in a malformed row.\n *\n * Detection is separated from mitigation on purpose: `escapeForSpreadsheet` prefixes a tab so\n * the cell displays as text, and the caller decides whether to apply it, because prefixing\n * changes the bytes and a pipeline reading the file programmatically wants the original.\n */\nexport function looksLikeFormula(value: string): boolean {\n return /^[=+\\-@\\t\\r]/.test(value);\n}\n\nexport function escapeForSpreadsheet(value: string): string {\n return looksLikeFormula(value) ? `\\t${value}` : value;\n}\n\n/** One output line, terminated with CRLF (what RFC 4180 specifies and Excel expects). */\nexport function encodeRow(values: readonly string[], opts: { spreadsheetSafe?: boolean } = {}): string {\n const prepared = opts.spreadsheetSafe ? values.map(escapeForSpreadsheet) : values;\n return prepared.map(encodeField).join(\",\") + \"\\r\\n\";\n}\n","import type { GeocodeMatchReason, GeocodeResult } from \"../index\";\n\n/**\n * The output schema a batch geocode writes, shared by the local CLI and the hosted service.\n *\n * One schema, one place, because the two are meant to be interchangeable: a customer who\n * prototypes with `unmap geocode csv` and then moves the job to the dashboard should get the\n * same columns in the same order with the same meanings, and diffing the two files should show\n * nothing but the coordinates.\n */\n\n/** Prefix on every column this adds, so a customer's own `status` column is never shadowed. */\nexport const OUTPUT_PREFIX = \"unmap_\";\n\n/**\n * What happened to one input row. Distinct from `match_type`, which is about the QUALITY of an\n * answer — a row can have `status: \"matched\"` and `match_type: \"fallback\"`, and that pairing is\n * the common case worth reviewing.\n */\nexport type RowStatus =\n /** A result came back and is good enough to use without looking at it. */\n | \"matched\"\n /** A result came back, but something about it should be looked at. See `unmap_review`. */\n | \"review\"\n /** The query ran and the corpus has nothing. Not an error, and it is billed. */\n | \"no_match\"\n /** The row never became a query: no address to send. Not billed. */\n | \"invalid\"\n /** The query ran and failed permanently (bad credentials, exhausted budget, repeated 5xx). */\n | \"failed\"\n /** Not attempted: the run was cancelled, paused or interrupted before reaching this row. */\n | \"unprocessed\";\n\n/**\n * Why a row is in the review file. Row-level, and deliberately a superset of the result-level\n * `match_reasons`: two of these are about the SHAPE of the answer rather than its content.\n */\nexport type ReviewReason =\n | GeocodeMatchReason\n /** The result is coarser than the row asked for. */\n | \"fallback_match\"\n /** More than one candidate scored closely; the top one may not be the intended one. */\n | \"ambiguous\"\n /** Nothing came back at all. */\n | \"no_match\"\n /** The row could not be turned into a query. */\n | \"invalid_input\";\n\nexport const OUTPUT_COLUMNS = [\n \"unmap_status\",\n \"unmap_id\",\n \"unmap_name\",\n \"unmap_lng\",\n \"unmap_lat\",\n \"unmap_layer\",\n \"unmap_source\",\n \"unmap_match_type\",\n \"unmap_precision\",\n \"unmap_review\",\n] as const;\n\nexport type OutputColumn = (typeof OUTPUT_COLUMNS)[number];\n\n/**\n * Resolve a collision between the columns this writes and the customer's own header.\n *\n * Silently overwriting a column called `unmap_status` would destroy data; refusing the file\n * outright would strand anyone who ran the tool twice on its own output, which is exactly what\n * people do. So a collision is suffixed (`unmap_status_2`) and reported, and the caller decides\n * whether to say something. Comparison is case-insensitive because spreadsheets are.\n */\nexport function resolveOutputHeader(\n inputHeader: readonly string[],\n): { header: string[]; renamed: Array<{ column: OutputColumn; to: string }> } {\n const taken = new Set(inputHeader.map((h) => h.trim().toLowerCase()));\n const renamed: Array<{ column: OutputColumn; to: string }> = [];\n const header = [...inputHeader];\n\n for (const column of OUTPUT_COLUMNS) {\n let name: string = column;\n if (taken.has(name.toLowerCase())) {\n let n = 2;\n while (taken.has(`${column}_${n}`.toLowerCase())) n++;\n name = `${column}_${n}`;\n renamed.push({ column, to: name });\n }\n taken.add(name.toLowerCase());\n header.push(name);\n }\n return { header, renamed };\n}\n\n/**\n * When the top result is close enough to the runner-up that picking it is a coin flip.\n *\n * Ratio, not a difference: BM25 scores are not on an absolute scale and are not comparable\n * across queries, so \"within 5%\" means something for every query while \"within 2 points\" means\n * something only for the query it was tuned on. 0.95 is deliberately tight — this flags a row\n * for a human to look at, and a review file that flags a third of the rows gets ignored whole.\n */\nexport const AMBIGUOUS_SCORE_RATIO = 0.95;\n\n/**\n * Classify one row's outcome from what the API returned.\n *\n * The rule the whole review file rests on: **nothing is silently discarded.** Every input row\n * gets exactly one record in the main output, and every row that is not plainly usable gets a\n * reason here and a line in the review file. A row that \"failed quietly\" is the outcome this\n * function exists to make impossible.\n */\nexport function classify(results: readonly GeocodeResult[]): { status: RowStatus; reasons: ReviewReason[] } {\n if (results.length === 0) return { status: \"no_match\", reasons: [\"no_match\"] };\n\n const [top, second] = results;\n const reasons: ReviewReason[] = [];\n\n if (top!.match_type === \"fallback\") reasons.push(\"fallback_match\");\n for (const r of top!.match_reasons ?? []) reasons.push(r);\n\n if (\n second &&\n typeof top!.score === \"number\" &&\n typeof second.score === \"number\" &&\n top!.score > 0 &&\n second.score / top!.score >= AMBIGUOUS_SCORE_RATIO\n ) {\n reasons.push(\"ambiguous\");\n }\n\n return { status: reasons.length ? \"review\" : \"matched\", reasons };\n}\n\n/** The ten values, in `OUTPUT_COLUMNS` order, for one row. */\nexport function outputValues(\n status: RowStatus,\n reasons: readonly ReviewReason[],\n top: GeocodeResult | undefined,\n): string[] {\n return [\n status,\n top?.id ?? \"\",\n top?.name ?? \"\",\n // Fixed precision rather than the float's own repr: a CSV of coordinates is read by humans\n // and by spreadsheets, and 51.04000000000001 is neither. 7 dp is ~1 cm, well beyond what any\n // source in the corpus actually resolves.\n top ? trimCoordinate(top.lng) : \"\",\n top ? trimCoordinate(top.lat) : \"\",\n top?.layer ?? \"\",\n top?.source ?? \"\",\n top?.match_type ?? \"\",\n top?.precision ?? \"\",\n reasons.join(\" \"),\n ];\n}\n\nfunction trimCoordinate(n: number): string {\n return n.toFixed(7).replace(/\\.?0+$/, \"\");\n}\n","import { ENERGY_SEARCH_LAYERS, type EnergySearchLayer } from \"./energy\";\n\n/**\n * `layers=` on `/geocode/search`, `/geocode/autocomplete` and `/geocode/structured`.\n *\n * The parameter already existed and already meant one thing: `layers=well,facility` sends the\n * query to the energy container instead of the civic one. Civic result types are added to the\n * same parameter rather than to a new one, because from the caller's side they are the same\n * question — \"which kinds of thing may come back\" — and two parameters answering it would have\n * to define what happens when both are set.\n *\n * What the two sets do is genuinely different, and that is why they cannot be mixed:\n * `well`/`facility` select a BACKEND (a different container, a different corpus, gated by the\n * `energy` add-on); the civic values FILTER results inside one corpus. `layers=address,well`\n * has no meaning that is not a guess, so it is a 400 rather than a silent choice.\n *\n * Unknown values are also a 400. That is a tightening: before this, unrecognised tokens were\n * dropped silently, so `layers=adress` searched everything and looked like it had worked.\n */\nexport const CIVIC_SEARCH_LAYERS = [\"address\", \"street\", \"locality\", \"region\", \"poi\"] as const;\nexport type CivicSearchLayer = (typeof CIVIC_SEARCH_LAYERS)[number];\n\nexport type SearchLayersParse =\n | { kind: \"none\" }\n | { kind: \"civic\"; layers: CivicSearchLayer[] }\n | { kind: \"energy\"; layers: EnergySearchLayer[] }\n | { error: string };\n\nconst CIVIC = new Set<string>(CIVIC_SEARCH_LAYERS);\nconst ENERGY = new Set<string>(ENERGY_SEARCH_LAYERS);\n\n/** Every accepted value, for error messages and docs. */\nexport function knownSearchLayers(): string {\n return [...CIVIC_SEARCH_LAYERS, ...ENERGY_SEARCH_LAYERS].join(\", \");\n}\n\nexport function parseSearchLayers(raw: string | undefined): SearchLayersParse {\n if (raw == null || raw.trim() === \"\") return { kind: \"none\" };\n\n const civic: CivicSearchLayer[] = [];\n const energy: EnergySearchLayer[] = [];\n const seen = new Set<string>();\n\n for (const token of raw.split(\",\")) {\n const id = token.trim();\n if (id === \"\") continue;\n if (seen.has(id)) continue;\n seen.add(id);\n if (CIVIC.has(id)) civic.push(id as CivicSearchLayer);\n else if (ENERGY.has(id)) energy.push(id as EnergySearchLayer);\n else return { error: `unknown layer \"${id}\". Known layers: ${knownSearchLayers()}` };\n }\n\n if (civic.length && energy.length) {\n return {\n error:\n `layers cannot mix civic and energy values: ${civic.join(\", \")} filter results within ` +\n `the civic corpus, while ${energy.join(\", \")} select the energy corpus instead. Ask for one or the other.`,\n };\n }\n if (energy.length) return { kind: \"energy\", layers: energy };\n if (civic.length) return { kind: \"civic\", layers: civic };\n return { kind: \"none\" };\n}\n\n/**\n * `focus=lng,lat`: a soft proximity preference.\n *\n * It moves ranking toward the point and excludes nothing — a relevant result on the other side\n * of the country still comes back, behind the near ones. That is the whole distinction from\n * `bbox`, which is a hard filter, and the docs and the UI copy have to keep saying it: \"prefer\n * nearby\" is not \"restrict to this area\".\n */\nexport type FocusParse = { focus: [number, number] } | { error: string } | undefined;\n\nexport function parseFocus(raw: string | undefined): FocusParse {\n if (raw == null || raw.trim() === \"\") return undefined;\n // Each piece must be non-empty before it is a number. `Number(\"\")` is 0, so a trailing comma\n // (\"-114,\") would otherwise parse as latitude 0 — a point in the Gulf of Guinea, silently\n // accepted, quietly ranking everything by its distance from there.\n const pieces = raw.split(\",\").map((s) => s.trim());\n const parts = pieces.map((s) => (s === \"\" ? Number.NaN : Number(s)));\n if (parts.length !== 2 || !parts.every(Number.isFinite)) {\n return { error: \"focus must be lng,lat with two finite numbers\" };\n }\n const [lng, lat] = parts as [number, number];\n if (lng < -180 || lng > 180) return { error: `focus longitude ${lng} is outside [-180, 180]` };\n if (lat < -90 || lat > 90) return { error: `focus latitude ${lat} is outside [-90, 90]` };\n return { focus: [lng, lat] };\n}\n","/**\n * Canada's provinces and territories, every spelling a query or a corpus row may use.\n *\n * Shared rather than duplicated: the geocode container reads it to strip a \"… , AB\" tail off a\n * free-text query (`apps/geocode/src/query-tail.ts`), and the gateway reads it to normalise the\n * `region` field of a structured search. Two copies would drift, and a province the gateway\n * accepts but the container cannot match is a silently empty result.\n *\n * Abbreviations may or may not carry a period (\"Ont\", \"Ont.\", \"Sask\", \"N.W.T.\", \"B.C.\"); the\n * dotted spellings are generated below rather than hand-listed. Bare \"que\" and \"man\" are\n * deliberately absent — both are ordinary words (French words ending in \"que\"; the Isle of Man) —\n * only their dotted forms (\"que.\", \"qué.\") count as an abbreviation.\n */\nconst BASE: Record<string, string> = {\n ab: \"AB\", alberta: \"AB\", alta: \"AB\",\n bc: \"BC\", \"british columbia\": \"BC\", \"colombie-britannique\": \"BC\",\n mb: \"MB\", manitoba: \"MB\",\n nb: \"NB\", \"new brunswick\": \"NB\", \"nouveau-brunswick\": \"NB\",\n nl: \"NL\", \"newfoundland and labrador\": \"NL\", newfoundland: \"NL\", \"terre-neuve-et-labrador\": \"NL\", nfld: \"NL\",\n ns: \"NS\", \"nova scotia\": \"NS\", \"nouvelle-écosse\": \"NS\", \"nouvelle-ecosse\": \"NS\",\n nt: \"NT\", \"northwest territories\": \"NT\", \"territoires du nord-ouest\": \"NT\", nwt: \"NT\",\n nu: \"NU\", nunavut: \"NU\",\n on: \"ON\", ontario: \"ON\", ont: \"ON\",\n pe: \"PE\", pei: \"PE\", \"prince edward island\": \"PE\", \"île-du-prince-édouard\": \"PE\",\n qc: \"QC\", pq: \"QC\", quebec: \"QC\", québec: \"QC\", \"que.\": \"QC\", \"qué.\": \"QC\",\n sk: \"SK\", saskatchewan: \"SK\", sask: \"SK\",\n yt: \"YT\", yukon: \"YT\",\n};\n\n// Dotted spellings of every abbreviation: \"b.c.\", \"n.w.t.\", \"ont.\", \"sask.\", \"alta.\", \"nfld.\".\n// Generated, not hand-listed, so a new abbreviation gets its dotted form for free.\nfor (const [k, v] of Object.entries(BASE)) {\n if (/^[a-z]{2}$/.test(k)) BASE[k[0] + \".\" + k[1] + \".\"] = v;\n else if (k === \"nwt\" || k === \"pei\") BASE[k.split(\"\").join(\".\") + \".\"] = v;\n else if (/^[a-z]{3,4}$/.test(k)) BASE[k + \".\"] = v;\n}\n\n/** Every recognised spelling, lowercase, mapped to its two-letter code. */\nexport const REGIONS: Readonly<Record<string, string>> = BASE;\n\n/**\n * Spellings sorted longest first, so \"newfoundland and labrador\" is tried before\n * \"newfoundland\". Computed after the dotted-form loop, so generated keys sort with the rest.\n */\nexport const REGION_KEYS: readonly string[] = Object.keys(BASE).sort((a, b) => b.length - a.length);\n\n/** The thirteen codes, for validation messages. */\nexport const REGION_CODES: readonly string[] = [...new Set(Object.values(BASE))].sort();\n\n/**\n * Resolve any accepted spelling to its two-letter code. Accent- and case-insensitive, so\n * \"Québec\", \"quebec\" and \"QC\" all give \"QC\". Undefined when nothing matches.\n */\nexport function parseRegion(raw: string | undefined | null): string | undefined {\n if (!raw) return undefined;\n const key = raw.trim().toLowerCase();\n if (REGIONS[key]) return REGIONS[key];\n const unaccented = key.normalize(\"NFD\").replace(/\\p{M}/gu, \"\");\n for (const [k, v] of Object.entries(REGIONS)) {\n if (k.normalize(\"NFD\").replace(/\\p{M}/gu, \"\") === unaccented) return v;\n }\n return undefined;\n}\n\n/**\n * Every spelling the corpus may hold for a region code, uppercased: the code itself plus each\n * name that maps to it. The loaders do not normalise `region` (OpenAddresses passes the source\n * value through, so `ONTARIO` and `ON` both occur), so a filter or boost must match all of them.\n */\nexport function regionForms(code: string): string[] {\n const forms = new Set<string>([code.toUpperCase()]);\n for (const [k, v] of Object.entries(REGIONS)) if (v === code) forms.add(k.toUpperCase());\n return [...forms];\n}\n","import { parseRegion, REGION_CODES } from \"./regions\";\n\n/**\n * Structured forward geocoding: the caller hands over the components of an address instead of\n * one string, and the engine keeps them apart all the way through.\n *\n * The distinction that matters, and the one the docs state plainly: some fields CONSTRAIN the\n * result set and some only RANK it.\n *\n * - `address` and `city` are retrieval and ranking. They decide which rows are candidates and\n * which candidate wins.\n * - `region` and `postalcode` constrain, under one rule: **a supplied component excludes\n * records that contradict it, and never excludes records that are silent about it.** A row\n * in Ontario is out when you asked for Alberta; a row with no province at all stays in and\n * comes back with `region_not_verified` in its `match_reasons`.\n * - `country` is validated, not matched: the corpus is Canada only.\n *\n * The alternative — joining the fields into one string and running the free-text engine — is\n * what this exists instead of. It would make `region` a scoring token that any row could match\n * by coincidence, which is how \"Kelowna, BC\" used to return the province of British Columbia.\n */\nexport interface StructuredQuery {\n /** Street line: civic number and street name, as the caller would write it on an envelope. */\n address?: string;\n city?: string;\n /** Two-letter code, normalised from any accepted spelling (\"Québec\" and \"qc\" both give \"QC\"). */\n region?: string;\n /** Six characters, no space, uppercase (\"M5H2N2\"). */\n postalcode?: string;\n /** Always \"CA\". Present so the field can be supplied and validated, not so it can vary. */\n country?: string;\n}\n\nexport interface StructuredOptions extends StructuredQuery {\n limit?: number;\n lang?: string;\n /** [minLng, minLat, maxLng, maxLat] hard filter, same semantics as `search`. */\n bbox?: [number, number, number, number];\n /** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */\n focus?: [number, number];\n /** Civic result types to allow (`address`, `street`, `locality`, `region`, `poi`). */\n layers?: string;\n signal?: AbortSignal;\n}\n\n/** The query fields, in the order the docs and the CLI list them. */\nexport const STRUCTURED_FIELDS = [\"address\", \"city\", \"region\", \"postalcode\", \"country\"] as const;\n\n/**\n * The fields that can start a search on their own. `region` is deliberately not one of them: a\n * province with no other component is not a geocode, it is a request for four million rows, and\n * answering it with an arbitrary ranked slice would be worse than saying so.\n */\nconst ANCHOR_FIELDS = [\"address\", \"city\", \"postalcode\"] as const;\n\nconst POSTCODE = /^[A-Z]\\d[A-Z]\\d[A-Z]\\d$/;\n\nexport type StructuredParseResult = { query: StructuredQuery } | { error: string };\n\n/** Normalise and validate raw structured input. Returns the cleaned query or one error string. */\nexport function parseStructuredQuery(raw: Partial<Record<string, string | undefined>>): StructuredParseResult {\n const trim = (v: string | undefined) => {\n const t = (v ?? \"\").trim();\n return t === \"\" ? undefined : t;\n };\n\n const country = trim(raw.country);\n if (country && !/^(ca|can|canada)$/i.test(country)) {\n return { error: `country must be CA: unmap covers Canada only (got \"${country}\")` };\n }\n\n let region: string | undefined;\n const rawRegion = trim(raw.region);\n if (rawRegion) {\n region = parseRegion(rawRegion);\n if (!region) {\n return { error: `unknown region \"${rawRegion}\". Use a province or territory code: ${REGION_CODES.join(\", \")}` };\n }\n }\n\n let postalcode: string | undefined;\n const rawPostal = trim(raw.postalcode);\n if (rawPostal) {\n postalcode = rawPostal.replace(/\\s+/g, \"\").toUpperCase();\n if (!POSTCODE.test(postalcode)) {\n return { error: `postalcode must be a Canadian postal code such as M5H 2N2 (got \"${rawPostal}\")` };\n }\n }\n\n const query: StructuredQuery = {\n ...(trim(raw.address) ? { address: trim(raw.address) } : {}),\n ...(trim(raw.city) ? { city: trim(raw.city) } : {}),\n ...(region ? { region } : {}),\n ...(postalcode ? { postalcode } : {}),\n country: \"CA\",\n };\n\n if (!ANCHOR_FIELDS.some((f) => query[f] != null)) {\n return {\n error: `structured search needs at least one of ${ANCHOR_FIELDS.join(\", \")}. ` +\n `region and country narrow a search; they cannot be one on their own.`,\n };\n }\n\n return { query };\n}\n","/**\n * ParcelMap BC PID detect. Grammar matches Township Canada\n * `web/server/api/parcels/search.get.js`: nine digits, optional dashes\n * (`010-867-813` or `010867813`). PIN, plan number, and parcel-name\n * fuzzy match are not distinctive enough to steal civic search.\n */\n\nexport interface ParcelQuery {\n /** Trimmed original. */\n raw: string;\n /** Lookup key: `XXX-XXX-XXX`. */\n pid: string;\n}\n\nconst PID = /^\\d{3}-?\\d{3}-?\\d{3}$/;\n\nexport function parseParcel(q: string): ParcelQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (!PID.test(raw)) return null;\n const digits = raw.replace(/-/g, \"\");\n return {\n raw,\n pid: `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`,\n };\n}\n\n/** True when `q` is a ParcelMap BC PID, not a civic address. */\nexport function looksLikeParcel(q: string): boolean {\n return parseParcel(q) != null;\n}\n","/**\n * Bbox query against identify-capable catalog layers. Same PostGIS tables as\n * `/identify`; tile-only catalog ids are rejected (gateway 501), not silently empty.\n */\n\nimport {\n IDENTIFY_LAYER_IDS,\n isIdentifyLayer,\n knownIdentifyLayers,\n type IdentifyLayerId,\n type IdentifyResult,\n} from \"./identify\";\n\nexport const QUERY_LAYER_IDS = IDENTIFY_LAYER_IDS;\nexport type QueryLayerId = IdentifyLayerId;\n\nexport const DEFAULT_QUERY_LIMIT = 50;\nexport const MAX_QUERY_LIMIT = 50;\n\nexport type QueryBbox = [number, number, number, number];\n\nexport function knownQueryLayers(): string {\n return knownIdentifyLayers();\n}\n\nexport function parseQueryLayer(\n raw: string | undefined,\n): { layer: QueryLayerId } | { unknown: string; error: string } | { error: string } {\n const id = (raw ?? \"\").trim();\n if (!id) return { error: \"layer required\" };\n if (isIdentifyLayer(id)) return { layer: id };\n return {\n unknown: id,\n error: `unknown query layer \"${id}\". queryable: ${knownQueryLayers()}`,\n };\n}\n\nexport function parseQueryBbox(raw: string | undefined): { bbox: QueryBbox } | { error: string } {\n const value = raw?.trim();\n if (!value) return { error: \"bbox (minlng,minlat,maxlng,maxlat) required\" };\n const pieces = value.split(\",\");\n if (pieces.length !== 4 || pieces.some((piece) => piece.trim() === \"\")) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n const nums = pieces.map(Number);\n if (nums.some((n) => !Number.isFinite(n))) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n const [minLng, minLat, maxLng, maxLat] = nums as QueryBbox;\n if (minLng >= maxLng || minLat >= maxLat) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n return { bbox: [minLng, minLat, maxLng, maxLat] };\n}\n\n/** Missing or non-numeric `limit` falls back; a finite value is clamped to 1–50. */\nexport function parseQueryLimit(raw: string | undefined): number {\n if (raw == null || raw === \"\") return DEFAULT_QUERY_LIMIT;\n const n = Number(raw);\n return Number.isFinite(n) ? Math.min(Math.max(Math.trunc(n), 1), MAX_QUERY_LIMIT) : DEFAULT_QUERY_LIMIT;\n}\n\nexport interface QueryOptions {\n /** Identify-capable catalog id. Tile-only catalog ids are a 501. */\n layer: string;\n /** `[minLng, minLat, maxLng, maxLat]` in WGS84 degrees. */\n bbox: QueryBbox;\n /** Max features. Default 50, clamped to 1–50. */\n limit?: number;\n /** Include geometry when the row has one. */\n geometry?: boolean;\n signal?: AbortSignal;\n}\n\nexport interface QueryResponse {\n layer: QueryLayerId;\n bbox: QueryBbox;\n results: IdentifyResult[];\n}\n","/**\n * Canadian rural and alternate delivery addresses.\n *\n * A rural route is not a place. `RR 2` names a mail carrier's circuit through a community, and\n * the same number exists in hundreds of communities; the site of the mailbox is not in any open\n * dataset. So this does not geocode the route. It splits the Canada Post delivery terms off the\n * front of a query, hands the rest to civic search, and returns the parsed terms alongside the\n * community that was found. \"RR 2 Okotoks\" resolves to Okotoks, labelled rural route 2, which is\n * the honest answer: the town is known, the mailbox is not.\n *\n * Recognised, per Canada Post's addressing guide:\n * RR / R.R. / RURAL ROUTE a rural route, numbered\n * SS / SUBURBAN SERVICE a suburban service route, numbered\n * MR / MOBILE ROUTE a mobile route, numbered\n * GD / GENERAL DELIVERY counter pickup, never numbered\n * SITE / COMP(ARTMENT) / BOX the position within the route\n * STN / RPO / LCD the retail outlet the route runs from\n *\n * Deliberately not a locality detector: a query has to carry one of the delivery keywords above\n * for this to fire, so an ordinary civic address is untouched.\n */\n\nexport type RuralRouteKind = \"rural-route\" | \"suburban-service\" | \"mobile-route\" | \"general-delivery\";\n\nexport interface RuralRouteQuery {\n /** Trimmed original. */\n raw: string;\n kind: RuralRouteKind;\n /** Route number as written. Absent for general delivery, which is never numbered. */\n number?: string;\n /** Position within the route, when given. */\n site?: string;\n compartment?: string;\n box?: string;\n /** The retail outlet the route runs from, e.g. \"MAIN\" in \"STN MAIN\". */\n station?: string;\n /**\n * What is left after the delivery terms are removed: normally the community, and the only\n * part that can actually be geocoded. Empty when the query was delivery terms and nothing\n * else, which cannot be resolved to anywhere.\n */\n remainder: string;\n}\n\nconst KINDS: { pattern: RegExp; kind: RuralRouteKind; numbered: boolean }[] = [\n { pattern: /\\b(?:R\\.?\\s?R\\.?|RURAL\\s+ROUTE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"rural-route\", numbered: true },\n { pattern: /\\b(?:S\\.?\\s?S\\.?|SUBURBAN\\s+SERVICE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"suburban-service\", numbered: true },\n { pattern: /\\b(?:M\\.?\\s?R\\.?|MOBILE\\s+ROUTE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"mobile-route\", numbered: true },\n { pattern: /\\b(?:G\\.?\\s?D\\.?|GENERAL\\s+DELIVERY)\\b/i, kind: \"general-delivery\", numbered: false },\n];\n\nconst PARTS: { pattern: RegExp; field: \"site\" | \"compartment\" | \"box\" | \"station\" }[] = [\n { pattern: /\\bSITE\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"site\" },\n { pattern: /\\bCOMP(?:ARTMENT)?\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"compartment\" },\n { pattern: /\\b(?:PO\\s*)?BOX\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"box\" },\n // One word only. Canada Post does publish two-word station names (\"STN ST LAURENT\"), but\n // \"STN MAIN BRANDON\" is ambiguous between a two-word station and a station plus a community,\n // and guessing wrong swallows the only part that can be geocoded. Missing the second word of\n // a rare station name costs a label; stealing the town costs the answer.\n { pattern: /\\b(?:STN|STATION|RPO|LCD)\\s+([A-Z][A-Z0-9'-]*)\\b/i, field: \"station\" },\n];\n\nexport function parseRuralRoute(q: string): RuralRouteQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (raw === \"\") return null;\n\n const matched = KINDS.map((k) => ({ ...k, match: k.pattern.exec(raw) })).find((k) => k.match !== null);\n if (!matched?.match) return null;\n\n // Strip the delivery terms one at a time so the remainder is what a civic corpus can answer.\n let rest = raw.replace(matched.pattern, \" \");\n const parsed: RuralRouteQuery = { raw, kind: matched.kind, remainder: \"\" };\n if (matched.numbered) parsed.number = String(Number(matched.match[1]));\n\n for (const part of PARTS) {\n const hit = part.pattern.exec(rest);\n if (!hit) continue;\n parsed[part.field] = hit[1]!.trim().toUpperCase();\n rest = rest.replace(part.pattern, \" \");\n }\n\n // Whatever survives is the community. Separators the delivery terms were glued to (commas,\n // stray hyphens) go with them rather than reaching the corpus as noise.\n parsed.remainder = rest.replace(/[,;]+/g, \" \").replace(/\\s+/g, \" \").replace(/^[-\\s]+|[-\\s]+$/g, \"\");\n return parsed;\n}\n\n/**\n * True when `q` carries Canada Post delivery terms.\n *\n * Note this is NOT a routing signal in the way `looksLikeLegalLand` is: a rural-route query is\n * still answered by civic search, on its remainder. It exists so the caller can strip the terms\n * and label the result, not so it can pick a different backend.\n */\nexport function looksLikeRuralRoute(q: string): boolean {\n return parseRuralRoute(q) != null;\n}\n"],"mappings":"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","names":["components","rest","con"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/legal-land.ts","../src/energy.ts","../src/identify.ts","../src/batch/csv.ts","../src/batch/output.ts","../src/search-layers.ts","../src/regions.ts","../src/structured.ts","../src/parcel.ts","../src/query.ts","../src/rural-route.ts","../src/places.ts"],"sourcesContent":["import { DEFAULT_GATEWAY } from \"@unmap/core\";\nimport type { LegalLandDetails } from \"./legal-land\";\nimport type { IdentifyOptions, IdentifyResponse } from \"./identify\";\nimport type { QueryOptions, QueryResponse } from \"./query\";\nimport type { StructuredOptions } from \"./structured\";\n\nexport {\n looksLikeLegalLand,\n parseLegalLand,\n toLegalLandDetails,\n type LegalLandComponents,\n type LegalLandDetails,\n type LegalLandLayerSystem,\n type LegalLandParse,\n type LegalLandSystem,\n type LegalLandUnit,\n} from \"./legal-land\";\n\nexport {\n ENERGY_SEARCH_LAYERS,\n energySearchRequested,\n isEnergySearchSource,\n looksLikeEnergyQuery,\n parseEnergyQuery,\n parseEnergySearchLayers,\n type EnergyDetails,\n type EnergyQuery,\n type EnergyQueryKind,\n type EnergySearchLayer,\n} from \"./energy\";\n\nexport {\n DEFAULT_IDENTIFY_LAYERS,\n ENERGY_IDENTIFY_LAYER_IDS,\n IDENTIFY_LAYER_IDS,\n LAND_IDENTIFY_LAYER_IDS,\n MINING_IDENTIFY_LAYER_IDS,\n isEnergyIdentifyLayer,\n isIdentifyLayer,\n isLandIdentifyLayer,\n knownIdentifyLayers,\n parseIdentifyAt,\n parseIdentifyLayers,\n type IdentifyLayerId,\n type IdentifyOptions,\n type IdentifyResponse,\n type IdentifyResult,\n} from \"./identify\";\n\nexport {\n CsvParser,\n alignRow,\n encodeField,\n encodeRow,\n escapeForSpreadsheet,\n looksLikeFormula,\n type CsvProblem,\n type CsvRecord,\n} from \"./batch/csv\";\n\nexport {\n AMBIGUOUS_SCORE_RATIO,\n OUTPUT_COLUMNS,\n OUTPUT_PREFIX,\n classify,\n outputValues,\n resolveOutputHeader,\n type OutputColumn,\n type ReviewReason,\n type RowStatus,\n} from \"./batch/output\";\n\nexport {\n CIVIC_SEARCH_LAYERS,\n knownSearchLayers,\n parseFocus,\n parseSearchLayers,\n type CivicSearchLayer,\n type FocusParse,\n type SearchLayersParse,\n} from \"./search-layers\";\n\nexport {\n REGIONS,\n REGION_CODES,\n REGION_KEYS,\n parseRegion,\n regionForms,\n} from \"./regions\";\n\nexport {\n STRUCTURED_FIELDS,\n parseStructuredQuery,\n type StructuredOptions,\n type StructuredParseResult,\n type StructuredQuery,\n} from \"./structured\";\n\nexport {\n looksLikeParcel,\n parseParcel,\n type ParcelQuery,\n} from \"./parcel\";\n\nexport {\n DEFAULT_QUERY_LIMIT,\n MAX_QUERY_LIMIT,\n QUERY_LAYER_IDS,\n knownQueryLayers,\n parseQueryBbox,\n parseQueryLayer,\n parseQueryLimit,\n type QueryBbox,\n type QueryLayerId,\n type QueryOptions,\n type QueryResponse,\n} from \"./query\";\n\nexport interface GeocoderOptions {\n key: string;\n gateway?: string;\n /** Override the fetch implementation (tests, non-global environments). */\n fetch?: typeof fetch;\n}\n\nexport type GeocodeLayer =\n | \"address\"\n | \"street\"\n | \"locality\"\n | \"region\"\n | \"poi\"\n | \"legal_land\"\n | \"well\"\n | \"facility\"\n | \"parcel\";\n\n/**\n * Which dataset the returned record came from. Conflation\n * (`pipeline/geocoding/conflate.sql`) keeps one survivor per place and deletes the rest, so\n * this is the SURVIVING record's source, not every dataset that agreed on the place. See\n * `docs/services/geocoding-sources.md`.\n */\nexport type GeocodeSource =\n | \"osm\"\n | \"openaddresses\"\n | \"statcan_nar\"\n | \"nrcan_cgndb\"\n | \"wof\"\n | \"geonames\";\n\n/**\n * How well the result answers what was asked, as a category — never a probability, and never\n * derived from `score` (which stays raw BM25 relevance).\n *\n * - `exact` — every component the query supplied and the corpus supports was matched.\n * - `partial` — something supplied did not match, or could not be verified.\n * - `fallback` — the result is COARSER than the query asked for: a civic number was requested\n * and the result carries none. A street or locality standing in for an address is always\n * this, never `exact`.\n * - `unknown` — input-match diagnostics do not apply (reverse, nearby, autocomplete).\n */\nexport type GeocodeMatchType = \"exact\" | \"partial\" | \"fallback\" | \"unknown\";\n\n/**\n * Positional resolution the SOURCE supports, separate from how well the input matched.\n *\n * `point` means a discrete point record in the source dataset — an address point or a POI. It\n * is **not** a rooftop-accuracy claim, and it never means interpolated: the engine performs no\n * interpolation. `locality` and `region` are centroids.\n */\nexport type GeocodePrecision = \"point\" | \"street\" | \"locality\" | \"region\" | \"unknown\";\n\n/** Stable reason codes explaining a non-`exact` `match_type`. Additive; never renamed. */\nexport type GeocodeMatchReason =\n | \"housenumber_not_matched\"\n | \"housenumber_differs\"\n | \"unit_not_verified\"\n | \"unit_differs\"\n | \"postalcode_not_verified\"\n | \"postalcode_differs\"\n | \"region_not_verified\"\n | \"region_differs\"\n | \"terms_unmatched\"\n | \"fuzzy_match\";\n\nexport interface GeocodeResult {\n id: string;\n name: string;\n layer: GeocodeLayer;\n lng: number;\n lat: number;\n score?: number;\n /** Originating dataset of the returned record. */\n source?: GeocodeSource | string;\n /** That dataset's own identifier for the record, when it published one. */\n source_id?: string;\n /** How well the result answers the query. `unknown` where input matching does not apply. */\n match_type?: GeocodeMatchType;\n /** Positional resolution the source supports. Not an accuracy figure. */\n precision?: GeocodePrecision;\n /** Why `match_type` is not `exact`. Empty or absent when it is. */\n match_reasons?: GeocodeMatchReason[];\n /** Canonical category id (e.g. \"health.pharmacy\") when the feature carries one. */\n category?: string;\n /** Spheroidal metres from the query anchor; only `/geocode/nearby` results carry it. */\n distance?: number;\n names?: Record<string, string>;\n address?: {\n housenumber?: string;\n street?: string;\n city?: string;\n region?: string;\n postcode?: string;\n country?: string;\n /** Unit/suite, where the source published one. Most of the corpus does not. */\n unit?: string;\n };\n /** Structured legal-land parse; only `layer: \"legal_land\"` results carry it. */\n legal_land?: LegalLandDetails;\n /** Well / facility extras; only `layer: \"well\" | \"facility\"` results carry them. */\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n /** ParcelMap BC PID; only `layer: \"parcel\"` results carry it. */\n pid?: string;\n /** Survey-cell bounds `[minLng, minLat, maxLng, maxLat]` when the lookup row has a geom. */\n bbox?: [number, number, number, number];\n /** Survey polygon when `?geometry=true` and the lookup row has one. */\n geometry?: {\n type: \"Polygon\" | \"MultiPolygon\";\n coordinates: number[][][] | number[][][][];\n };\n}\n\nexport interface SearchOptions {\n limit?: number;\n lang?: string;\n /** [minLng, minLat, maxLng, maxLat] hard filter. Excludes; see `focus` to merely prefer. */\n bbox?: [number, number, number, number];\n /** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */\n focus?: [number, number];\n /** Include the survey polygon on a legal-land hit when the table has one. */\n geometry?: boolean;\n /**\n * Which result types may come back. Civic values (`address`, `street`, `locality`, `region`,\n * `poi`) filter within the civic corpus; energy values (`well`, `facility`) select the energy\n * corpus instead and need the energy add-on. The two sets cannot be mixed.\n */\n layers?: string;\n /** Province or territory, any accepted spelling. Excludes rows in another province. */\n region?: string;\n signal?: AbortSignal;\n}\n\nexport interface NearbyOptions {\n /** [lng, lat] anchor to sort by distance from. Exactly one of `near` or `bbox`. */\n near?: [number, number];\n /** [minLng, minLat, maxLng, maxLat]; the API anchors at its centre. */\n bbox?: [number, number, number, number];\n /** Search radius in metres (API default 5000, clamped to [50, 50000]). */\n radius?: number;\n limit?: number;\n lang?: string;\n signal?: AbortSignal;\n}\n\nexport class GeocoderError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n /** The gateway's machine-readable `code`, when the response carried one (e.g. `unauthorized`). */\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"GeocoderError\";\n }\n}\n\n/**\n * The gateway answers every error with a JSON body whose `error` says what went wrong\n * (\"mode must be one of auto, car, bicycle, pedestrian, truck\", \"q required\"). Put that in the\n * message so a caller who logs the thrown error reads the reason, not just the status.\n */\nasync function errorFrom(res: { status: number; json(): Promise<unknown> }, path: string): Promise<GeocoderError> {\n const fallback = `geocode ${res.status} on ${path}`;\n let body: { error?: unknown; code?: unknown } | undefined;\n try {\n body = (await res.json()) as { error?: unknown; code?: unknown };\n } catch {\n body = undefined;\n }\n const reason = typeof body?.error === \"string\" ? body.error : undefined;\n const code = typeof body?.code === \"string\" ? body.code : undefined;\n return new GeocoderError(res.status, reason ? `${reason} (${fallback})` : fallback, code);\n}\n\n/** Typed client for the unmap geocoding API (gateway `/geocode/*`). */\nexport class Geocoder {\n private readonly key: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: GeocoderOptions) {\n this.key = opts.key;\n this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n // Bound to globalThis: stored on an instance and called as `this.fetchImpl(...)`, a bare\n // `fetch` reference is invoked with the Geocoder as its receiver, and browsers reject that\n // with \"Illegal invocation\". Node is lenient, so this only ever surfaces in the browser.\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** Full-text search. */\n search(q: string, opts: SearchOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\"/geocode/search\", { q, ...toParams(opts) }, opts.signal);\n }\n\n /** Type-ahead suggestions for partial input. */\n autocomplete(q: string, opts: SearchOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\"/geocode/autocomplete\", { q, ...toParams(opts) }, opts.signal);\n }\n\n /**\n * Forward geocoding from address components rather than one string.\n *\n * `address` and `city` decide which rows are candidates and which wins. `region` and\n * `postalcode` narrow: they drop rows that contradict them and keep rows that are silent\n * about them, which come back with `region_not_verified` / `postalcode_not_verified` in\n * `match_reasons`. Needs at least one of `address`, `city` or `postalcode`.\n */\n structured(opts: StructuredOptions): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/structured\",\n {\n address: opts.address,\n city: opts.city,\n region: opts.region,\n postalcode: opts.postalcode,\n country: opts.country,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n bbox: opts.bbox ? opts.bbox.join(\",\") : undefined,\n focus: opts.focus ? opts.focus.join(\",\") : undefined,\n layers: opts.layers,\n },\n opts.signal,\n );\n }\n\n /**\n * Category search: POIs of a kind near a point, sorted by distance (metres, in each\n * result's `distance`). `category` takes canonical taxonomy ids (\"health.pharmacy\") or\n * plain words in English or French (\"pharmacy\", \"pharmacie\") — the gateway resolves\n * aliases, so this client passes the value through untouched.\n */\n nearby(category: string | string[], opts: NearbyOptions = {}): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/nearby\",\n {\n category: Array.isArray(category) ? category.join(\",\") : category,\n near: opts.near ? opts.near.join(\",\") : undefined,\n bbox: opts.bbox ? opts.bbox.join(\",\") : undefined,\n radius: opts.radius != null ? String(opts.radius) : undefined,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n },\n opts.signal,\n );\n }\n\n /** Reverse geocode a coordinate. */\n reverse(\n lng: number,\n lat: number,\n opts: { lang?: string; signal?: AbortSignal } = {},\n ): Promise<GeocodeResult[]> {\n return this.query(\n \"/geocode/reverse\",\n { lon: String(lng), lat: String(lat), lang: opts.lang },\n opts.signal,\n );\n }\n\n /**\n * Which catalog features contain a point. Default layer is `municipalities`\n * (core). `survey.dls` / `survey.nts` need the `legal-land` add-on.\n * `energy.*` need the `energy` add-on. `mining.*` need the `mining` add-on\n * and identify via Land extra tables. Empty until the matching PostGIS table\n * is loaded. No mining search grammar — claim numbers stay `energy.mineral-tenure`.\n */\n identify(opts: IdentifyOptions): Promise<IdentifyResponse> {\n return this.requestJson(\n \"/identify\",\n {\n at: opts.at.join(\",\"),\n layers: opts.layers?.length ? opts.layers.join(\",\") : undefined,\n geometry: opts.geometry ? \"true\" : undefined,\n },\n opts.signal,\n ) as Promise<IdentifyResponse>;\n }\n\n /**\n * Features of one catalog layer that intersect a bbox. Same PostGIS tables as\n * `identify`. Tile-only catalog ids are a 501. Survey layers need `legal-land`;\n * `energy.*` need `energy`; `survey.parcels` needs `agriculture`; mining\n * layers need `mining`. Empty until the matching table is loaded.\n */\n queryLayer(opts: QueryOptions): Promise<QueryResponse> {\n return this.requestJson(\n \"/data/query\",\n {\n layer: opts.layer,\n bbox: opts.bbox.join(\",\"),\n limit: opts.limit != null ? String(opts.limit) : undefined,\n geometry: opts.geometry ? \"true\" : undefined,\n },\n opts.signal,\n ) as Promise<QueryResponse>;\n }\n\n private async requestJson(\n path: string,\n params: Record<string, string | undefined>,\n signal?: AbortSignal,\n ): Promise<unknown> {\n const q = new URLSearchParams({ key: this.key });\n for (const [k, v] of Object.entries(params)) if (v != null && v !== \"\") q.set(k, v);\n const res = await this.fetchImpl(`${this.base}${path}?${q}`, {\n signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) throw await errorFrom(res, path);\n return res.json();\n }\n\n private async query(\n path: string,\n params: Record<string, string | undefined>,\n signal?: AbortSignal,\n ): Promise<GeocodeResult[]> {\n const body = (await this.requestJson(path, params, signal)) as\n | { results?: GeocodeResult[] }\n | GeocodeResult[];\n return Array.isArray(body) ? body : (body.results ?? []);\n }\n}\n\nfunction toParams(o: SearchOptions): Record<string, string | undefined> {\n return {\n limit: o.limit != null ? String(o.limit) : undefined,\n lang: o.lang,\n bbox: o.bbox ? o.bbox.join(\",\") : undefined,\n focus: o.focus ? o.focus.join(\",\") : undefined,\n region: o.region,\n geometry: o.geometry ? \"true\" : undefined,\n layers: o.layers,\n };\n}\n\nexport {\n looksLikeRuralRoute,\n parseRuralRoute,\n type RuralRouteKind,\n type RuralRouteQuery,\n} from \"./rural-route\";\n\nexport {\n Places,\n PlacesError,\n type Place,\n type PlacesResponse,\n type PlacesOptions,\n type PlaceSearchOptions,\n type PlaceNearbyOptions,\n} from \"./places\";\n","/**\n * Framework-free port of Township Canada's legal-land regex grammar\n * (`api/packages/shared/regex_rules.js` + `helpers.processInput`).\n *\n * A successful parse is a structured candidate, not a geocoder hit. Coordinates,\n * civic addresses, and place names return null — those stay on the civic path.\n */\n\nexport type LegalLandSystem = \"DLS\" | \"NTS\" | \"GTS\" | \"FPS\" | \"RIVER_LOT\";\n\nexport type LegalLandUnit =\n | \"lsd\"\n | \"qtr\"\n | \"half\"\n | \"sec\"\n | \"twp\"\n | \"nts\"\n | \"nts_sheet\"\n | \"lot\"\n | \"con\"\n | \"grid\"\n | \"section\"\n | \"unit\"\n | \"river_lot\";\n\nexport type LegalLandComponents = {\n meridian?: string;\n range?: string;\n township?: string;\n section?: string;\n quarter?: string;\n half?: string;\n lsd?: string;\n quarterUnit?: string;\n ntsUnit?: string;\n block?: string;\n series?: string;\n mapArea?: string;\n mapSheet?: string;\n fpsUnit?: string;\n fpsSection?: string;\n latDeg?: string;\n latMin?: string;\n lonDeg?: string;\n lonMin?: string;\n lot?: string;\n concession?: string;\n townshipName?: string;\n kind?: string;\n parts?: string[];\n};\n\nexport type LegalLandParse = {\n system: LegalLandSystem;\n canonical: string;\n unit: LegalLandUnit;\n components: LegalLandComponents;\n province?: string;\n};\n\nconst SEP = String.raw`(?:\\s*-\\s*|\\s+)`;\nconst SEP_OPT = String.raw`(?:\\s*-\\s*|\\s+)?`;\n\nconst LSD_N = String.raw`(?<lsd>0?[1-9]|1[0-6])`;\nconst SEC_N = String.raw`(?<section>(?:0?[1-9]|[1-2][0-9]|3[0-6])a?)`;\nconst TWP_N = String.raw`(?<township>(?:(?:00|0)?[1-9]|0?[1-9][0-9]|1[0-2][0-9])a?)`;\nconst RGE_N = String.raw`(?<range>(?:0?[1-9]|[1-8][0-9]|9[0-3])a?)`;\nconst MER_N = String.raw`(?<meridian>[Ww][1-6]|[Ee][1-2])[Mm]?`;\n\nconst DLS_LSD = new RegExp(`^${LSD_N}${SEP}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_SEC = new RegExp(`^${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_TWP = new RegExp(`^${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`, \"i\");\nconst DLS_QTR_MATCH = new RegExp(`^([ns][ew])(1/4)?(?:\\\\s*-\\\\s*|\\\\s+).*$`, \"i\");\nconst DLS_QTR = new RegExp(\n `^(?<quarter>[ns][ew])(?:[\\\\s-]*1/4)?${SEP_OPT}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`,\n \"i\",\n);\nconst DLS_HALF_MATCH = new RegExp(`^([nsew])(1/2)?(?:\\\\s*-\\\\s*|\\\\s+).*$`, \"i\");\nconst DLS_HALF = new RegExp(\n `^(?<half>[nsew])(?:[\\\\s-]*1/2)?${SEP_OPT}${SEC_N}${SEP}${TWP_N}${SEP}${RGE_N}${SEP_OPT}${MER_N}$`,\n \"i\",\n);\n\nconst NTS = new RegExp(\n `^(?<quarterUnit>[abcd])${SEP_OPT}(?<ntsUnit>(?:00|0)?[1-9]|0?[1-9][0-9]|100)${SEP_OPT}(?<block>[a-l])\\\\s*/?\\\\s*(?<series>0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])${SEP_OPT}(?<mapArea>[a-p])${SEP_OPT}(?<mapSheet>0?[1-9]|1[0-6])$`,\n \"i\",\n);\nconst NTS_SHEET = new RegExp(\n `^(?<series>0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])(?<mapArea>[a-p])\\\\s*/?\\\\s*(?<mapSheet>0?[1-9]|1[0-6])$`,\n \"i\",\n);\n\nconst FPS_UNIT = /^([a-p])\\s+(\\d{1,2})\\s+(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\nconst FPS_SECTION = /^(\\d{1,2})\\s+(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\nconst FPS_GRID = /^(\\d{2})-(\\d{2})\\s+n\\s+(\\d{1,3})-(\\d{1,3})\\s+w$/i;\n\nconst ON_LOT = /^lot\\s+(\\d+)(?:\\s+(.*))?$/i;\nconst ON_CON = /^(?:con|concession)\\s+(\\d+)\\s+([\\w ]+)$/i;\nconst ON_CON_IN_REST = /^(?:con|concession)\\s+(\\d+)\\s*(.*)$/i;\nconst ON_LOT_IN_REST = /^lot\\s+(\\d+)\\s*(.*)$/i;\n\nconst RIVER = /^(pl|rl|wl|lx|sl|ot)(?:[\\s-]+[a-z0-9]+)+$/i;\n\nfunction sanitize(q: string): string {\n return q.trim().replace(/['\"]/g, \"\").replace(/[;\\\\]/g, \"\");\n}\n\n/** Users often glue the quarter/half onto the section: `NW25-24-1-W5`. Leave `N1/2` / `NW1/4` intact. */\nfunction insertDirectionSeparator(q: string): string {\n return q.replace(/^([NS][EW]|[NSEW])(?!1\\/[24]\\b)(\\d)/i, \"$1-$2\");\n}\n\nfunction cleanPart(value: string): string {\n return value.replace(/^0+(?=\\d)/, \"\").replace(/\\/0+/, \"/\").toUpperCase();\n}\n\nfunction dlsProvince(meridian: string): string | undefined {\n const m = meridian.toUpperCase();\n if (m === \"W5\") return \"AB\";\n if (m === \"W1\" || m === \"E1\" || m === \"E2\") return \"MB\";\n return undefined;\n}\n\nfunction titleCase(value: string): string {\n return value.replace(/\\w\\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());\n}\n\nfunction groups(re: RegExp, q: string): Record<string, string> | null {\n const match = re.exec(q);\n if (!match?.groups) return null;\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(match.groups)) {\n if (value === undefined || value === \"\") continue;\n out[key] = cleanPart(value);\n }\n return out;\n}\n\nfunction dlsFrom(unit: \"lsd\" | \"qtr\" | \"half\" | \"sec\" | \"twp\", g: Record<string, string>): LegalLandParse {\n const meridian = g.meridian!;\n const components: LegalLandComponents = { meridian, range: g.range, township: g.township };\n const pieces: string[] = [];\n if (unit === \"lsd\") {\n components.lsd = g.lsd;\n components.section = g.section;\n pieces.push(g.lsd!, g.section!);\n } else if (unit === \"qtr\") {\n components.quarter = g.quarter;\n components.section = g.section;\n pieces.push(g.quarter!, g.section!);\n } else if (unit === \"half\") {\n components.half = g.half;\n components.section = g.section;\n pieces.push(g.half!, g.section!);\n } else if (unit === \"sec\") {\n components.section = g.section;\n pieces.push(g.section!);\n }\n pieces.push(g.township!, g.range!, meridian);\n const parsed: LegalLandParse = {\n system: \"DLS\",\n canonical: pieces.join(\"-\"),\n unit,\n components,\n };\n const province = dlsProvince(meridian);\n if (province) parsed.province = province;\n return parsed;\n}\n\nfunction parseDls(q: string): LegalLandParse | null {\n const candidate = insertDirectionSeparator(q);\n const lsd = groups(DLS_LSD, candidate);\n if (lsd) return dlsFrom(\"lsd\", lsd);\n if (DLS_QTR_MATCH.test(candidate)) {\n const qtr = groups(DLS_QTR, candidate);\n if (qtr) return dlsFrom(\"qtr\", qtr);\n }\n if (DLS_HALF_MATCH.test(candidate)) {\n const half = groups(DLS_HALF, candidate);\n if (half) return dlsFrom(\"half\", half);\n }\n const sec = groups(DLS_SEC, candidate);\n if (sec) return dlsFrom(\"sec\", sec);\n const twp = groups(DLS_TWP, candidate);\n if (twp) return dlsFrom(\"twp\", twp);\n return null;\n}\n\nfunction parseNts(q: string): LegalLandParse | null {\n const full = groups(NTS, q);\n if (full) {\n return {\n system: \"NTS\",\n canonical: `${full.quarterUnit}-${full.ntsUnit}-${full.block}/${full.series}-${full.mapArea}-${full.mapSheet}`,\n unit: \"nts\",\n components: {\n quarterUnit: full.quarterUnit,\n ntsUnit: full.ntsUnit,\n block: full.block,\n series: full.series,\n mapArea: full.mapArea,\n mapSheet: full.mapSheet,\n },\n };\n }\n const sheet = groups(NTS_SHEET, q);\n if (!sheet) return null;\n return {\n system: \"NTS\",\n canonical: `${sheet.series}${sheet.mapArea}/${sheet.mapSheet}`,\n unit: \"nts_sheet\",\n components: {\n series: sheet.series,\n mapArea: sheet.mapArea,\n mapSheet: sheet.mapSheet,\n },\n };\n}\n\nfunction parseFps(q: string): LegalLandParse | null {\n const unit = FPS_UNIT.exec(q);\n if (unit) {\n const components = {\n fpsUnit: unit[1]!.toUpperCase(),\n fpsSection: unit[2]!,\n latDeg: unit[3]!,\n latMin: unit[4]!,\n lonDeg: unit[5]!,\n lonMin: unit[6]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.fpsUnit} ${components.fpsSection} ${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"unit\",\n components,\n };\n }\n const section = FPS_SECTION.exec(q);\n if (section) {\n const components = {\n fpsSection: section[1]!,\n latDeg: section[2]!,\n latMin: section[3]!,\n lonDeg: section[4]!,\n lonMin: section[5]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.fpsSection} ${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"section\",\n components,\n };\n }\n const grid = FPS_GRID.exec(q);\n if (!grid) return null;\n const components = {\n latDeg: grid[1]!,\n latMin: grid[2]!,\n lonDeg: grid[3]!,\n lonMin: grid[4]!,\n };\n return {\n system: \"FPS\",\n canonical: `${components.latDeg}-${components.latMin} N ${components.lonDeg}-${components.lonMin} W`,\n unit: \"grid\",\n components,\n };\n}\n\nfunction parseGts(q: string): LegalLandParse | null {\n const lot = ON_LOT.exec(q);\n if (lot) {\n const components: LegalLandComponents = { lot: cleanPart(lot[1]!) };\n const rest = (lot[2] ?? \"\").trim();\n const con = ON_CON_IN_REST.exec(rest);\n const nameBits: string[] = [];\n if (con) {\n components.concession = cleanPart(con[1]!);\n const after = (con[2] ?? \"\").trim();\n if (after) components.townshipName = titleCase(after);\n nameBits.push(`Con ${components.concession}`);\n if (components.townshipName) nameBits.push(components.townshipName);\n } else if (rest) {\n components.townshipName = titleCase(rest);\n nameBits.push(components.townshipName);\n }\n return {\n system: \"GTS\",\n canonical: [`Lot ${components.lot}`, ...nameBits].join(\" \"),\n unit: \"lot\",\n components,\n province: \"ON\",\n };\n }\n const con = ON_CON.exec(q);\n if (!con) return null;\n const components: LegalLandComponents = { concession: cleanPart(con[1]!) };\n const rest = con[2]!.trim();\n const nestedLot = ON_LOT_IN_REST.exec(rest);\n if (nestedLot) {\n components.lot = cleanPart(nestedLot[1]!);\n const after = (nestedLot[2] ?? \"\").trim();\n if (after) components.townshipName = titleCase(after);\n } else {\n components.townshipName = titleCase(rest);\n }\n const tail = [\n components.lot ? `Lot ${components.lot}` : \"\",\n components.townshipName ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n return {\n system: \"GTS\",\n canonical: [`Con ${components.concession}`, tail].filter(Boolean).join(\" \"),\n unit: components.lot ? \"lot\" : \"con\",\n components,\n province: \"ON\",\n };\n}\n\nfunction parseRiverLot(q: string): LegalLandParse | null {\n if (!RIVER.test(q)) return null;\n const tokens = q.split(/[\\s-]+/).filter(Boolean).map((t) => t.toUpperCase());\n const kind = tokens[0]!;\n const parts = tokens.slice(1);\n return {\n system: \"RIVER_LOT\",\n canonical: tokens.join(\"-\"),\n unit: \"river_lot\",\n components: { kind, parts },\n province: \"MB\",\n };\n}\n\nexport function parseLegalLand(q: string): LegalLandParse | null {\n if (typeof q !== \"string\") return null;\n const raw = sanitize(q);\n if (!raw) return null;\n\n return parseFps(raw) ?? parseGts(raw) ?? parseRiverLot(raw) ?? parseDls(raw) ?? parseNts(raw);\n}\n\n/** Public `legal_land.system` values on a geocode result (LOCATION-MODEL). */\nexport type LegalLandLayerSystem = \"dls\" | \"nts\" | \"fps\" | \"on\" | \"river_lot\";\n\nconst LAYER_SYSTEM: Record<LegalLandSystem, LegalLandLayerSystem> = {\n DLS: \"dls\",\n NTS: \"nts\",\n GTS: \"on\",\n FPS: \"fps\",\n RIVER_LOT: \"river_lot\",\n};\n\n/** True when `q` is a well-formed legal-land description, not a civic query. */\nexport function looksLikeLegalLand(q: string): boolean {\n return parseLegalLand(q) != null;\n}\n\nexport interface LegalLandDetails {\n system: LegalLandLayerSystem;\n province?: string;\n canonical: string;\n unit: LegalLandUnit;\n components: LegalLandComponents;\n}\n\n/** Map a parse onto the public result block. Does not invent coordinates. */\nexport function toLegalLandDetails(parsed: LegalLandParse): LegalLandDetails {\n return {\n system: LAYER_SYSTEM[parsed.system],\n ...(parsed.province ? { province: parsed.province } : {}),\n canonical: parsed.canonical,\n unit: parsed.unit,\n components: parsed.components,\n };\n}\n","/**\n * Energy-query detect for wells / facilities.\n *\n * UWI grammars are Township Canada's `web/server/utils/regex.js` (`uwi`, `bc_uwi`).\n * Well licences are only treated as energy when they carry a clear prefix (`W`,\n * `WL`, `LIC` / `LICENCE` / `LICENSE`). Bare digits stay civic.\n * Facility names are too generic: they are energy only when the caller asks\n * (`?layers=well|facility` or `profile`/`source=energy`).\n */\n\nexport const ENERGY_SEARCH_LAYERS = [\"well\", \"facility\"] as const;\nexport type EnergySearchLayer = (typeof ENERGY_SEARCH_LAYERS)[number];\n\nexport type EnergyQueryKind = \"uwi\" | \"licence\";\n\nexport interface EnergyQuery {\n kind: EnergyQueryKind;\n /** Trimmed original. */\n raw: string;\n /** Lookup key: UWI uppercased, or licence digits. */\n key: string;\n}\n\n/**\n * AER ST37 (`00/…`, `F1/…`) and Petrinex/IHS (`102/…`) DLS UWI.\n * Format: LocationExceptionCode/LSD-Section-Township-Range-Meridian/EventSequence\n */\nconst DLS_UWI =\n /^\\s*(?:[A-Z0-9]{2}|\\d{3})\\/(1[0-6]|0?[1-9])-(3[0-6]|[12][0-9]|0?[1-9])-(1[0-2][0-9]|0?[1-9][0-9]|00[1-9])-(([1-8][0-9]|9[0-3]|0?[1-9])\\s*(w[1-6]|e[1-2]|[1-6]))\\s*m?\\/?(\\w{1,2})?\\s*$/i;\n\n/**\n * BC NTS UWI: `200/B-039-H/094-B-08/00`\n * Format: LocationExceptionCode/Unit-Block-Quadrant/MapArea-Group-Sheet/EventSequence\n */\nconst BC_UWI =\n /^\\s*\\d{3}\\/(a|b|c|d)(\\s*-\\s*|\\s+)?((00|0)?[1-9]|0?[1-9][0-9]|100)(\\s*-\\s*|\\s+)?(a|b|c|d|e|f|g|h|i|j|k|l)\\s*?\\/\\s*(0?8[2-9]|0?9[0-9]|10[0-9]|11[0-4])(\\s*-\\s*|\\s+)?(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p)(\\s*-\\s*|\\s+)?(1[0-6]|0?[1-9])\\/\\d{1,2}\\s*$/i;\n\n/** Prefixed well licence. Bare 5–7 digit strings are not distinctive enough. */\nconst WELL_LICENCE = /^\\s*(?:w|wl|lic(?:\\.|ence|ense)?)\\s*[-#]?\\s*(\\d{5,7})\\s*$/i;\n\nexport function parseEnergyQuery(q: string): EnergyQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (!raw) return null;\n if (BC_UWI.test(raw) || DLS_UWI.test(raw)) {\n return { kind: \"uwi\", raw, key: raw.replace(/\\s+/g, \"\").toUpperCase() };\n }\n const licence = WELL_LICENCE.exec(raw);\n if (licence?.[1]) {\n return { kind: \"licence\", raw, key: licence[1] };\n }\n return null;\n}\n\n/** True when `q` is a UWI or a prefixed well licence, not a civic address. */\nexport function looksLikeEnergyQuery(q: string): boolean {\n return parseEnergyQuery(q) != null;\n}\n\nexport function parseEnergySearchLayers(raw: string | undefined): EnergySearchLayer[] {\n if (!raw) return [];\n const out: EnergySearchLayer[] = [];\n const seen = new Set<EnergySearchLayer>();\n for (const token of raw.split(\",\")) {\n const id = token.trim();\n if (id === \"well\" || id === \"facility\") {\n if (!seen.has(id)) {\n seen.add(id);\n out.push(id);\n }\n }\n }\n return out;\n}\n\nexport function isEnergySearchSource(raw: string | undefined): boolean {\n return raw?.trim() === \"energy\";\n}\n\n/**\n * Whether this search should take the energy branch (addon gate + table lookup).\n * Civic strings stay civic unless the caller asked for energy layers/source.\n */\nexport function energySearchRequested(\n q: string,\n opts: { layers?: string; profile?: string; source?: string } = {},\n): boolean {\n if (looksLikeEnergyQuery(q)) return true;\n if (parseEnergySearchLayers(opts.layers).length > 0) return true;\n return isEnergySearchSource(opts.profile) || isEnergySearchSource(opts.source);\n}\n\nexport interface EnergyDetails {\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n}\n","/**\n * Point-in-polygon identify: which catalog features contain a coordinate.\n *\n * Identify-capable layers are a subset of the gateway catalog. `parks`,\n * `rail`, `aboriginal-lands`, `energy.ccs`, and `energy.geothermal` are\n * catalogued for tiles/overlays but have no identify table, so they 400 here\n * rather than return a silent miss. Energy tables are empty-safe: a missing\n * relation is `[]` for that layer, not a 500.\n */\n\nexport const LAND_IDENTIFY_LAYER_IDS = [\n \"municipalities\",\n \"survey.dls\",\n \"survey.nts\",\n \"survey.parcels\",\n] as const;\n\nexport const ENERGY_IDENTIFY_LAYER_IDS = [\n \"energy.wells\",\n \"energy.pipelines\",\n \"energy.facilities\",\n] as const;\n\n/** Routed through the Land container as extra tables. Not a fourth container. */\nexport const MINING_IDENTIFY_LAYER_IDS = [\n \"mining.occurrences\",\n \"mining.prospectivity\",\n \"mining.sk-potential\",\n] as const;\n\nexport const IDENTIFY_LAYER_IDS = [\n ...LAND_IDENTIFY_LAYER_IDS,\n ...ENERGY_IDENTIFY_LAYER_IDS,\n \"energy.fields\",\n \"energy.petroleum-tenure\",\n \"energy.mineral-tenure\",\n ...MINING_IDENTIFY_LAYER_IDS,\n] as const;\n\nexport type IdentifyLayerId = (typeof IDENTIFY_LAYER_IDS)[number];\n\n/** Default when `layers` is omitted: core only, so an account key without add-ons can identify. */\nexport const DEFAULT_IDENTIFY_LAYERS: readonly IdentifyLayerId[] = [\"municipalities\"];\n\nexport function isIdentifyLayer(id: string): id is IdentifyLayerId {\n return (IDENTIFY_LAYER_IDS as readonly string[]).includes(id);\n}\n\nexport function isLandIdentifyLayer(id: IdentifyLayerId): boolean {\n return (\n (LAND_IDENTIFY_LAYER_IDS as readonly string[]).includes(id) ||\n (MINING_IDENTIFY_LAYER_IDS as readonly string[]).includes(id)\n );\n}\n\nexport function isEnergyIdentifyLayer(id: IdentifyLayerId): boolean {\n return id.startsWith(\"energy.\");\n}\n\nexport function knownIdentifyLayers(): string {\n return IDENTIFY_LAYER_IDS.join(\", \");\n}\n\nexport function parseIdentifyLayers(raw: string | undefined): { layers: IdentifyLayerId[] } | { error: string } {\n const ids = (raw ?? \"\")\n .split(\",\")\n .map((id) => id.trim())\n .filter((id) => id.length > 0);\n if (ids.length === 0) {\n return { layers: [...DEFAULT_IDENTIFY_LAYERS] };\n }\n const layers: IdentifyLayerId[] = [];\n const seen = new Set<string>();\n for (const id of ids) {\n if (!isIdentifyLayer(id)) {\n return { error: `unknown identify layer \"${id}\". known: ${knownIdentifyLayers()}` };\n }\n if (!seen.has(id)) {\n seen.add(id);\n layers.push(id);\n }\n }\n return { layers };\n}\n\nexport function parseIdentifyAt(\n at: string | undefined,\n lon: string | undefined,\n lat: string | undefined,\n): { at: [number, number] } | { error: string } {\n const rawAt = at?.trim();\n if (rawAt) {\n const pieces = rawAt.split(\",\");\n if (pieces.length !== 2 || pieces.some((p) => p.trim() === \"\")) {\n return { error: \"at must be lng,lat\" };\n }\n const lng = Number(pieces[0]);\n const latN = Number(pieces[1]);\n if (!Number.isFinite(lng) || !Number.isFinite(latN)) {\n return { error: \"at must be lng,lat\" };\n }\n return { at: [lng, latN] };\n }\n if (lon != null && lon !== \"\" && lat != null && lat !== \"\") {\n const lng = Number(lon);\n const latN = Number(lat);\n if (!Number.isFinite(lng) || !Number.isFinite(latN)) {\n return { error: \"lon and lat must be numbers\" };\n }\n return { at: [lng, latN] };\n }\n return { error: \"at (lng,lat) or lon and lat required\" };\n}\n\nexport interface IdentifyOptions {\n /** `[lng, lat]` in WGS84 degrees. */\n at: [number, number];\n /** Identify-capable catalog ids. Default `municipalities`. Survey layers need `legal-land`. Energy layers need `energy`. `survey.parcels` needs `agriculture`. Mining layers need `mining`. */\n layers?: readonly string[];\n /** Include polygons when the row has one. */\n geometry?: boolean;\n signal?: AbortSignal;\n}\n\nexport interface IdentifyResult {\n layer: IdentifyLayerId;\n id: string;\n name: string;\n province?: string;\n unit?: string;\n survey_system?: string;\n uwi?: string;\n licence?: string;\n operator?: string;\n status?: string;\n pid?: string;\n geometry?: {\n type: \"Polygon\" | \"MultiPolygon\";\n coordinates: number[][][] | number[][][][];\n };\n}\n\nexport interface IdentifyResponse {\n at: [number, number];\n results: IdentifyResult[];\n}\n","/**\n * RFC 4180 CSV, streaming, with the Canadian-address-file details that break naive splitters.\n *\n * Hand-written rather than a dependency for two reasons that both matter here. `@unmap/cli` is\n * deliberately a zero-runtime-dependency Node tool, and the hosted batch consumer is a\n * Cloudflare Worker — one codec that runs in both, with no bundler shims, is worth the ~150\n * lines. The other reason is that the failure modes below are the whole job: a parser that gets\n * them wrong corrupts a customer's file silently, which is worse than refusing it.\n *\n * What it handles, and why each one is here:\n *\n * - **A UTF-8 BOM.** Excel writes one. Left in place it becomes part of the first header name,\n * so `--query-column address` fails to find `address` and the error blames the user.\n * - **Quoted commas and quoted newlines.** \"Gatineau, QC\" and a multi-line note field are\n * ordinary in address exports. A record can therefore span several lines, which is why a\n * hosted job cannot chunk a CSV at arbitrary newlines.\n * - **Escaped quotes** (`\"\"` inside a quoted field).\n * - **CRLF, LF and a lone CR** as record separators, mixed within one file.\n * - **Leading zeros** — postal codes and civic numbers. Everything stays a string, always.\n * - **Ragged rows.** A row with fewer or more fields than the header is a fact about the file,\n * not a crash: it is reported as an error for that row and the rest of the file continues.\n *\n * The parser is a state machine over chunks, so a 50 MiB file never has to be a 50 MiB string.\n */\n\nexport interface CsvRecord {\n /** 1-based index among data records, excluding the header. Stable across resumes. */\n index: number;\n /** Field values in header order, exactly as they appeared. Never coerced. */\n values: string[];\n}\n\nexport interface CsvProblem {\n index: number;\n code: \"too_few_fields\" | \"too_many_fields\";\n message: string;\n}\n\nconst QUOTE = 34; // \"\nconst COMMA = 44; // ,\nconst CR = 13;\nconst LF = 10;\n\n/**\n * Incremental parser. Feed it strings; it yields complete records.\n *\n * Deliberately not an async iterator: the caller owns the read loop, which is what lets the CLI\n * and the Worker use their own very different stream APIs over the same state machine.\n */\nexport class CsvParser {\n private field = \"\";\n private row: string[] = [];\n private inQuotes = false;\n /** A quote seen inside a quoted field: either an escape or the close. */\n private pendingQuote = false;\n /** A CR seen outside quotes: end of record, and a following LF is part of the same break. */\n private pendingCr = false;\n private started = false;\n private rowHasContent = false;\n\n /**\n * Feed one chunk. Returns the records completed by it.\n *\n * A record that ends exactly at a chunk boundary is returned here; a partial one is held until\n * the next chunk, or until `end()`.\n */\n write(chunk: string): string[][] {\n const out: string[][] = [];\n // The BOM is only a BOM at the very start of the stream. Anywhere else U+FEFF is a\n // zero-width no-break space and part of somebody's data.\n if (!this.started) {\n this.started = true;\n if (chunk.charCodeAt(0) === 0xfeff) chunk = chunk.slice(1);\n }\n\n for (let i = 0; i < chunk.length; i++) {\n const ch = chunk.charCodeAt(i);\n\n if (this.pendingCr) {\n this.pendingCr = false;\n // CRLF is one break; a lone CR was already the break, so an LF here is consumed.\n if (ch === LF) continue;\n }\n\n if (this.inQuotes) {\n if (this.pendingQuote) {\n this.pendingQuote = false;\n if (ch === QUOTE) {\n this.field += '\"';\n continue;\n }\n this.inQuotes = false;\n // Fall through: this character closes the field or the record.\n } else if (ch === QUOTE) {\n this.pendingQuote = true;\n continue;\n } else {\n this.field += chunk[i];\n continue;\n }\n }\n\n if (ch === QUOTE && this.field === \"\") {\n this.inQuotes = true;\n this.rowHasContent = true;\n continue;\n }\n if (ch === COMMA) {\n this.row.push(this.field);\n this.field = \"\";\n this.rowHasContent = true;\n continue;\n }\n if (ch === CR) {\n this.pendingCr = true;\n out.push(this.finishRow());\n continue;\n }\n if (ch === LF) {\n out.push(this.finishRow());\n continue;\n }\n this.field += chunk[i];\n this.rowHasContent = true;\n }\n return out.filter((r) => r !== EMPTY);\n }\n\n /** Flush a final record with no trailing newline. */\n end(): string[][] {\n if (this.inQuotes && this.pendingQuote) this.inQuotes = false;\n if (this.field !== \"\" || this.row.length > 0 || this.rowHasContent) {\n const row = this.finishRow();\n return row === EMPTY ? [] : [row];\n }\n return [];\n }\n\n /** True when the stream ended inside an unterminated quoted field. */\n get unterminated(): boolean {\n return this.inQuotes && !this.pendingQuote;\n }\n\n private finishRow(): string[] {\n this.row.push(this.field);\n const row = this.row;\n this.field = \"\";\n this.row = [];\n const hadContent = this.rowHasContent;\n this.rowHasContent = false;\n // A blank line between records is whitespace, not a row of one empty field. A line that is\n // genuinely one empty QUOTED field (\"\") has rowHasContent set and survives.\n if (!hadContent && row.length === 1 && row[0] === \"\") return EMPTY;\n return row;\n }\n}\n\n/** Sentinel for \"this line was blank\", distinguishable from a real one-empty-field row. */\nconst EMPTY: string[] = [];\n\n/**\n * Align a raw row against the header.\n *\n * Ragged rows are reported, not repaired and not thrown: a short row is padded so the caller can\n * still carry its original values into the output, and a long row keeps its extra fields for the\n * same reason. Discarding either would lose customer data in a file we were only asked to\n * annotate.\n */\nexport function alignRow(\n header: readonly string[],\n values: readonly string[],\n index: number,\n): { record: CsvRecord; problem?: CsvProblem } {\n if (values.length === header.length) return { record: { index, values: [...values] } };\n if (values.length < header.length) {\n return {\n record: { index, values: [...values, ...Array(header.length - values.length).fill(\"\")] },\n problem: {\n index,\n code: \"too_few_fields\",\n message: `row ${index} has ${values.length} fields, header has ${header.length}`,\n },\n };\n }\n return {\n record: { index, values: [...values] },\n problem: {\n index,\n code: \"too_many_fields\",\n message: `row ${index} has ${values.length} fields, header has ${header.length}`,\n },\n };\n}\n\n/**\n * Quote a value for output: only when it has to be, and always the same way.\n *\n * The tab is in the set even though RFC 4180 does not require it. It is there for\n * `escapeForSpreadsheet` below, which neutralises a formula by prefixing one: unquoted, a\n * leading tab is read inconsistently across spreadsheet apps, and quoted it is unambiguous\n * everywhere. A tab anywhere else in a field is rare enough that quoting it costs nothing.\n */\nexport function encodeField(value: string): string {\n if (value === \"\") return \"\";\n if (/[\",\\r\\n\\t]/.test(value)) return `\"${value.replace(/\"/g, '\"\"')}\"`;\n return value;\n}\n\n/**\n * Whether a value would be read as a formula by a spreadsheet.\n *\n * Excel, Sheets and LibreOffice all evaluate a cell beginning `=`, `+`, `-`, `@`, and CSV is the\n * classic delivery vehicle for that (CSV injection). It matters here because a geocoder's output\n * is a file people open in a spreadsheet, and because Canadian address data really does contain\n * values that start this way — a negative latitude, \"-123 Main St\" in a malformed row.\n *\n * Detection is separated from mitigation on purpose: `escapeForSpreadsheet` prefixes a tab so\n * the cell displays as text, and the caller decides whether to apply it, because prefixing\n * changes the bytes and a pipeline reading the file programmatically wants the original.\n */\nexport function looksLikeFormula(value: string): boolean {\n return /^[=+\\-@\\t\\r]/.test(value);\n}\n\nexport function escapeForSpreadsheet(value: string): string {\n return looksLikeFormula(value) ? `\\t${value}` : value;\n}\n\n/** One output line, terminated with CRLF (what RFC 4180 specifies and Excel expects). */\nexport function encodeRow(values: readonly string[], opts: { spreadsheetSafe?: boolean } = {}): string {\n const prepared = opts.spreadsheetSafe ? values.map(escapeForSpreadsheet) : values;\n return prepared.map(encodeField).join(\",\") + \"\\r\\n\";\n}\n","import type { GeocodeMatchReason, GeocodeResult } from \"../index\";\n\n/**\n * The output schema a batch geocode writes, shared by the local CLI and the hosted service.\n *\n * One schema, one place, because the two are meant to be interchangeable: a customer who\n * prototypes with `unmap geocode csv` and then moves the job to the dashboard should get the\n * same columns in the same order with the same meanings, and diffing the two files should show\n * nothing but the coordinates.\n */\n\n/** Prefix on every column this adds, so a customer's own `status` column is never shadowed. */\nexport const OUTPUT_PREFIX = \"unmap_\";\n\n/**\n * What happened to one input row. Distinct from `match_type`, which is about the QUALITY of an\n * answer — a row can have `status: \"matched\"` and `match_type: \"fallback\"`, and that pairing is\n * the common case worth reviewing.\n */\nexport type RowStatus =\n /** A result came back and is good enough to use without looking at it. */\n | \"matched\"\n /** A result came back, but something about it should be looked at. See `unmap_review`. */\n | \"review\"\n /** The query ran and the corpus has nothing. Not an error, and it is billed. */\n | \"no_match\"\n /** The row never became a query: no address to send. Not billed. */\n | \"invalid\"\n /** The query ran and failed permanently (bad credentials, exhausted budget, repeated 5xx). */\n | \"failed\"\n /** Not attempted: the run was cancelled, paused or interrupted before reaching this row. */\n | \"unprocessed\";\n\n/**\n * Why a row is in the review file. Row-level, and deliberately a superset of the result-level\n * `match_reasons`: two of these are about the SHAPE of the answer rather than its content.\n */\nexport type ReviewReason =\n | GeocodeMatchReason\n /** The result is coarser than the row asked for. */\n | \"fallback_match\"\n /** More than one candidate scored closely; the top one may not be the intended one. */\n | \"ambiguous\"\n /** Nothing came back at all. */\n | \"no_match\"\n /** The row could not be turned into a query. */\n | \"invalid_input\";\n\nexport const OUTPUT_COLUMNS = [\n \"unmap_status\",\n \"unmap_id\",\n \"unmap_name\",\n \"unmap_lng\",\n \"unmap_lat\",\n \"unmap_layer\",\n \"unmap_source\",\n \"unmap_match_type\",\n \"unmap_precision\",\n \"unmap_review\",\n] as const;\n\nexport type OutputColumn = (typeof OUTPUT_COLUMNS)[number];\n\n/**\n * Resolve a collision between the columns this writes and the customer's own header.\n *\n * Silently overwriting a column called `unmap_status` would destroy data; refusing the file\n * outright would strand anyone who ran the tool twice on its own output, which is exactly what\n * people do. So a collision is suffixed (`unmap_status_2`) and reported, and the caller decides\n * whether to say something. Comparison is case-insensitive because spreadsheets are.\n */\nexport function resolveOutputHeader(\n inputHeader: readonly string[],\n): { header: string[]; renamed: Array<{ column: OutputColumn; to: string }> } {\n const taken = new Set(inputHeader.map((h) => h.trim().toLowerCase()));\n const renamed: Array<{ column: OutputColumn; to: string }> = [];\n const header = [...inputHeader];\n\n for (const column of OUTPUT_COLUMNS) {\n let name: string = column;\n if (taken.has(name.toLowerCase())) {\n let n = 2;\n while (taken.has(`${column}_${n}`.toLowerCase())) n++;\n name = `${column}_${n}`;\n renamed.push({ column, to: name });\n }\n taken.add(name.toLowerCase());\n header.push(name);\n }\n return { header, renamed };\n}\n\n/**\n * When the top result is close enough to the runner-up that picking it is a coin flip.\n *\n * Ratio, not a difference: BM25 scores are not on an absolute scale and are not comparable\n * across queries, so \"within 5%\" means something for every query while \"within 2 points\" means\n * something only for the query it was tuned on. 0.95 is deliberately tight — this flags a row\n * for a human to look at, and a review file that flags a third of the rows gets ignored whole.\n */\nexport const AMBIGUOUS_SCORE_RATIO = 0.95;\n\n/**\n * Classify one row's outcome from what the API returned.\n *\n * The rule the whole review file rests on: **nothing is silently discarded.** Every input row\n * gets exactly one record in the main output, and every row that is not plainly usable gets a\n * reason here and a line in the review file. A row that \"failed quietly\" is the outcome this\n * function exists to make impossible.\n */\nexport function classify(results: readonly GeocodeResult[]): { status: RowStatus; reasons: ReviewReason[] } {\n if (results.length === 0) return { status: \"no_match\", reasons: [\"no_match\"] };\n\n const [top, second] = results;\n const reasons: ReviewReason[] = [];\n\n if (top!.match_type === \"fallback\") reasons.push(\"fallback_match\");\n for (const r of top!.match_reasons ?? []) reasons.push(r);\n\n if (\n second &&\n typeof top!.score === \"number\" &&\n typeof second.score === \"number\" &&\n top!.score > 0 &&\n second.score / top!.score >= AMBIGUOUS_SCORE_RATIO\n ) {\n reasons.push(\"ambiguous\");\n }\n\n return { status: reasons.length ? \"review\" : \"matched\", reasons };\n}\n\n/** The ten values, in `OUTPUT_COLUMNS` order, for one row. */\nexport function outputValues(\n status: RowStatus,\n reasons: readonly ReviewReason[],\n top: GeocodeResult | undefined,\n): string[] {\n return [\n status,\n top?.id ?? \"\",\n top?.name ?? \"\",\n // Fixed precision rather than the float's own repr: a CSV of coordinates is read by humans\n // and by spreadsheets, and 51.04000000000001 is neither. 7 dp is ~1 cm, well beyond what any\n // source in the corpus actually resolves.\n top ? trimCoordinate(top.lng) : \"\",\n top ? trimCoordinate(top.lat) : \"\",\n top?.layer ?? \"\",\n top?.source ?? \"\",\n top?.match_type ?? \"\",\n top?.precision ?? \"\",\n reasons.join(\" \"),\n ];\n}\n\nfunction trimCoordinate(n: number): string {\n return n.toFixed(7).replace(/\\.?0+$/, \"\");\n}\n","import { ENERGY_SEARCH_LAYERS, type EnergySearchLayer } from \"./energy\";\n\n/**\n * `layers=` on `/geocode/search`, `/geocode/autocomplete` and `/geocode/structured`.\n *\n * The parameter already existed and already meant one thing: `layers=well,facility` sends the\n * query to the energy container instead of the civic one. Civic result types are added to the\n * same parameter rather than to a new one, because from the caller's side they are the same\n * question — \"which kinds of thing may come back\" — and two parameters answering it would have\n * to define what happens when both are set.\n *\n * What the two sets do is genuinely different, and that is why they cannot be mixed:\n * `well`/`facility` select a BACKEND (a different container, a different corpus, gated by the\n * `energy` add-on); the civic values FILTER results inside one corpus. `layers=address,well`\n * has no meaning that is not a guess, so it is a 400 rather than a silent choice.\n *\n * Unknown values are also a 400. That is a tightening: before this, unrecognised tokens were\n * dropped silently, so `layers=adress` searched everything and looked like it had worked.\n */\nexport const CIVIC_SEARCH_LAYERS = [\"address\", \"street\", \"locality\", \"region\", \"poi\"] as const;\nexport type CivicSearchLayer = (typeof CIVIC_SEARCH_LAYERS)[number];\n\nexport type SearchLayersParse =\n | { kind: \"none\" }\n | { kind: \"civic\"; layers: CivicSearchLayer[] }\n | { kind: \"energy\"; layers: EnergySearchLayer[] }\n | { error: string };\n\nconst CIVIC = new Set<string>(CIVIC_SEARCH_LAYERS);\nconst ENERGY = new Set<string>(ENERGY_SEARCH_LAYERS);\n\n/** Every accepted value, for error messages and docs. */\nexport function knownSearchLayers(): string {\n return [...CIVIC_SEARCH_LAYERS, ...ENERGY_SEARCH_LAYERS].join(\", \");\n}\n\nexport function parseSearchLayers(raw: string | undefined): SearchLayersParse {\n if (raw == null || raw.trim() === \"\") return { kind: \"none\" };\n\n const civic: CivicSearchLayer[] = [];\n const energy: EnergySearchLayer[] = [];\n const seen = new Set<string>();\n\n for (const token of raw.split(\",\")) {\n const id = token.trim();\n if (id === \"\") continue;\n if (seen.has(id)) continue;\n seen.add(id);\n if (CIVIC.has(id)) civic.push(id as CivicSearchLayer);\n else if (ENERGY.has(id)) energy.push(id as EnergySearchLayer);\n else return { error: `unknown layer \"${id}\". Known layers: ${knownSearchLayers()}` };\n }\n\n if (civic.length && energy.length) {\n return {\n error:\n `layers cannot mix civic and energy values: ${civic.join(\", \")} filter results within ` +\n `the civic corpus, while ${energy.join(\", \")} select the energy corpus instead. Ask for one or the other.`,\n };\n }\n if (energy.length) return { kind: \"energy\", layers: energy };\n if (civic.length) return { kind: \"civic\", layers: civic };\n return { kind: \"none\" };\n}\n\n/**\n * `focus=lng,lat`: a soft proximity preference.\n *\n * It moves ranking toward the point and excludes nothing — a relevant result on the other side\n * of the country still comes back, behind the near ones. That is the whole distinction from\n * `bbox`, which is a hard filter, and the docs and the UI copy have to keep saying it: \"prefer\n * nearby\" is not \"restrict to this area\".\n */\nexport type FocusParse = { focus: [number, number] } | { error: string } | undefined;\n\nexport function parseFocus(raw: string | undefined): FocusParse {\n if (raw == null || raw.trim() === \"\") return undefined;\n // Each piece must be non-empty before it is a number. `Number(\"\")` is 0, so a trailing comma\n // (\"-114,\") would otherwise parse as latitude 0 — a point in the Gulf of Guinea, silently\n // accepted, quietly ranking everything by its distance from there.\n const pieces = raw.split(\",\").map((s) => s.trim());\n const parts = pieces.map((s) => (s === \"\" ? Number.NaN : Number(s)));\n if (parts.length !== 2 || !parts.every(Number.isFinite)) {\n return { error: \"focus must be lng,lat with two finite numbers\" };\n }\n const [lng, lat] = parts as [number, number];\n if (lng < -180 || lng > 180) return { error: `focus longitude ${lng} is outside [-180, 180]` };\n if (lat < -90 || lat > 90) return { error: `focus latitude ${lat} is outside [-90, 90]` };\n return { focus: [lng, lat] };\n}\n","/**\n * Canada's provinces and territories, every spelling a query or a corpus row may use.\n *\n * Shared rather than duplicated: the geocode container reads it to strip a \"… , AB\" tail off a\n * free-text query (`apps/geocode/src/query-tail.ts`), and the gateway reads it to normalise the\n * `region` field of a structured search. Two copies would drift, and a province the gateway\n * accepts but the container cannot match is a silently empty result.\n *\n * Abbreviations may or may not carry a period (\"Ont\", \"Ont.\", \"Sask\", \"N.W.T.\", \"B.C.\"); the\n * dotted spellings are generated below rather than hand-listed. Bare \"que\" and \"man\" are\n * deliberately absent — both are ordinary words (French words ending in \"que\"; the Isle of Man) —\n * only their dotted forms (\"que.\", \"qué.\") count as an abbreviation.\n */\nconst BASE: Record<string, string> = {\n ab: \"AB\", alberta: \"AB\", alta: \"AB\",\n bc: \"BC\", \"british columbia\": \"BC\", \"colombie-britannique\": \"BC\",\n mb: \"MB\", manitoba: \"MB\",\n nb: \"NB\", \"new brunswick\": \"NB\", \"nouveau-brunswick\": \"NB\",\n nl: \"NL\", \"newfoundland and labrador\": \"NL\", newfoundland: \"NL\", \"terre-neuve-et-labrador\": \"NL\", nfld: \"NL\",\n ns: \"NS\", \"nova scotia\": \"NS\", \"nouvelle-écosse\": \"NS\", \"nouvelle-ecosse\": \"NS\",\n nt: \"NT\", \"northwest territories\": \"NT\", \"territoires du nord-ouest\": \"NT\", nwt: \"NT\",\n nu: \"NU\", nunavut: \"NU\",\n on: \"ON\", ontario: \"ON\", ont: \"ON\",\n pe: \"PE\", pei: \"PE\", \"prince edward island\": \"PE\", \"île-du-prince-édouard\": \"PE\",\n qc: \"QC\", pq: \"QC\", quebec: \"QC\", québec: \"QC\", \"que.\": \"QC\", \"qué.\": \"QC\",\n sk: \"SK\", saskatchewan: \"SK\", sask: \"SK\",\n yt: \"YT\", yukon: \"YT\",\n};\n\n// Dotted spellings of every abbreviation: \"b.c.\", \"n.w.t.\", \"ont.\", \"sask.\", \"alta.\", \"nfld.\".\n// Generated, not hand-listed, so a new abbreviation gets its dotted form for free.\nfor (const [k, v] of Object.entries(BASE)) {\n if (/^[a-z]{2}$/.test(k)) BASE[k[0] + \".\" + k[1] + \".\"] = v;\n else if (k === \"nwt\" || k === \"pei\") BASE[k.split(\"\").join(\".\") + \".\"] = v;\n else if (/^[a-z]{3,4}$/.test(k)) BASE[k + \".\"] = v;\n}\n\n/** Every recognised spelling, lowercase, mapped to its two-letter code. */\nexport const REGIONS: Readonly<Record<string, string>> = BASE;\n\n/**\n * Spellings sorted longest first, so \"newfoundland and labrador\" is tried before\n * \"newfoundland\". Computed after the dotted-form loop, so generated keys sort with the rest.\n */\nexport const REGION_KEYS: readonly string[] = Object.keys(BASE).sort((a, b) => b.length - a.length);\n\n/** The thirteen codes, for validation messages. */\nexport const REGION_CODES: readonly string[] = [...new Set(Object.values(BASE))].sort();\n\n/**\n * Resolve any accepted spelling to its two-letter code. Accent- and case-insensitive, so\n * \"Québec\", \"quebec\" and \"QC\" all give \"QC\". Undefined when nothing matches.\n */\nexport function parseRegion(raw: string | undefined | null): string | undefined {\n if (!raw) return undefined;\n const key = raw.trim().toLowerCase();\n if (REGIONS[key]) return REGIONS[key];\n const unaccented = key.normalize(\"NFD\").replace(/\\p{M}/gu, \"\");\n for (const [k, v] of Object.entries(REGIONS)) {\n if (k.normalize(\"NFD\").replace(/\\p{M}/gu, \"\") === unaccented) return v;\n }\n return undefined;\n}\n\n/**\n * Every spelling the corpus may hold for a region code, uppercased: the code itself plus each\n * name that maps to it. The loaders do not normalise `region` (OpenAddresses passes the source\n * value through, so `ONTARIO` and `ON` both occur), so a filter or boost must match all of them.\n */\nexport function regionForms(code: string): string[] {\n const forms = new Set<string>([code.toUpperCase()]);\n for (const [k, v] of Object.entries(REGIONS)) if (v === code) forms.add(k.toUpperCase());\n return [...forms];\n}\n","import { parseRegion, REGION_CODES } from \"./regions\";\n\n/**\n * Structured forward geocoding: the caller hands over the components of an address instead of\n * one string, and the engine keeps them apart all the way through.\n *\n * The distinction that matters, and the one the docs state plainly: some fields CONSTRAIN the\n * result set and some only RANK it.\n *\n * - `address` and `city` are retrieval and ranking. They decide which rows are candidates and\n * which candidate wins.\n * - `region` and `postalcode` constrain, under one rule: **a supplied component excludes\n * records that contradict it, and never excludes records that are silent about it.** A row\n * in Ontario is out when you asked for Alberta; a row with no province at all stays in and\n * comes back with `region_not_verified` in its `match_reasons`.\n * - `country` is validated, not matched: the corpus is Canada only.\n *\n * The alternative — joining the fields into one string and running the free-text engine — is\n * what this exists instead of. It would make `region` a scoring token that any row could match\n * by coincidence, which is how \"Kelowna, BC\" used to return the province of British Columbia.\n */\nexport interface StructuredQuery {\n /** Street line: civic number and street name, as the caller would write it on an envelope. */\n address?: string;\n city?: string;\n /** Two-letter code, normalised from any accepted spelling (\"Québec\" and \"qc\" both give \"QC\"). */\n region?: string;\n /** Six characters, no space, uppercase (\"M5H2N2\"). */\n postalcode?: string;\n /** Always \"CA\". Present so the field can be supplied and validated, not so it can vary. */\n country?: string;\n}\n\nexport interface StructuredOptions extends StructuredQuery {\n limit?: number;\n lang?: string;\n /** [minLng, minLat, maxLng, maxLat] hard filter, same semantics as `search`. */\n bbox?: [number, number, number, number];\n /** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */\n focus?: [number, number];\n /** Civic result types to allow (`address`, `street`, `locality`, `region`, `poi`). */\n layers?: string;\n signal?: AbortSignal;\n}\n\n/** The query fields, in the order the docs and the CLI list them. */\nexport const STRUCTURED_FIELDS = [\"address\", \"city\", \"region\", \"postalcode\", \"country\"] as const;\n\n/**\n * The fields that can start a search on their own. `region` is deliberately not one of them: a\n * province with no other component is not a geocode, it is a request for four million rows, and\n * answering it with an arbitrary ranked slice would be worse than saying so.\n */\nconst ANCHOR_FIELDS = [\"address\", \"city\", \"postalcode\"] as const;\n\nconst POSTCODE = /^[A-Z]\\d[A-Z]\\d[A-Z]\\d$/;\n\nexport type StructuredParseResult = { query: StructuredQuery } | { error: string };\n\n/** Normalise and validate raw structured input. Returns the cleaned query or one error string. */\nexport function parseStructuredQuery(raw: Partial<Record<string, string | undefined>>): StructuredParseResult {\n const trim = (v: string | undefined) => {\n const t = (v ?? \"\").trim();\n return t === \"\" ? undefined : t;\n };\n\n const country = trim(raw.country);\n if (country && !/^(ca|can|canada)$/i.test(country)) {\n return { error: `country must be CA: unmap covers Canada only (got \"${country}\")` };\n }\n\n let region: string | undefined;\n const rawRegion = trim(raw.region);\n if (rawRegion) {\n region = parseRegion(rawRegion);\n if (!region) {\n return { error: `unknown region \"${rawRegion}\". Use a province or territory code: ${REGION_CODES.join(\", \")}` };\n }\n }\n\n let postalcode: string | undefined;\n const rawPostal = trim(raw.postalcode);\n if (rawPostal) {\n postalcode = rawPostal.replace(/\\s+/g, \"\").toUpperCase();\n if (!POSTCODE.test(postalcode)) {\n return { error: `postalcode must be a Canadian postal code such as M5H 2N2 (got \"${rawPostal}\")` };\n }\n }\n\n const query: StructuredQuery = {\n ...(trim(raw.address) ? { address: trim(raw.address) } : {}),\n ...(trim(raw.city) ? { city: trim(raw.city) } : {}),\n ...(region ? { region } : {}),\n ...(postalcode ? { postalcode } : {}),\n country: \"CA\",\n };\n\n if (!ANCHOR_FIELDS.some((f) => query[f] != null)) {\n return {\n error: `structured search needs at least one of ${ANCHOR_FIELDS.join(\", \")}. ` +\n `region and country narrow a search; they cannot be one on their own.`,\n };\n }\n\n return { query };\n}\n","/**\n * ParcelMap BC PID detect. Grammar matches Township Canada\n * `web/server/api/parcels/search.get.js`: nine digits, optional dashes\n * (`010-867-813` or `010867813`). PIN, plan number, and parcel-name\n * fuzzy match are not distinctive enough to steal civic search.\n */\n\nexport interface ParcelQuery {\n /** Trimmed original. */\n raw: string;\n /** Lookup key: `XXX-XXX-XXX`. */\n pid: string;\n}\n\nconst PID = /^\\d{3}-?\\d{3}-?\\d{3}$/;\n\nexport function parseParcel(q: string): ParcelQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (!PID.test(raw)) return null;\n const digits = raw.replace(/-/g, \"\");\n return {\n raw,\n pid: `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`,\n };\n}\n\n/** True when `q` is a ParcelMap BC PID, not a civic address. */\nexport function looksLikeParcel(q: string): boolean {\n return parseParcel(q) != null;\n}\n","/**\n * Bbox query against identify-capable catalog layers. Same PostGIS tables as\n * `/identify`; tile-only catalog ids are rejected (gateway 501), not silently empty.\n */\n\nimport {\n IDENTIFY_LAYER_IDS,\n isIdentifyLayer,\n knownIdentifyLayers,\n type IdentifyLayerId,\n type IdentifyResult,\n} from \"./identify\";\n\nexport const QUERY_LAYER_IDS = IDENTIFY_LAYER_IDS;\nexport type QueryLayerId = IdentifyLayerId;\n\nexport const DEFAULT_QUERY_LIMIT = 50;\nexport const MAX_QUERY_LIMIT = 50;\n\nexport type QueryBbox = [number, number, number, number];\n\nexport function knownQueryLayers(): string {\n return knownIdentifyLayers();\n}\n\nexport function parseQueryLayer(\n raw: string | undefined,\n): { layer: QueryLayerId } | { unknown: string; error: string } | { error: string } {\n const id = (raw ?? \"\").trim();\n if (!id) return { error: \"layer required\" };\n if (isIdentifyLayer(id)) return { layer: id };\n return {\n unknown: id,\n error: `unknown query layer \"${id}\". queryable: ${knownQueryLayers()}`,\n };\n}\n\nexport function parseQueryBbox(raw: string | undefined): { bbox: QueryBbox } | { error: string } {\n const value = raw?.trim();\n if (!value) return { error: \"bbox (minlng,minlat,maxlng,maxlat) required\" };\n const pieces = value.split(\",\");\n if (pieces.length !== 4 || pieces.some((piece) => piece.trim() === \"\")) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n const nums = pieces.map(Number);\n if (nums.some((n) => !Number.isFinite(n))) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n const [minLng, minLat, maxLng, maxLat] = nums as QueryBbox;\n if (minLng >= maxLng || minLat >= maxLat) {\n return { error: \"bbox must be minlng,minlat,maxlng,maxlat\" };\n }\n return { bbox: [minLng, minLat, maxLng, maxLat] };\n}\n\n/** Missing or non-numeric `limit` falls back; a finite value is clamped to 1–50. */\nexport function parseQueryLimit(raw: string | undefined): number {\n if (raw == null || raw === \"\") return DEFAULT_QUERY_LIMIT;\n const n = Number(raw);\n return Number.isFinite(n) ? Math.min(Math.max(Math.trunc(n), 1), MAX_QUERY_LIMIT) : DEFAULT_QUERY_LIMIT;\n}\n\nexport interface QueryOptions {\n /** Identify-capable catalog id. Tile-only catalog ids are a 501. */\n layer: string;\n /** `[minLng, minLat, maxLng, maxLat]` in WGS84 degrees. */\n bbox: QueryBbox;\n /** Max features. Default 50, clamped to 1–50. */\n limit?: number;\n /** Include geometry when the row has one. */\n geometry?: boolean;\n signal?: AbortSignal;\n}\n\nexport interface QueryResponse {\n layer: QueryLayerId;\n bbox: QueryBbox;\n results: IdentifyResult[];\n}\n","/**\n * Canadian rural and alternate delivery addresses.\n *\n * A rural route is not a place. `RR 2` names a mail carrier's circuit through a community, and\n * the same number exists in hundreds of communities; the site of the mailbox is not in any open\n * dataset. So this does not geocode the route. It splits the Canada Post delivery terms off the\n * front of a query, hands the rest to civic search, and returns the parsed terms alongside the\n * community that was found. \"RR 2 Okotoks\" resolves to Okotoks, labelled rural route 2, which is\n * the honest answer: the town is known, the mailbox is not.\n *\n * Recognised, per Canada Post's addressing guide:\n * RR / R.R. / RURAL ROUTE a rural route, numbered\n * SS / SUBURBAN SERVICE a suburban service route, numbered\n * MR / MOBILE ROUTE a mobile route, numbered\n * GD / GENERAL DELIVERY counter pickup, never numbered\n * SITE / COMP(ARTMENT) / BOX the position within the route\n * STN / RPO / LCD the retail outlet the route runs from\n *\n * Deliberately not a locality detector: a query has to carry one of the delivery keywords above\n * for this to fire, so an ordinary civic address is untouched.\n */\n\nexport type RuralRouteKind = \"rural-route\" | \"suburban-service\" | \"mobile-route\" | \"general-delivery\";\n\nexport interface RuralRouteQuery {\n /** Trimmed original. */\n raw: string;\n kind: RuralRouteKind;\n /** Route number as written. Absent for general delivery, which is never numbered. */\n number?: string;\n /** Position within the route, when given. */\n site?: string;\n compartment?: string;\n box?: string;\n /** The retail outlet the route runs from, e.g. \"MAIN\" in \"STN MAIN\". */\n station?: string;\n /**\n * What is left after the delivery terms are removed: normally the community, and the only\n * part that can actually be geocoded. Empty when the query was delivery terms and nothing\n * else, which cannot be resolved to anywhere.\n */\n remainder: string;\n}\n\nconst KINDS: { pattern: RegExp; kind: RuralRouteKind; numbered: boolean }[] = [\n { pattern: /\\b(?:R\\.?\\s?R\\.?|RURAL\\s+ROUTE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"rural-route\", numbered: true },\n { pattern: /\\b(?:S\\.?\\s?S\\.?|SUBURBAN\\s+SERVICE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"suburban-service\", numbered: true },\n { pattern: /\\b(?:M\\.?\\s?R\\.?|MOBILE\\s+ROUTE)\\s*#?\\s*(\\d{1,3})\\b/i, kind: \"mobile-route\", numbered: true },\n { pattern: /\\b(?:G\\.?\\s?D\\.?|GENERAL\\s+DELIVERY)\\b/i, kind: \"general-delivery\", numbered: false },\n];\n\nconst PARTS: { pattern: RegExp; field: \"site\" | \"compartment\" | \"box\" | \"station\" }[] = [\n { pattern: /\\bSITE\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"site\" },\n { pattern: /\\bCOMP(?:ARTMENT)?\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"compartment\" },\n { pattern: /\\b(?:PO\\s*)?BOX\\s*#?\\s*([A-Z0-9-]{1,8})\\b/i, field: \"box\" },\n // One word only. Canada Post does publish two-word station names (\"STN ST LAURENT\"), but\n // \"STN MAIN BRANDON\" is ambiguous between a two-word station and a station plus a community,\n // and guessing wrong swallows the only part that can be geocoded. Missing the second word of\n // a rare station name costs a label; stealing the town costs the answer.\n { pattern: /\\b(?:STN|STATION|RPO|LCD)\\s+([A-Z][A-Z0-9'-]*)\\b/i, field: \"station\" },\n];\n\nexport function parseRuralRoute(q: string): RuralRouteQuery | null {\n if (typeof q !== \"string\") return null;\n const raw = q.trim();\n if (raw === \"\") return null;\n\n const matched = KINDS.map((k) => ({ ...k, match: k.pattern.exec(raw) })).find((k) => k.match !== null);\n if (!matched?.match) return null;\n\n // Strip the delivery terms one at a time so the remainder is what a civic corpus can answer.\n let rest = raw.replace(matched.pattern, \" \");\n const parsed: RuralRouteQuery = { raw, kind: matched.kind, remainder: \"\" };\n if (matched.numbered) parsed.number = String(Number(matched.match[1]));\n\n for (const part of PARTS) {\n const hit = part.pattern.exec(rest);\n if (!hit) continue;\n parsed[part.field] = hit[1]!.trim().toUpperCase();\n rest = rest.replace(part.pattern, \" \");\n }\n\n // Whatever survives is the community. Separators the delivery terms were glued to (commas,\n // stray hyphens) go with them rather than reaching the corpus as noise.\n parsed.remainder = rest.replace(/[,;]+/g, \" \").replace(/\\s+/g, \" \").replace(/^[-\\s]+|[-\\s]+$/g, \"\");\n return parsed;\n}\n\n/**\n * True when `q` carries Canada Post delivery terms.\n *\n * Note this is NOT a routing signal in the way `looksLikeLegalLand` is: a rural-route query is\n * still answered by civic search, on its remainder. It exists so the caller can strip the terms\n * and label the result, not so it can pick a different backend.\n */\nexport function looksLikeRuralRoute(q: string): boolean {\n return parseRuralRoute(q) != null;\n}\n","import { DEFAULT_GATEWAY } from \"@unmap/core\";\n\n/**\n * Typed client for the Places API (gateway `/places/*`).\n *\n * It lives in `@unmap/geocoding` rather than in a package of its own because places ARE the\n * geocoding corpus: the same rows, the same container, the same taxonomy. A twelfth npm package\n * would also mean a twelfth entry in the changesets fixed group and a twelfth trusted publisher\n * to configure, which is real founder work for no boundary that exists in the code.\n *\n * `Geocoder.nearby()` is untouched and still answers in the geocoder's own shape. This is the\n * Place shape over the same rows.\n */\n\n/** A Canadian point of interest, as `/places/*` returns it. */\nexport interface Place {\n /** `um:place:<id>`. Opaque: a corpus rebuild may renumber the tail, so do not parse it. */\n id: string;\n /** The corpus id, for `POST /places/{id}/feedback`. */\n source_id: string;\n name: string;\n /** Every language and alternate spelling the corpus holds, including syllabics where recorded. */\n names: Record<string, string>;\n /** Taxonomy id, e.g. `finance.bank`. */\n category: string | null;\n /** The part before the dot, e.g. `finance`. */\n category_group: string | null;\n /** `[lng, lat]`, WGS84. */\n coordinates: [number, number];\n address: {\n housenumber?: string;\n street?: string;\n city?: string;\n region?: string;\n postcode?: string;\n country?: string;\n };\n /** Metres from the anchor. Only on `nearby()`. */\n distance?: number;\n /** Registry id of the dataset, for provenance. */\n source: string;\n}\n\n/**\n * The envelope `/places/*` answers in.\n *\n * `meta` is deliberately surfaced rather than unwrapped away: a caller that cannot see\n * `attribution` cannot comply with the licence the data came under, and one that cannot see\n * `status` cannot tell fresh data from stale.\n */\nexport interface PlacesResponse<T> {\n data: T;\n meta: {\n status: \"current\" | \"stale\" | \"partial\" | \"unknown\";\n attribution: string;\n unmap_updated_at: string;\n source?: string;\n source_updated_at?: string | null;\n };\n}\n\nexport interface PlacesOptions {\n key: string;\n gateway?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PlaceSearchOptions {\n limit?: number;\n lang?: string;\n bbox?: [number, number, number, number];\n signal?: AbortSignal;\n}\n\nexport interface PlaceNearbyOptions {\n /** `[lng, lat]`. Exactly one of `near` or `bbox`. */\n near?: [number, number];\n bbox?: [number, number, number, number];\n /** Metres, clamped server-side to 50..50000. */\n radius?: number;\n limit?: number;\n lang?: string;\n signal?: AbortSignal;\n}\n\nexport class PlacesError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"PlacesError\";\n }\n}\n\nasync function errorFrom(res: Response, path: string): Promise<PlacesError> {\n const fallback = `places ${res.status} on ${path}`;\n let body: { error?: unknown; code?: unknown } | undefined;\n try {\n body = (await res.json()) as { error?: unknown; code?: unknown };\n } catch {\n body = undefined;\n }\n // The gateway's `error` says what went wrong in words. Putting it in the message means a caller\n // who logs the thrown error reads the reason rather than just a status.\n const reason = typeof body?.error === \"string\" ? body.error : undefined;\n const code = typeof body?.code === \"string\" ? body.code : undefined;\n return new PlacesError(res.status, reason ? `${reason} (${fallback})` : fallback, code);\n}\n\n/**\n * Canadian places.\n *\n * const places = new Places({ key: \"um_live_...\" });\n * const { data } = await places.nearby(\"bakery\", { near: [-114.07, 51.05] });\n *\n * ## Fields this does not carry\n *\n * No `phone`, `website`, `opening_hours`, `accessibility` or `operator`. The corpus does not hold\n * them, so they are ABSENT from the response rather than null: an absent key is a statement about\n * unmap's coverage and is true, while `\"phone\": null` would be a statement about the place and\n * usually a false one. The types above reflect that rather than declaring optional fields nothing\n * ever populates.\n */\nexport class Places {\n private readonly key: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: PlacesOptions) {\n this.key = opts.key;\n this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n // Bound to globalThis for the same reason `Geocoder` does it: a bare `fetch` stored on an\n // instance is invoked with the instance as its receiver, and browsers reject that with\n // \"Illegal invocation\". Node is lenient, so it only ever surfaces in a browser.\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Search places by name.\n *\n * Unlike `Geocoder.search()`, which returns whatever best matches and for \"Calgary\" is the city\n * itself, this returns places.\n */\n search(q: string, opts: PlaceSearchOptions = {}): Promise<PlacesResponse<Place[]>> {\n return this.request<Place[]>(\"/places/search\", {\n q,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n bbox: opts.bbox?.join(\",\"),\n }, opts.signal);\n }\n\n /**\n * Places of a category near a point.\n *\n * `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French\n * (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `\"boulangerie\"` works.\n */\n nearby(\n category: string | readonly string[],\n opts: PlaceNearbyOptions = {},\n ): Promise<PlacesResponse<Place[]>> {\n return this.request<Place[]>(\"/places/nearby\", {\n category: Array.isArray(category) ? category.join(\",\") : String(category),\n near: opts.near?.join(\",\"),\n bbox: opts.bbox?.join(\",\"),\n radius: opts.radius != null ? String(opts.radius) : undefined,\n limit: opts.limit != null ? String(opts.limit) : undefined,\n lang: opts.lang,\n }, opts.signal);\n }\n\n /**\n * One place by its unmap id.\n *\n * Throws `PlacesError` with status 404 when it does not exist. A malformed id is also a 404,\n * because from a caller's side \"does not exist\" and \"is not a well-formed id\" are the same\n * answer; the gateway's message distinguishes them as far as it honestly can.\n */\n get(id: string, opts: { lang?: string; signal?: AbortSignal } = {}): Promise<PlacesResponse<Place>> {\n return this.request<Place>(`/places/${encodeURIComponent(id)}`, { lang: opts.lang }, opts.signal);\n }\n\n private async request<T>(\n path: string,\n params: Record<string, string | undefined>,\n signal?: AbortSignal,\n ): Promise<PlacesResponse<T>> {\n const q = new URLSearchParams({ key: this.key });\n for (const [k, v] of Object.entries(params)) if (v != null && v !== \"\") q.set(k, v);\n const res = await this.fetchImpl(`${this.base}${path}?${q}`, {\n signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) throw await errorFrom(res, path);\n return (await res.json()) as PlacesResponse<T>;\n }\n}\n"],"mappings":"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","names":["DEFAULT_GATEWAY","components","rest","con","errorFrom","DEFAULT_GATEWAY"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unmap/geocoding",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Typed client for the unmap geocoding API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@unmap/core": "0.
|
|
41
|
+
"@unmap/core": "0.13.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsup": "^8.3.0",
|