From ae7497363a05f36fdb52c37438a6583528e51cef Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Tue, 12 May 2026 15:09:04 -0400 Subject: [PATCH] Quiet expected PolicyEngine API fallback errors --- frontend/src/dataLookup.js | 18 ++++++-- frontend/src/policyengineApi.js | 2 +- frontend/src/policyengineApi.test.js | 66 ++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/frontend/src/dataLookup.js b/frontend/src/dataLookup.js index 7144c56..7012bc4 100644 --- a/frontend/src/dataLookup.js +++ b/frontend/src/dataLookup.js @@ -1,7 +1,8 @@ import { calculateHouseholdViaPolicyEngine, calculateSeriesViaPolicyEngine, -} from './policyengineApi' + PolicyEngineApiError, +} from './policyengineApi.js' const parseErrorMessage = async (response) => { try { @@ -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', { @@ -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 } @@ -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) } } diff --git a/frontend/src/policyengineApi.js b/frontend/src/policyengineApi.js index ff8bd0b..e56c4e8 100644 --- a/frontend/src/policyengineApi.js +++ b/frontend/src/policyengineApi.js @@ -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' diff --git a/frontend/src/policyengineApi.test.js b/frontend/src/policyengineApi.test.js index bec5355..575a281 100644 --- a/frontend/src/policyengineApi.test.js +++ b/frontend/src/policyengineApi.test.js @@ -1,6 +1,7 @@ import assert from 'node:assert/strict' import test from 'node:test' +import { calculateSeries } from './dataLookup.js' import { buildCliffDrivers, buildHouseholdResultFromResponse, @@ -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: '' }, @@ -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 + } +})