Skip to content

Commit 6803cbb

Browse files
authored
Merge pull request #950 from techmatters/CHI-3500-usch_location_paths
CHI-3500: Make address values for USCH into filterable paths
2 parents 6591e17 + 9916608 commit 6803cbb

13 files changed

Lines changed: 681 additions & 51 deletions

File tree

resources-domain/lambdas/s3-importer/src/uschMappings.ts

Lines changed: 278 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,85 @@ import type { AccountSID } from '@tech-matters/types';
2424
import type { FlatResource } from '@tech-matters/resources-types';
2525
import { parse } from 'date-fns';
2626

27+
// https://gist.github.com/mshafrir/2646763
28+
const US_STATE_CODE_MAPPING = {
29+
AL: 'Alabama',
30+
AK: 'Alaska',
31+
AS: 'American Samoa',
32+
AZ: 'Arizona',
33+
AR: 'Arkansas',
34+
CA: 'California',
35+
CO: 'Colorado',
36+
CT: 'Connecticut',
37+
DE: 'Delaware',
38+
DC: 'District Of Columbia',
39+
FM: 'Federated States Of Micronesia',
40+
FL: 'Florida',
41+
GA: 'Georgia',
42+
GU: 'Guam',
43+
HI: 'Hawaii',
44+
ID: 'Idaho',
45+
IL: 'Illinois',
46+
IN: 'Indiana',
47+
IA: 'Iowa',
48+
KS: 'Kansas',
49+
KY: 'Kentucky',
50+
LA: 'Louisiana',
51+
ME: 'Maine',
52+
MH: 'Marshall Islands',
53+
MD: 'Maryland',
54+
MA: 'Massachusetts',
55+
MI: 'Michigan',
56+
MN: 'Minnesota',
57+
MS: 'Mississippi',
58+
MO: 'Missouri',
59+
MT: 'Montana',
60+
NE: 'Nebraska',
61+
NV: 'Nevada',
62+
NH: 'New Hampshire',
63+
NJ: 'New Jersey',
64+
NM: 'New Mexico',
65+
NY: 'New York',
66+
NC: 'North Carolina',
67+
ND: 'North Dakota',
68+
MP: 'Northern Mariana Islands',
69+
OH: 'Ohio',
70+
OK: 'Oklahoma',
71+
OR: 'Oregon',
72+
PW: 'Palau',
73+
PA: 'Pennsylvania',
74+
PR: 'Puerto Rico',
75+
RI: 'Rhode Island',
76+
SC: 'South Carolina',
77+
SD: 'South Dakota',
78+
TN: 'Tennessee',
79+
TX: 'Texas',
80+
UT: 'Utah',
81+
VT: 'Vermont',
82+
VI: 'Virgin Islands',
83+
VA: 'Virginia',
84+
WA: 'Washington',
85+
WV: 'West Virginia',
86+
WI: 'Wisconsin',
87+
WY: 'Wyoming',
88+
} as Record<string, string>;
89+
90+
const CANADIAN_PROVINCE_CODE_MAPPING = {
91+
AB: 'Alberta',
92+
BC: 'British Columbia',
93+
NL: 'Newfoundland and Labrador',
94+
PE: 'Île-du-Prince-Édouard',
95+
NS: 'Nouvelle-Écosse',
96+
NB: 'New Brunswick',
97+
ON: 'Ontario',
98+
MB: 'Manitoba',
99+
SK: 'Saskatchewan',
100+
YT: 'Yukon',
101+
NT: 'Northwest Territories',
102+
NU: 'Nunavut',
103+
QC: 'Québec',
104+
} as Record<string, string>;
105+
27106
/*
28107
* This defines all the mapping logic to convert Childhelp resource to an Aselo resource.
29108
* The mapping is defined as a tree of nodes.
@@ -93,6 +172,31 @@ export type UschExpandedResource = Partial<
93172
}
94173
>;
95174

175+
const isUnitedStates = (country: string | undefined) =>
176+
['us', 'usa', 'unitedstates'].includes(
177+
(country ?? '').toLowerCase().replaceAll(/[.\s]/g, ''),
178+
);
179+
180+
const isUSStateOrTerritory = (country: string | undefined) =>
181+
country &&
182+
(Object.keys(US_STATE_CODE_MAPPING).includes(country) ||
183+
Object.values(US_STATE_CODE_MAPPING).includes(country));
184+
185+
const isCanadianProvince = (country: string | undefined) =>
186+
country &&
187+
(Object.keys(CANADIAN_PROVINCE_CODE_MAPPING).includes(country) ||
188+
Object.values(CANADIAN_PROVINCE_CODE_MAPPING).includes(country));
189+
190+
const lookupUsStateNameFromCode = ({
191+
Country: country,
192+
StateProvince: stateProvince,
193+
}: UschExpandedResource): string | undefined => {
194+
if (isUnitedStates(country)) {
195+
return US_STATE_CODE_MAPPING[stateProvince ?? ''] ?? stateProvince;
196+
}
197+
return stateProvince;
198+
};
199+
96200
export const expandCsvLine = (csv: UschCsvResource): UschExpandedResource => {
97201
const expanded = {
98202
...csv,
@@ -113,8 +217,27 @@ export const USCH_MAPPING_NODE: MappingNode = {
113217
Name: resourceFieldMapping('name'),
114218
AlternateName: translatableAttributeMapping('alternateName', { language: 'en' }),
115219
Address: attributeMapping('stringAttributes', 'address/street'),
116-
City: attributeMapping('stringAttributes', 'address/city'),
117-
StateProvince: attributeMapping('stringAttributes', 'address/province'),
220+
City: attributeMapping('stringAttributes', 'address/city', {
221+
value: ({ currentValue, rootResource }) =>
222+
[
223+
(rootResource as UschExpandedResource).Country,
224+
(rootResource as UschExpandedResource).StateProvince,
225+
currentValue,
226+
].join('/'),
227+
info: ({ currentValue, rootResource }) => ({
228+
country: (rootResource as UschExpandedResource).Country,
229+
stateProvince: lookupUsStateNameFromCode(rootResource as UschExpandedResource),
230+
name: currentValue,
231+
}),
232+
}),
233+
StateProvince: attributeMapping('stringAttributes', 'address/province', {
234+
value: ({ currentValue, rootResource }) =>
235+
`${(rootResource as UschExpandedResource).Country}/${currentValue}`,
236+
info: ({ rootResource }) => ({
237+
country: (rootResource as UschExpandedResource).Country,
238+
name: lookupUsStateNameFromCode(rootResource as UschExpandedResource),
239+
}),
240+
}),
118241
PostalCode: attributeMapping('stringAttributes', 'address/postalCode'),
119242
Country: attributeMapping('stringAttributes', 'address/country'),
120243
HoursOfOperation: translatableAttributeMapping('hoursOfOperation'),
@@ -188,10 +311,159 @@ export const USCH_MAPPING_NODE: MappingNode = {
188311
},
189312
Coverage: {
190313
children: {
191-
'{coverageIndex}': translatableAttributeMapping('coverage/{coverageIndex}', {
192-
value: ({ currentValue }) => currentValue,
193-
language: 'en',
194-
}),
314+
'{coverageIndex}': {
315+
mappings: [
316+
translatableAttributeMapping('coverage/{coverageIndex}', {
317+
value: ({ currentValue }) => {
318+
const [countryOrState] = currentValue.toString().split(/\s+-\s+/);
319+
320+
if (isUSStateOrTerritory(countryOrState)) {
321+
return `United States/${currentValue.replaceAll(/\s+-\s+/g, '/')}`;
322+
} else {
323+
return `${currentValue.replaceAll(/\s+-\s+/g, '/')}`;
324+
}
325+
},
326+
info: ({ currentValue }) => {
327+
const [countryOrState, provinceOrCity, internationalCity] = currentValue
328+
.toString()
329+
.split(/\s+-\s+/);
330+
if (isUSStateOrTerritory(countryOrState)) {
331+
return {
332+
country: 'United States',
333+
stateProvince:
334+
US_STATE_CODE_MAPPING[countryOrState ?? ''] ?? countryOrState,
335+
city: provinceOrCity,
336+
name: currentValue,
337+
};
338+
} else if (isCanadianProvince(countryOrState)) {
339+
return {
340+
country: 'Canada',
341+
stateProvince:
342+
CANADIAN_PROVINCE_CODE_MAPPING[countryOrState ?? ''] ??
343+
countryOrState,
344+
city: provinceOrCity,
345+
name: currentValue,
346+
};
347+
} else {
348+
return {
349+
country: countryOrState,
350+
stateProvince: provinceOrCity,
351+
city: internationalCity,
352+
name: currentValue,
353+
};
354+
}
355+
},
356+
language: 'en',
357+
}),
358+
// Not using coverage/country because that makes things bessy with root coverage values
359+
translatableAttributeMapping('coverageCountry/{coverageIndex}', {
360+
value: ({ currentValue }) => {
361+
const [countryOrState] = currentValue.toString().split(/\s+-\s+/);
362+
if (isUSStateOrTerritory(countryOrState)) {
363+
return 'United States';
364+
} else if (isCanadianProvince(countryOrState)) {
365+
return 'Canada';
366+
} else {
367+
return countryOrState;
368+
}
369+
},
370+
language: 'en',
371+
}),
372+
// Not using coverage/province because that makes things bessy with root coverage values
373+
translatableAttributeMapping('coverageStateProvince/{coverageIndex}', {
374+
value: ({ currentValue }) => {
375+
const [countryOrState, provinceOrCity] = currentValue
376+
.toString()
377+
.split(/\s+-\s+/);
378+
if (isUSStateOrTerritory(countryOrState)) {
379+
return `United States/${countryOrState}`;
380+
} else if (isCanadianProvince(countryOrState)) {
381+
return `Canada/${countryOrState}`;
382+
} else if (provinceOrCity) {
383+
return `${countryOrState}/${provinceOrCity}`;
384+
} else return '';
385+
},
386+
info: ({ currentValue }) => {
387+
const [countryOrState, provinceOrCity] = currentValue
388+
.toString()
389+
.split(/\s+-\s+/);
390+
if (isUSStateOrTerritory(countryOrState)) {
391+
const stateProvince =
392+
US_STATE_CODE_MAPPING[countryOrState ?? ''] ?? countryOrState;
393+
return {
394+
country: 'United States',
395+
stateProvince,
396+
name: stateProvince,
397+
};
398+
} else if (isCanadianProvince(countryOrState)) {
399+
const stateProvince =
400+
CANADIAN_PROVINCE_CODE_MAPPING[countryOrState ?? ''] ?? countryOrState;
401+
return {
402+
country: 'Canada',
403+
stateProvince,
404+
name: stateProvince,
405+
};
406+
} else if (provinceOrCity) {
407+
return {
408+
country: countryOrState,
409+
stateProvince: provinceOrCity,
410+
name: provinceOrCity,
411+
};
412+
} else return null;
413+
},
414+
language: 'en',
415+
}),
416+
// Not using coverage/city because that makes things messy with root coverage values
417+
translatableAttributeMapping('coverageCity/{coverageIndex}', {
418+
value: ({ currentValue }) => {
419+
const [countryOrState, provinceOrCity, internationalCity] = currentValue
420+
.toString()
421+
.split(/\s+-\s+/);
422+
if (isUSStateOrTerritory(countryOrState)) {
423+
return `United States/${countryOrState}/${provinceOrCity}`;
424+
} else if (isCanadianProvince(countryOrState)) {
425+
return `Canada/${countryOrState}/${provinceOrCity}`;
426+
} else if (internationalCity) {
427+
return `${countryOrState}/${provinceOrCity}/${internationalCity}`;
428+
} else return '';
429+
},
430+
info: ({ currentValue, rootResource }) => {
431+
const [countryOrState, provinceOrCity, internationalCity] = currentValue
432+
.toString()
433+
.split(/\s+-\s+/);
434+
if (isUSStateOrTerritory(countryOrState)) {
435+
const stateProvince =
436+
US_STATE_CODE_MAPPING[countryOrState ?? ''] ?? countryOrState;
437+
return {
438+
country: 'United States',
439+
stateProvince,
440+
city: provinceOrCity,
441+
name: provinceOrCity,
442+
};
443+
} else if (isCanadianProvince(rootResource.Country)) {
444+
const stateProvince =
445+
CANADIAN_PROVINCE_CODE_MAPPING[countryOrState ?? ''] ?? countryOrState;
446+
return {
447+
country: 'Canada',
448+
stateProvince,
449+
city: provinceOrCity,
450+
name: provinceOrCity,
451+
};
452+
} else if (internationalCity) {
453+
return {
454+
country: countryOrState,
455+
stateProvince: provinceOrCity,
456+
city: internationalCity,
457+
name: internationalCity,
458+
};
459+
} else {
460+
return null;
461+
}
462+
},
463+
language: 'en',
464+
}),
465+
],
466+
},
195467
},
196468
},
197469
Comment: translatableAttributeMapping('comment', { language: 'en' }),

resources-domain/packages/resources-search-config/resourceIndexDocumentMappings/uschMappings.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* along with this program. If not, see https://www.gnu.org/licenses/.
1515
*/
1616

17-
import type { ReferrableResourceAttribute } from '@tech-matters/resources-types';
1817
import { ResourceIndexDocumentMappings } from './resourceIndexDocumentMappings';
1918
import { ResourcesSearchConfiguration } from '../searchConfiguration';
2019

@@ -59,22 +58,16 @@ const resourceIndexDocumentMappings: ResourceIndexDocumentMappings = {
5958
type: 'keyword',
6059
isArrayField: true,
6160
attributeKeyPattern: /(.*)([cC])ountry$/,
62-
indexValueGenerator: ({ value, info }: ReferrableResourceAttribute<string>) =>
63-
[info?.name, value].filter(i => i).join(' '),
6461
},
6562
province: {
6663
type: 'keyword',
6764
isArrayField: true,
6865
attributeKeyPattern: /(.*)([pP])rovince$/,
69-
indexValueGenerator: ({ value, info }: ReferrableResourceAttribute<string>) =>
70-
[info?.name, value].filter(i => i).join(' '),
7166
},
7267
city: {
7368
type: 'keyword',
7469
isArrayField: true,
7570
attributeKeyPattern: /(.*)[cC]ity$/,
76-
indexValueGenerator: ({ value, info }: ReferrableResourceAttribute<string>) =>
77-
[info?.name, value].filter(i => i).join(' '),
7871
},
7972
},
8073
languageFields: {

resources-domain/resources-service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"test:service": "cross-env POSTGRES_PORT=5433 RDS_USERNAME=hrm RDS_PASSWORD=postgres RESOURCES_PASSWORD=resources-password run-s -c docker:compose:test:up db:create:schema test:service:ci:migrate test:service:ci:run docker:compose:test:down",
3333
"test:service:ci": "RDS_USERNAME=rdsadmin RDS_PASSWORD=postgres RESOURCES_PASSWORD=resources-password run-s db:create:schema test:service:ci:migrate test:service:ci:run",
3434
"test:service:ci:migrate": "node ./db-migrate",
35-
"test:service:ci:run": "cross-env AWS_REGION=us-east-1 CI=true TWILIO_ACCOUNT_SID=ACxxx TWILIO_AUTH_TOKEN=xxxxxx SSM_ENDPOINT=http://mock-ssm/ jest --verbose --maxWorkers=1 --forceExit --coverage tests/service/adminSearch.test.ts",
35+
"test:service:ci:run": "cross-env AWS_REGION=us-east-1 CI=true TWILIO_ACCOUNT_SID=ACxxx TWILIO_AUTH_TOKEN=xxxxxx SSM_ENDPOINT=http://mock-ssm/ jest --verbose --maxWorkers=1 --forceExit --coverage tests/service",
3636
"test:coverage": "run-s docker:compose:test:up test:service:migrate test:coverage:run docker:compose:test:down",
3737
"test:coverage:run": "cross-env POSTGRES_PORT=5433 AWS_REGION=us-east-1 jest --verbose --maxWorkers=1 --coverage",
3838
"test:migrate": "run-s test:service:migrate",

resources-domain/resources-service/src/referenceAttributes/referenceAttributeRoutesV0.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ const referenceAttributeRoutes = () => {
2222
const router: IRouter = Router();
2323
const { getResourceReferenceAttributeList } = referenceAttributeService();
2424

25-
router.get('/:list', async (req, res) => {
25+
router.get('/*', async (req, res) => {
2626
const { valueStartsWith, language } = req.query;
27-
const { list } = req.params;
27+
const list = (req as any).params[0];
2828
const result = await getResourceReferenceAttributeList(
2929
req.hrmAccountId as AccountSID,
3030
list,

0 commit comments

Comments
 (0)