From data-science
Wires frontend components to backend API clients, ensuring end-to-end data flow from user input through requests, responses, transforms, to rendered UI. Handles loading/error/empty states and caching.
npx claudepluginhub policyengine/policyengine-claude --plugin data-sciencesonnet**IMPORTANT**: Use careful, step-by-step reasoning before taking any action. Think through: 1. How data flows from user input to API call to rendered output 2. Whether request/response types are consistent across the boundary 3. Whether loading, error, and empty states are handled at every step Wires the frontend components to the backend API client and ensures the full data flow works correctly.
Builds dashboard data layers from plan.yaml: precomputed JSON files, PolicyEngine API clients with React Query hooks, or custom Modal backends. Outputs typed API clients, tests, and TypeScript types.
Designs frontend-backend integration layers using GraphQL queries, state synchronization, authorization flows, caching strategies, and error handling for reliable patterns.
Builds production-ready React 19/TypeScript UI components using optimistic updates, Zod-validated APIs, design tokens, animations, concurrent features, and exhaustive type safety. Investigates project context first.
Share bugs, ideas, or general feedback.
IMPORTANT: Use careful, step-by-step reasoning before taking any action. Think through:
Wires the frontend components to the backend API client and ensures the full data flow works correctly.
Skill: policyengine-interactive-tools-skillSkill: policyengine-design-skillplan.yaml for referenceRead through the codebase and trace the data path:
User input (form state)
→ Request builder (form state → API request)
→ API client (request → response)
→ Response transformer (API response → component props)
→ Chart/card/table components (props → rendered UI)
For each step, verify:
If not already present, create functions that transform form state into API request objects:
// lib/requestBuilders.ts
import type { HouseholdSimulationRequest } from '../api/types';
import type { FormValues } from '../components/HouseholdInputs';
export function buildHouseholdRequest(
values: FormValues,
countryId: string,
): HouseholdSimulationRequest {
const modelName = countryId === 'uk' ? 'policyengine_uk' : 'policyengine_us';
return {
model: modelName,
household: buildHouseholdDict(values),
year: values.year || new Date().getFullYear(),
policy_id: values.policyId || null,
};
}
function buildHouseholdDict(values: FormValues): Record<string, unknown> {
// Map form fields to PolicyEngine household structure
// Following the structure from policyengine-interactive-tools-skill
return {
people: {
head: {
age: { [String(values.year)]: values.age || 40 },
employment_income: { [String(values.year)]: values.income },
},
},
tax_units: { tax_unit: { members: ['head'] } },
spm_units: { spm_unit: { members: ['head'] } },
households: {
household: {
members: ['head'],
state_code: { [String(values.year)]: values.state },
},
},
};
}
Create functions that extract chart-ready data from API responses:
// lib/responseTransformers.ts
import type { HouseholdSimulationResult } from '../api/types';
export function extractMetrics(
result: HouseholdSimulationResult
): DashboardMetrics {
const household = result.household[0] || {};
const person = result.person[0] || {};
return {
incomeTax: person.income_tax ?? 0,
netIncome: household.household_net_income ?? 0,
// Map all variables from plan.yaml
};
}
export function buildChartData(
results: HouseholdSimulationResult[],
xValues: number[],
): ChartDataPoint[] {
return xValues.map((x, i) => ({
x,
...extractMetrics(results[i]),
}));
}
Ensure the hooks in useCalculation.ts are correctly used by components:
// Verify caching prevents duplicate calls
const mutation = useHouseholdSimulation();
// Should NOT re-trigger on every render
useEffect(() => {
if (formChanged) {
mutation.mutate(buildHouseholdRequest(values, countryId));
}
}, [formValues]); // Only re-run when form values change
For every component that displays data:
Loading state:
{mutation.isPending && (
<div className="loading">
<span className="loading-spinner" />
<span>Calculating...</span>
</div>
)}
Error state:
{mutation.isError && (
<div className="error-message">
<p>Something went wrong. Please try again.</p>
<button onClick={() => mutation.reset()}>Retry</button>
</div>
)}
Empty/initial state:
{!mutation.data && !mutation.isPending && (
<div className="empty-state">
<p>Enter your details and click Calculate to see results.</p>
</div>
)}
If the plan includes charts that vary a parameter (e.g., "tax by income"), implement the variation logic:
export function useIncomeVariation(baseValues: FormValues, countryId: string) {
return useQuery({
queryKey: ['income-variation', baseValues],
queryFn: async () => {
const incomePoints = generateRange(0, 500000, 25); // 20 points
const results = await Promise.all(
incomePoints.map(income =>
simulateHousehold(
buildHouseholdRequest({ ...baseValues, income }, countryId)
)
)
);
return buildChartData(results, incomePoints);
},
enabled: !!baseValues.state, // Only run when required inputs are set
});
}
bun run dev # Start dev server — verify no runtime crashes
Do NOT run build or tests — that is the validator's job in Phase 5.