Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ yarn-error.log*
.idea

venv/*
bun.lock
59 changes: 59 additions & 0 deletions src/__tests__/layout/VersionBadge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import VersionBadge from "../../layout/VersionBadge";

describe("VersionBadge", () => {
test("renders a compact strip with the country package version and dataset", () => {
render(
<VersionBadge
countryId="us"
modelVersion="1.653.3"
dataset="enhanced_cps"
/>,
);
expect(
screen.getByLabelText(/versions used for this result/i),
).toHaveTextContent("policyengine-us@1.653.3 · enhanced_cps");
});

test("renders nothing when modelVersion is missing", () => {
const { container } = render(
<VersionBadge countryId="us" dataset="enhanced_cps" />,
);
expect(container).toBeEmptyDOMElement();
});

test("maps country ids to package names", () => {
render(
<VersionBadge countryId="uk" modelVersion="2.88.0" dataset="default" />,
);
expect(screen.getByLabelText(/versions used/i)).toHaveTextContent(
"policyengine-uk@2.88.0",
);
});

test("falls back to 'default' when dataset is not set", () => {
render(<VersionBadge countryId="us" modelVersion="1.653.3" />);
expect(screen.getByLabelText(/versions used/i)).toHaveTextContent(
"policyengine-us@1.653.3 · default",
);
});

test("includes data version and truncated h5 sha when provided", () => {
render(
<VersionBadge
countryId="us"
modelVersion="1.653.3"
dataset="enhanced_cps"
dataVersion="1.85.2"
h5Sha="abc1234567890fedcba1234567890fedcba1234567890fedcba1234567890abc"
/>,
);
const badge = screen.getByLabelText(/versions used/i);
expect(badge).toHaveTextContent("enhanced_cps@1.85.2");
expect(badge).toHaveTextContent("sha256:abc12345");
expect(badge).not.toHaveTextContent(
"fedcba1234567890fedcba1234567890fedcba1234567890abc",
);
});
});
139 changes: 139 additions & 0 deletions src/layout/VersionBadge.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { useState } from "react";
import { Popover } from "antd";
import style from "../style";

/**
* Small always-visible strip that identifies which model version and
* dataset produced a simulation result.
*
* See PolicyEngine/policyengine-app#2831 for motivation. This is the
* plain version-identification surface — the TRACE-bound "Cite this
* result" download is scoped separately in #2830 and blocked on the
* api work in PolicyEngine/policyengine-api#3485.
*
* Today the api metadata endpoint only exposes the country-package
* version and the selectable dataset name. Once the api migrates to
* policyengine.py v4 (api#3486) and begins returning
* policyengine-{country}-data version and h5 content hash on every
* simulation response, this component should read those additional
* fields and expand the visible badge.
*/
export default function VersionBadge(props) {
const {
countryId,
modelVersion,
dataset,
dataVersion,
h5Sha,
compact = false,
} = props;

const [popoverOpen, setPopoverOpen] = useState(false);

if (!modelVersion) {
return null;
}

const modelPackageName =
countryId === "us"
? "policyengine-us"
: countryId === "uk"
? "policyengine-uk"
: countryId === "canada"
? "policyengine-canada"
: `policyengine-${countryId}`;

// Human-readable dataset label. The api exposes canonical names like
// "enhanced_cps" or "cps" — surface them as-is so a reader who
// knows the pipeline can tell exactly what ran.
const datasetLabel = dataset || "default";

const compactStrip = (
<code
style={{
fontFamily: "monospace",
fontSize: compact ? "11px" : "12px",
color: style.colors.DARK_GRAY,
backgroundColor: style.colors.LIGHT_GRAY,
padding: "2px 6px",
borderRadius: "3px",
whiteSpace: "nowrap",
}}
aria-label={`Model and dataset versions used for this result`}
>
{`${modelPackageName}@${modelVersion} · ${datasetLabel}`}
{dataVersion && `@${dataVersion}`}
{h5Sha && ` · h5 sha256:${h5Sha.substring(0, 8)}…`}
</code>
);

const detailContent = (
<div style={{ maxWidth: 360, fontSize: 13, lineHeight: 1.5 }}>
<div style={{ marginBottom: 8 }}>
<strong>Model package</strong>
<div style={{ fontFamily: "monospace", fontSize: 12 }}>
{modelPackageName}=={modelVersion}
</div>
</div>
<div style={{ marginBottom: 8 }}>
<strong>Dataset</strong>
<div style={{ fontFamily: "monospace", fontSize: 12 }}>
{datasetLabel}
{dataVersion ? `@${dataVersion}` : ""}
</div>
</div>
{h5Sha && (
<div style={{ marginBottom: 8 }}>
<strong>h5 content hash</strong>
<div
style={{
fontFamily: "monospace",
fontSize: 11,
wordBreak: "break-all",
}}
>
sha256:{h5Sha}
</div>
</div>
)}
<div style={{ color: style.colors.DARK_GRAY, marginTop: 10 }}>
These identify the pinned software and microdata that produced this
result. A citable TRO that binds them under a SHA-256 composition
fingerprint is coming &mdash; see issue{" "}
<a
href="https://github.com/PolicyEngine/policyengine-app/issues/2830"
target="_blank"
rel="noopener noreferrer"
>
policyengine-app#2830
</a>
.
</div>
</div>
);

return (
<Popover
content={detailContent}
title="Reproducibility identifiers"
trigger="click"
open={popoverOpen}
onOpenChange={setPopoverOpen}
placement="top"
>
<span
role="button"
tabIndex={0}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setPopoverOpen(!popoverOpen);
}
}}
style={{ cursor: "pointer" }}
>
{compactStrip}
</span>
</Popover>
);
}
22 changes: 22 additions & 0 deletions src/pages/household/output/HouseholdOutput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PoliciesModelledPopup from "../../../modals/PoliciesModelledPopup";
import React from "react";
import { message } from "antd";
import ResultActions from "layout/ResultActions";
import VersionBadge from "../../../layout/VersionBadge";

export default function HouseholdOutput(props) {
const [searchParams] = useSearchParams();
Expand Down Expand Up @@ -131,6 +132,9 @@ export default function HouseholdOutput(props) {
const facebookLink = `https://www.facebook.com/sharer/sharer.php?u=${url}`;
const linkedInLink = `https://www.linkedin.com/sharing/share-offsite/?url=${url}`;

const selectedVersion = urlParams.get("version") || metadata.version;
const dataset = urlParams.get("dataset");

return (
<ResultsPanel>
<ResultActions
Expand All @@ -140,6 +144,24 @@ export default function HouseholdOutput(props) {
linkedInLink={linkedInLink}
/>
{pane}
{householdBaseline && !loading && (
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: 16,
paddingTop: 12,
borderTop: "1px solid #eee",
}}
>
<VersionBadge
countryId={metadata.countryId}
modelVersion={selectedVersion}
dataset={dataset}
compact
/>
</div>
)}
</ResultsPanel>
);
}
34 changes: 25 additions & 9 deletions src/pages/policy/output/Display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import PolicyBreakdown from "./PolicyBreakdown";
import { Helmet } from "react-helmet";
import useCountryId from "../../../hooks/useCountryId";
import BottomImpactDescription from "../../../layout/BottomImpactDescription";
import VersionBadge from "../../../layout/VersionBadge";
import { Link } from "react-router-dom";
import MultiYearBudgetaryImpact from "./budget/MultiYearBudgetaryImpact";

Expand Down Expand Up @@ -352,19 +353,34 @@ export function LowLevelDisplay(props) {
//eslint-disable-next-line
const bottomElements =
mobile & !embed ? null : (
<p
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
flexWrap: "wrap",
marginBottom: 0,
fontSize: "12px",
}}
>
{bottomText}
{bottomLink && (
<a href={bottomLink} target="_blank" rel="noreferrer">
Learn more
</a>
)}
</p>
<p
style={{
marginBottom: 0,
fontSize: "12px",
}}
>
{bottomText}
{bottomLink && (
<a href={bottomLink} target="_blank" rel="noreferrer">
Learn more
</a>
)}
</p>
<VersionBadge
countryId={metadata.countryId}
modelVersion={selectedVersion}
dataset={dataset}
/>
</div>
);

// If ?embed=True, just show `pane`, full screen.
Expand Down