Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions frontend/src/dataLookup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
calculateHouseholdViaPolicyEngine,
calculateSeriesViaPolicyEngine,
} from './policyengineApi'
PolicyEngineApiError,
} from './policyengineApi.js'

const parseErrorMessage = async (response) => {
try {
Expand Down Expand Up @@ -30,6 +31,17 @@ const postJson = async (path, payload) => {
return response.json()
}

const logUnexpectedPolicyEngineFallback = (error) => {
if (
error instanceof PolicyEngineApiError
|| error?.name === 'PolicyEngineApiError'
) {
return
}

console.error(error)
}

export const formatCurrency = (value, digits = 0) => new Intl.NumberFormat(
'en-US',
{
Expand Down Expand Up @@ -266,7 +278,7 @@ export async function calculateStateResult(inputs, metadata) {
try {
return await calculateHouseholdViaPolicyEngine(payload, metadata)
} catch (error) {
console.error(error)
logUnexpectedPolicyEngineFallback(error)
const response = await postJson('/api/calculate', payload)
return response.result
}
Expand All @@ -285,7 +297,7 @@ export async function calculateSeries(inputs, metadata, options = {}) {
try {
return await calculateSeriesViaPolicyEngine(payload, metadata)
} catch (error) {
console.error(error)
logUnexpectedPolicyEngineFallback(error)
return postJson('/api/series', payload)
}
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/policyengineApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function isCcdfModeledState(state, metadata) {
return DEFAULT_CCDF_MODELED_STATES.has(state)
}

class PolicyEngineApiError extends Error {
export class PolicyEngineApiError extends Error {
constructor(message, status, response) {
super(message)
this.name = 'PolicyEngineApiError'
Expand Down
66 changes: 66 additions & 0 deletions frontend/src/policyengineApi.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict'
import test from 'node:test'

import { calculateSeries } from './dataLookup.js'
import {
buildCliffDrivers,
buildHouseholdResultFromResponse,
Expand All @@ -9,7 +10,14 @@ import {
import { applyFilingStatusSelection } from './utils/filingStatus.js'

const metadata = {
year: 2026,
states: [{ code: 'GA', name: 'Georgia' }],
defaults: {
chart_max_earned_income: 1000,
max_adults: 6,
max_dependents: 6,
series_step: 500,
},
programs: [
{ key: 'tanf', label: 'TANF', short_label: 'TANF', description: '' },
{ key: 'chip', label: 'CHIP', short_label: 'CHIP', description: '' },
Expand Down Expand Up @@ -266,3 +274,61 @@ test('applyFilingStatusSelection leaves existing household members alone for non

assert.deepEqual(result, { filing_status: 'HEAD_OF_HOUSEHOLD' })
})

test('calculateSeries quietly falls back after a PolicyEngine API error', async () => {
const originalFetch = globalThis.fetch
const originalConsoleError = console.error
const consoleErrors = []
const requests = []

globalThis.fetch = async (url) => {
requests.push(String(url))

if (String(url).includes('api.policyengine.org')) {
return new Response(
JSON.stringify({ error: 'PolicyEngine API unavailable' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
},
)
}

assert.equal(String(url), '/api/series')
return new Response(
JSON.stringify({
data: [],
step_annual: 500,
max_earned_income: 1000,
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
},
)
}
console.error = (...args) => consoleErrors.push(args)

try {
const result = await calculateSeries(
{
state: 'GA',
people: [{ kind: 'adult', age: 33 }],
filing_status: 'SINGLE',
chart_max_earned_income: 1000,
},
metadata,
{ step: 500 },
)

assert.deepEqual(result.data, [])
assert.deepEqual(consoleErrors, [])
assert.deepEqual(requests, [
'https://api.policyengine.org/us/calculate',
'/api/series',
])
} finally {
globalThis.fetch = originalFetch
console.error = originalConsoleError
}
})