teqo-backup/scripts/lib/dossieHealthData.mjs

94 lines
2.8 KiB
JavaScript

/**
* IBGE baseline for the C186 dossiê (owner of the programmatic municipal
* context). Fetches the sourced population baseline used as the municipal
* denominator ("leitura relativa" — never an absolute state share). SIOPS/CNES
* health-execution series are not JSON APIs; they stay a per-era research task
* and degrade to an explicit gap when absent.
*
* `fetchImpl` is injectable so the unit tests never touch the network.
*/
import { formatInteger } from './cityReportFormat.mjs'
const SIDRA_BASE = 'https://apisidra.ibge.gov.br/values'
const CENSUS_YEAR = 2022
export const sidraPopulationUrl = (ibgeCode, year = CENSUS_YEAR) =>
`${SIDRA_BASE}/t/9514/n6/${ibgeCode}/v/93/p/${year}`
/** SIDRA `/values` returns a header row first; the datum is `payload[1].V`. */
export const parseSidraValue = (payload) => {
if (!Array.isArray(payload) || payload.length < 2) return null
const raw = String(payload[1]?.V ?? '').replace(/[^\d-]/g, '')
if (raw === '') return null
const value = Number(raw)
return Number.isFinite(value) ? value : null
}
const gap = ({ reason, sourceUrl, consultedAt }) => ({
status: 'gap',
reason,
items: [],
sourceUrl,
consultedAt,
})
/**
* @param {{
* ibgeCode?: string | null,
* fetchImpl?: typeof fetch,
* now?: Date,
* year?: number,
* }} [options]
* @returns {Promise<{ status: 'ok'|'gap', items: Array<{topic: string, detail: string, value: number, year: string, sourceUrl: string, sourceDate: string}>, reason: string|null, sourceUrl: string|null, consultedAt: string }>}
*/
export const fetchHealthData = async ({
ibgeCode = null,
fetchImpl = fetch,
now = new Date(),
year = CENSUS_YEAR,
} = {}) => {
const consultedAt = now.toISOString()
if (!ibgeCode) {
return gap({
reason: 'Município sem código IBGE no snapshot — contexto não consultado.',
sourceUrl: null,
consultedAt,
})
}
const sourceUrl = sidraPopulationUrl(ibgeCode, year)
try {
const response = await fetchImpl(sourceUrl)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const population = parseSidraValue(await response.json())
if (population === null) {
return gap({
reason: 'SIDRA não devolveu valor de população para o município.',
sourceUrl,
consultedAt,
})
}
return {
status: 'ok',
reason: null,
sourceUrl,
consultedAt,
items: [
{
topic: `População residente (Censo ${year})`,
detail: `${formatInteger(population)} habitantes`,
value: population,
year: String(year),
sourceUrl,
sourceDate: consultedAt.slice(0, 10),
},
],
}
} catch (error) {
return gap({
reason: `Falha ao consultar o IBGE/SIDRA: ${error instanceof Error ? error.message : error}`,
sourceUrl,
consultedAt,
})
}
}