From 5fa437c751f83437400ce4fadc3e7bfae32ef65f Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 17 Apr 2026 16:08:38 -0400 Subject: [PATCH 1/2] Add Modeling Differences documentation section Adds a new tab to the dashboard Documentation page explaining how PolicyEngine and TAXSIM outputs differ. Two parts: - Static conventions (stable architectural choices): v22/actc CTC split, state EITC bundling (incl. MN/MO/WA working family credits), filing-status auto-detection, spouse income splitting, Additional Medicare Tax handling, non-refundable credit ordering. - Dynamic list of open GitHub issues labeled "model-difference", so active divergences stay in sync with the issue tracker. Co-Authored-By: Claude Opus 4.7 (1M context) --- changelog.d/add-model-diff-docs.added.md | 1 + dashboard/public/config-data.json | 2 +- .../src/components/DocumentationContent.jsx | 5 + dashboard/src/components/ModelDifferences.jsx | 269 ++++++++++++++++++ dashboard/src/utils/githubApi.js | 9 + 5 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 changelog.d/add-model-diff-docs.added.md create mode 100644 dashboard/src/components/ModelDifferences.jsx diff --git a/changelog.d/add-model-diff-docs.added.md b/changelog.d/add-model-diff-docs.added.md new file mode 100644 index 00000000..ee5b549d --- /dev/null +++ b/changelog.d/add-model-diff-docs.added.md @@ -0,0 +1 @@ +Add documentation section explaining how PolicyEngine and TAXSIM outputs differ. Renders static output conventions (CTC/ACTC split, state EITC bundling, filing-status auto-detection, spouse income splitting, Additional Medicare Tax, non-refundable credit ordering) plus a live feed of GitHub issues labeled model-difference. diff --git a/dashboard/public/config-data.json b/dashboard/public/config-data.json index d424d0d4..42bab550 100644 --- a/dashboard/public/config-data.json +++ b/dashboard/public/config-data.json @@ -280,5 +280,5 @@ } ] }, - "lastUpdated": "2026-03-24T15:14:08.434Z" + "lastUpdated": "2026-04-17T20:07:56.953Z" } \ No newline at end of file diff --git a/dashboard/src/components/DocumentationContent.jsx b/dashboard/src/components/DocumentationContent.jsx index 13261f42..8912ef54 100644 --- a/dashboard/src/components/DocumentationContent.jsx +++ b/dashboard/src/components/DocumentationContent.jsx @@ -16,6 +16,7 @@ import { import { highlightCode } from '../utils/codeHighlight'; import { loadConfigurationData } from '../utils/configLoader'; import LoadingSpinner from './common/LoadingSpinner'; +import ModelDifferences from './ModelDifferences'; import { OUTPUT_VARIABLES, INPUT_VARIABLE_CATEGORIES, @@ -465,6 +466,7 @@ policyengine_versions() { id: 'installation', label: 'Installation & Usage' }, { id: 'options', label: 'All Runners & CLI' }, { id: 'mappings', label: 'Variable Mappings' }, + { id: 'differences', label: 'Modeling Differences' }, { id: 'datasets', label: 'Sample Datasets' }, ].map(({ id, label }) => ( + {isExpanded && conv.detail && ( +
+

{conv.detail}

+
+ )} + + ); + })} + + + + {/* Dynamic issues */} +
+
+
+ +

+ Known differences under investigation +

+ + {loading ? '…' : issues.length} + +
+ + + View on GitHub + +
+

+ Issues tagged{' '} + + {MODEL_DIFFERENCE_LABEL} + {' '} + document current PolicyEngine vs. TAXSIM divergences and link to the + statute or form instructions for each case. They disappear from this + list when closed. +

+ + {loading && ( +
+
+ Loading… +
+ )} + + {error && ( +
+ {error} +
+ )} + + {!loading && !error && issues.length === 0 && ( +
+ No open model-difference issues. +
+ )} + + {!loading && !error && issues.length > 0 && ( +
+ {issues.map((issue) => ( +
+ + #{issue.number} {issue.title} + + +
+ + + {issue.author} + + + + {formatDate(issue.created_at)} + +
+ {issue.labels.length > 0 && ( +
+ + {issue.labels.map((label, index) => ( + + {label} + + ))} +
+ )} + {issue.body && ( +
+ {issue.body.length > 200 + ? `${issue.body.substring(0, 200)}…` + : issue.body} +
+ )} +
+ ))} +
+ )} +
+ + ); +}; + +export default ModelDifferences; diff --git a/dashboard/src/utils/githubApi.js b/dashboard/src/utils/githubApi.js index 99c84f80..650cfadb 100644 --- a/dashboard/src/utils/githubApi.js +++ b/dashboard/src/utils/githubApi.js @@ -83,6 +83,15 @@ export const getIssuesForState = (issues, stateCode) => { }); }; +// Get issues with a specific label +export const getIssuesByLabel = (issues, labelName) => { + if (!labelName) return []; + + return issues.filter(issue => + issue.labels.some(label => label.name === labelName) + ); +}; + // Format issue data for display export const formatIssue = (issue) => { return { From fd5002f97d1071b54de41b1477ba07ffcaac1d85 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 17 Apr 2026 16:57:57 -0400 Subject: [PATCH 2/2] Add rebate timing convention to Modeling Differences PolicyEngine assigns one-time state rebates (e.g. GA HB 112, VA HB 1600) to the tax year whose liability determines the rebate amount, not the trigger year or payout year. References issues #716 (GA) and #718 (VA). Co-Authored-By: Claude Opus 4.7 (1M context) --- dashboard/src/components/ModelDifferences.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dashboard/src/components/ModelDifferences.jsx b/dashboard/src/components/ModelDifferences.jsx index 83f47fdc..703bfe29 100644 --- a/dashboard/src/components/ModelDifferences.jsx +++ b/dashboard/src/components/ModelDifferences.jsx @@ -63,6 +63,14 @@ const CONVENTIONS = [ detail: 'This can affect the reported amount of individual credits when the pre-credit tax liability is consumed by earlier-ordered credits.', }, + { + id: 'rebate-timing', + title: 'One-time rebate timing convention', + summary: + 'PolicyEngine assigns one-time state rebates to the tax year whose liability determines the rebate amount (the eligibility year), not the trigger year or payout year. TAXSIM conventions have varied by rebate.', + detail: + 'Examples: Georgia HB 112 surplus rebate (issue #716) and Virginia HB 1600 rebate (issue #718). Picking the eligibility year keeps the rebate reflected in the same liability that generated it.', + }, ]; const ModelDifferences = () => {