openvibers/openvibe.rent Commits

Rent: the home-price card finds the state from Nominatim's ISO code (there is no state_code) (#31)

OP OpenVibers committed 12 Oct 2026 · c40d4de838 · 2 files, +20 −4

Rent#30's home-price card never appeared on production, although the FHFA rows were ingested (8,293 metro and 1,122 state rows). The geocoded location carried `state: null`: the code read `address.state_code`, a field Nominatim does not send, and the test's mock had invented it.

- The state code now comes from Nominatim's `"ISO3166-2-lvl4": "US-CO"`, falling back to the state's name (the 50 states, DC and Puerto Rico). Verified against a real Nominatim answer for Gadsden, AL: `{"city": "Gadsden", "state": "Alabama", "ISO3166-2-lvl4": "US-AL", …}`.
- The tests' mocks now have the real shape. Denver carries the ISO field and Telluride only the state name, so both paths are covered.

Tests: housing-hpi, housing.

Changes

ddiff --git a/server/housing/places.js b/server/housing/places.js
iindex 927da20..9efed52 100644
---- a/server/housing/places.js
++++ b/server/housing/places.js
@@@ -78,6 +78,21 @@ const epochIso = (v) => {
return Number.isNaN(d.getTime()) ? null : d.toISOString();
};
++const US_STATES = { alabama: 'AL', alaska: 'AK', arizona: 'AZ', arkansas: 'AR', california: 'CA', colorado: 'CO', connecticut: 'CT',
++ delaware: 'DE', 'district of columbia': 'DC', florida: 'FL', georgia: 'GA', hawaii: 'HI', idaho: 'ID', illinois: 'IL', indiana: 'IN',
++ iowa: 'IA', kansas: 'KS', kentucky: 'KY', louisiana: 'LA', maine: 'ME', maryland: 'MD', massachusetts: 'MA', michigan: 'MI',
++ minnesota: 'MN', mississippi: 'MS', missouri: 'MO', montana: 'MT', nebraska: 'NE', nevada: 'NV', 'new hampshire': 'NH',
++ 'new jersey': 'NJ', 'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', 'north dakota': 'ND', ohio: 'OH', oklahoma: 'OK',
++ oregon: 'OR', pennsylvania: 'PA', 'puerto rico': 'PR', 'rhode island': 'RI', 'south carolina': 'SC', 'south dakota': 'SD',
++ tennessee: 'TN', texas: 'TX', utah: 'UT', vermont: 'VT', virginia: 'VA', washington: 'WA', 'west virginia': 'WV', wisconsin: 'WI', wyoming: 'WY' };
++
++/** A US place's two-letter state code from Nominatim's address: ISO3166-2-lvl4 ("US-CO") first, else the state's name. */
++function usStateCode(addr) {
++ const iso = /^US-([A-Z]{2})$/.exec(String(addr['ISO3166-2-lvl4'] || ''));
++ if (iso) return iso[1];
++ return US_STATES[String(addr.state || '').trim().toLowerCase()] || null;
++}
++
/** A HUD-supplied URL, kept only when it parses and speaks http(s): it must never become a hostile href. */
function safeUrl(v) {
if (typeof v !== 'string' || !v.trim()) return null;
@@@ -327,11 +342,12 @@ async function searchPlaces(upstream, { q = null, kinds = null } = {}, config) {
const country = hit.address && (hit.address.country_code || hit.address.country)
? String(hit.address.country_code || hit.address.country).toLowerCase() : null;
// The two pieces the FHFA House Price Index lookup matches on (server/housing/hpi.js): a city and the
-- // two-letter state code. Nominatim gives the code as address.state_code for US places.
++ // two-letter state code. Nominatim (addressdetails=1) gives a US state as "ISO3166-2-lvl4": "US-CO" and
++ // "state": "Colorado"; there is no state_code field (Rent#30 read one, so production never matched).
const addr = hit.address || {};
const city = str(addr.city) || str(addr.town) || str(addr.village) || str(addr.hamlet) || str(addr.municipality)
|| str(addr.county) || str(addr.suburb) || str(addr.city_district);
-- const state = str(addr.state_code);
++ const state = usStateCode(addr);
place = { name: hit.display_name || String(q), lat: la, lon: lo, from: 'geocode', geocoded_at: g.fetched_at, country_code: country, bbox: parseBbox(hit.boundingbox), city, state };
} else {
throw new UpstreamError('housing.place.invalid', 'q: a place to search for', { status: 422 });
ddiff --git a/test/housing-hpi.test.js b/test/housing-hpi.test.js
iindex ea8c396..40515b9 100644
---- a/test/housing-hpi.test.js
++++ b/test/housing-hpi.test.js
@@@ -46,12 +46,12 @@ const jsonResponse = (obj, status = 200) => new Response(JSON.stringify(obj), {
const denverPlace = [{
place_id: 1, lat: '39.7392', lon: '-104.9903', display_name: 'Denver, Colorado, United States',
boundingbox: ['39.61', '39.86', '-105.11', '-104.88'],
-- address: { country: 'United States', country_code: 'us', state: 'Colorado', state_code: 'CO', city: 'Denver' },
++ address: { country: 'United States', country_code: 'us', state: 'Colorado', 'ISO3166-2-lvl4': 'US-CO', city: 'Denver' },
}];
const telluridePlace = [{
place_id: 2, lat: '37.9375', lon: '-107.8123', display_name: 'Telluride, Colorado, United States',
boundingbox: ['37.92', '37.95', '-107.84', '-107.78'],
-- address: { country: 'United States', country_code: 'us', state: 'Colorado', state_code: 'CO', city: 'Telluride' },
++ address: { country: 'United States', country_code: 'us', state: 'Colorado', city: 'Telluride' },
}];
const mockFetch = async (url) => {