Skip to content

Instantly share code, notes, and snippets.

@MaxGhenis
Last active May 13, 2026 20:14
Show Gist options
  • Select an option

  • Save MaxGhenis/ddf033f52eb51e6082bade87564f7b83 to your computer and use it in GitHub Desktop.

Select an option

Save MaxGhenis/ddf033f52eb51e6082bade87564f7b83 to your computer and use it in GitHub Desktop.
PolicyEngine Social Security microdata regression investigation

Social Security microdata regression investigation

Reproducibility appendix for the PolicyEngine postmortem on social_security and social_security_retirement being dropped or ignored in microsimulation.

Files:

  • social_security_microdata_regression_investigation.ipynb: executed notebook with source-control, GitHub Actions, and PyPI evidence.
  • reproduce_social_security_microdata_regression.py: plain Python script with the same checks.

The investigation is metadata-only: it does not download large H5 data files. It expects local clones of PolicyEngine/policyengine-us-data and PolicyEngine/policyengine-us; set US_DATA_REPO and US_REPO if needed.

"""Reproduce the Social Security microdata regression timeline.
Requirements:
- local clones of PolicyEngine/policyengine-us-data and PolicyEngine/policyengine-us
- git, gh, jq
- network access for PyPI metadata checks
Set US_DATA_REPO and US_REPO if your clones are in different locations.
"""
from __future__ import annotations
import json
import os
import re
import subprocess
import tarfile
import tempfile
import urllib.request
from datetime import datetime
from pathlib import Path
US_DATA = Path(os.environ.get("US_DATA_REPO", "/Users/maxghenis/PolicyEngine/policyengine-us-data"))
US = Path(os.environ.get("US_REPO", "/Users/maxghenis/PolicyEngine/policyengine-us"))
def run(cmd: str, cwd: Path | None = None) -> str:
result = subprocess.run(
cmd,
cwd=str(cwd) if cwd else None,
shell=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
)
return result.stdout.strip()
def git(repo: Path, args: str) -> str:
return run(f"git {args}", cwd=repo)
def policyengine_us_version_at(data_sha: str) -> str:
lock = git(US_DATA, f"show {data_sha}:uv.lock")
lines = lock.splitlines()
for idx, line in enumerate(lines):
if line.strip() == 'name = "policyengine-us"':
for following in lines[idx + 1 : idx + 8]:
m = re.search(r'version = "([^"]+)"', following)
if m:
return m.group(1)
return "not found"
def pyproject_version_at(repo: Path, sha: str) -> str:
pyproject = git(repo, f"show {sha}:pyproject.toml")
for line in pyproject.splitlines():
if line.startswith("version = "):
return line.split('"')[1]
return "not found"
def commit_row(repo: Path, sha: str) -> dict[str, str]:
out = git(repo, f"show --format='%cI%x09%h%x09%s' --no-patch {sha}")
date, short, subject = out.split("\t", 2)
return {"sha": short, "date": date, "subject": subject}
def has_retirement_formula_in_sdist(version: str) -> bool:
with urllib.request.urlopen(f"https://pypi.org/pypi/policyengine-us/{version}/json", timeout=30) as response:
payload = json.load(response)
sdist_url = next(
file["url"]
for file in payload["urls"]
if file["packagetype"] == "sdist"
)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / f"policyengine_us-{version}.tar.gz"
urllib.request.urlretrieve(sdist_url, path)
with tarfile.open(path) as tar:
member = next(
m
for m in tar.getmembers()
if m.name.endswith(
"policyengine_us/variables/gov/ssa/ss/social_security_retirement.py"
)
)
text = tar.extractfile(member).read().decode()
return "def formula(" in text
def duration(start: str, end: str) -> str:
delta = datetime.fromisoformat(end) - datetime.fromisoformat(start)
days = delta.days
hours, rem = divmod(delta.seconds, 3600)
minutes = rem // 60
return f"{days}d {hours}h {minutes}m"
def print_table(rows: list[dict[str, str]], columns: list[str]) -> None:
widths = {
col: max(len(col), *(len(str(row.get(col, ""))) for row in rows))
for col in columns
}
print(" | ".join(col.ljust(widths[col]) for col in columns))
print("-+-".join("-" * widths[col] for col in columns))
for row in rows:
print(" | ".join(str(row.get(col, "")).ljust(widths[col]) for col in columns))
def main() -> None:
print("Repositories")
print(f"policyengine-us-data: {US_DATA}")
print(f"policyengine-us: {US}")
print()
print("1. Key source-control events")
key_events = [
("policyengine-us-data", US_DATA, "7025f6a2", "merged formula/adds/subtracts export pruning"),
("policyengine-us-data", US_DATA, "6216d02c", "first data package update after #554"),
("policyengine-us-data", US_DATA, "dd3455a0", "QRF-impute CPS-only variables including SS subcomponents"),
("policyengine-us", US, "c5f1b5da1b", "made social_security_retirement formula-backed"),
("policyengine-us-data", US_DATA, "491ac09c", "first bad dependency lock, failed pipeline"),
("policyengine-us-data", US_DATA, "a2f3bb36", "first completed bad generated data run"),
("policyengine-us-data", US_DATA, "f14931eb", "first promoted/live bad data run found"),
("policyengine-us", US, "06a52825cf", "restored social_security_retirement as canonical input"),
("policyengine-us-data", US_DATA, "61a43e95", "first data package update after the fix"),
]
rows = []
for repo_name, repo, sha, note in key_events:
row = commit_row(repo, sha)
row["repo"] = repo_name
row["note"] = note
if repo_name == "policyengine-us-data":
row["data_version"] = pyproject_version_at(repo, sha)
row["policyengine_us"] = policyengine_us_version_at(sha)
else:
row["data_version"] = ""
row["policyengine_us"] = pyproject_version_at(repo, sha)
rows.append(row)
print_table(rows, ["date", "repo", "sha", "data_version", "policyengine_us", "note"])
print()
print("2. PyPI check: first policyengine-us release where social_security_retirement had a formula")
formula_rows = []
for version in ["1.642.0", "1.643.0", "1.644.0", "1.691.3"]:
formula_rows.append(
{
"policyengine_us": version,
"retirement_has_formula": str(has_retirement_formula_in_sdist(version)),
}
)
print_table(formula_rows, ["policyengine_us", "retirement_has_formula"])
print()
print("3. Incident windows used in the postmortem")
windows = [
{
"window": "total social_security direct-input risk",
"start": "2026-03-04T15:23:42+00:00",
"end": "2026-03-14T13:41:06+00:00",
"duration": duration(
"2026-03-04T15:23:42+00:00",
"2026-03-14T13:41:06+00:00",
),
"basis": "#554 data release -> #589 source fix",
},
{
"window": "retirement, generated artifacts",
"start": "2026-04-30T20:51:13+00:00",
"end": "2026-05-12T17:02:15+00:00",
"duration": duration(
"2026-04-30T20:51:13+00:00",
"2026-05-12T17:02:15+00:00",
),
"basis": "first completed bad run -> first fixed Run Pipeline",
},
{
"window": "retirement, live/promoted artifacts",
"start": "2026-05-04T03:08:33+00:00",
"end": "2026-05-12T17:02:15+00:00",
"duration": duration(
"2026-05-04T03:08:33+00:00",
"2026-05-12T17:02:15+00:00",
),
"basis": "first promoted bad run found -> first fixed Run Pipeline",
},
]
print_table(windows, ["window", "start", "end", "duration", "basis"])
print()
print("4. GitHub Actions evidence for first fixed data update")
runs = run(
"gh run list --repo PolicyEngine/policyengine-us-data "
"--workflow 'Run Pipeline' --created '2026-05-12..2026-05-13' "
"--limit 10 --json createdAt,conclusion,headSha,url "
"| jq -r '.[] | [.createdAt,.conclusion,.headSha,.url] | @tsv'"
)
print(runs)
print()
print("Conclusions")
print("- social_security was at risk when policyengine-us-data stopped exporting computed variables in #554.")
print("- The safer data contract is: export leaf inputs; calculate formula/adds/subtracts variables in policyengine-us.")
print("- That contract failed again when policyengine-us made social_security_retirement formula-backed without preserving the canonical input path.")
print("- The May 12 fix restored social_security_retirement as the canonical input and moved reported/data-only paths out of public formulas.")
if __name__ == "__main__":
main()
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": "# Social Security microdata regression investigation\n\nThis notebook captures the metadata-only investigation behind the PolicyEngine Social Security postmortem.\nIt avoids downloading H5 datasets; instead it reconstructs the incident timeline from local git history,\nGitHub Actions metadata, and PyPI source distributions.\n\nTo rerun on another machine, set `US_DATA_REPO` and `US_REPO` to local clones of\n`PolicyEngine/policyengine-us-data` and `PolicyEngine/policyengine-us`."
},
{
"cell_type": "code",
"metadata": {},
"source": "import os, sys\nprint(sys.version)\nprint('US_DATA_REPO=', os.environ.get('US_DATA_REPO', '/Users/maxghenis/PolicyEngine/policyengine-us-data'))\nprint('US_REPO=', os.environ.get('US_REPO', '/Users/maxghenis/PolicyEngine/policyengine-us'))",
"execution_count": 1,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": "3.9.6 (default, Apr 17 2026, 18:15:52) \n[Clang 21.0.0 (clang-2100.1.1.101)]\nUS_DATA_REPO= /Users/maxghenis/PolicyEngine/policyengine-us-data\nUS_REPO= /Users/maxghenis/PolicyEngine/policyengine-us\n"
}
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Reproducible investigation script\n\nThe cell below is the same Python code included as `reproduce_social_security_microdata_regression.py` in this gist."
},
{
"cell_type": "code",
"metadata": {},
"source": "\n\"\"\"Reproduce the Social Security microdata regression timeline.\n\nRequirements:\n - local clones of PolicyEngine/policyengine-us-data and PolicyEngine/policyengine-us\n - git, gh, jq\n - network access for PyPI metadata checks\n\nSet US_DATA_REPO and US_REPO if your clones are in different locations.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport re\nimport subprocess\nimport tarfile\nimport tempfile\nimport urllib.request\nfrom datetime import datetime\nfrom pathlib import Path\n\n\nUS_DATA = Path(os.environ.get(\"US_DATA_REPO\", \"/Users/maxghenis/PolicyEngine/policyengine-us-data\"))\nUS = Path(os.environ.get(\"US_REPO\", \"/Users/maxghenis/PolicyEngine/policyengine-us\"))\n\n\ndef run(cmd: str, cwd: Path | None = None) -> str:\n result = subprocess.run(\n cmd,\n cwd=str(cwd) if cwd else None,\n shell=True,\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.STDOUT,\n check=True,\n )\n return result.stdout.strip()\n\n\ndef git(repo: Path, args: str) -> str:\n return run(f\"git {args}\", cwd=repo)\n\n\ndef policyengine_us_version_at(data_sha: str) -> str:\n lock = git(US_DATA, f\"show {data_sha}:uv.lock\")\n lines = lock.splitlines()\n for idx, line in enumerate(lines):\n if line.strip() == 'name = \"policyengine-us\"':\n for following in lines[idx + 1 : idx + 8]:\n m = re.search(r'version = \"([^\"]+)\"', following)\n if m:\n return m.group(1)\n return \"not found\"\n\n\ndef pyproject_version_at(repo: Path, sha: str) -> str:\n pyproject = git(repo, f\"show {sha}:pyproject.toml\")\n for line in pyproject.splitlines():\n if line.startswith(\"version = \"):\n return line.split('\"')[1]\n return \"not found\"\n\n\ndef commit_row(repo: Path, sha: str) -> dict[str, str]:\n out = git(repo, f\"show --format='%cI%x09%h%x09%s' --no-patch {sha}\")\n date, short, subject = out.split(\"\\t\", 2)\n return {\"sha\": short, \"date\": date, \"subject\": subject}\n\n\ndef has_retirement_formula_in_sdist(version: str) -> bool:\n with urllib.request.urlopen(f\"https://pypi.org/pypi/policyengine-us/{version}/json\", timeout=30) as response:\n payload = json.load(response)\n sdist_url = next(\n file[\"url\"]\n for file in payload[\"urls\"]\n if file[\"packagetype\"] == \"sdist\"\n )\n with tempfile.TemporaryDirectory() as tmp:\n path = Path(tmp) / f\"policyengine_us-{version}.tar.gz\"\n urllib.request.urlretrieve(sdist_url, path)\n with tarfile.open(path) as tar:\n member = next(\n m\n for m in tar.getmembers()\n if m.name.endswith(\n \"policyengine_us/variables/gov/ssa/ss/social_security_retirement.py\"\n )\n )\n text = tar.extractfile(member).read().decode()\n return \"def formula(\" in text\n\n\ndef duration(start: str, end: str) -> str:\n delta = datetime.fromisoformat(end) - datetime.fromisoformat(start)\n days = delta.days\n hours, rem = divmod(delta.seconds, 3600)\n minutes = rem // 60\n return f\"{days}d {hours}h {minutes}m\"\n\n\ndef print_table(rows: list[dict[str, str]], columns: list[str]) -> None:\n widths = {\n col: max(len(col), *(len(str(row.get(col, \"\"))) for row in rows))\n for col in columns\n }\n print(\" | \".join(col.ljust(widths[col]) for col in columns))\n print(\"-+-\".join(\"-\" * widths[col] for col in columns))\n for row in rows:\n print(\" | \".join(str(row.get(col, \"\")).ljust(widths[col]) for col in columns))\n\n\ndef main() -> None:\n print(\"Repositories\")\n print(f\"policyengine-us-data: {US_DATA}\")\n print(f\"policyengine-us: {US}\")\n print()\n\n print(\"1. Key source-control events\")\n key_events = [\n (\"policyengine-us-data\", US_DATA, \"7025f6a2\", \"merged formula/adds/subtracts export pruning\"),\n (\"policyengine-us-data\", US_DATA, \"6216d02c\", \"first data package update after #554\"),\n (\"policyengine-us-data\", US_DATA, \"dd3455a0\", \"QRF-impute CPS-only variables including SS subcomponents\"),\n (\"policyengine-us\", US, \"c5f1b5da1b\", \"made social_security_retirement formula-backed\"),\n (\"policyengine-us-data\", US_DATA, \"491ac09c\", \"first bad dependency lock, failed pipeline\"),\n (\"policyengine-us-data\", US_DATA, \"a2f3bb36\", \"first completed bad generated data run\"),\n (\"policyengine-us-data\", US_DATA, \"f14931eb\", \"first promoted/live bad data run found\"),\n (\"policyengine-us\", US, \"06a52825cf\", \"restored social_security_retirement as canonical input\"),\n (\"policyengine-us-data\", US_DATA, \"61a43e95\", \"first data package update after the fix\"),\n ]\n rows = []\n for repo_name, repo, sha, note in key_events:\n row = commit_row(repo, sha)\n row[\"repo\"] = repo_name\n row[\"note\"] = note\n if repo_name == \"policyengine-us-data\":\n row[\"data_version\"] = pyproject_version_at(repo, sha)\n row[\"policyengine_us\"] = policyengine_us_version_at(sha)\n else:\n row[\"data_version\"] = \"\"\n row[\"policyengine_us\"] = pyproject_version_at(repo, sha)\n rows.append(row)\n print_table(rows, [\"date\", \"repo\", \"sha\", \"data_version\", \"policyengine_us\", \"note\"])\n print()\n\n print(\"2. PyPI check: first policyengine-us release where social_security_retirement had a formula\")\n formula_rows = []\n for version in [\"1.642.0\", \"1.643.0\", \"1.644.0\", \"1.691.3\"]:\n formula_rows.append(\n {\n \"policyengine_us\": version,\n \"retirement_has_formula\": str(has_retirement_formula_in_sdist(version)),\n }\n )\n print_table(formula_rows, [\"policyengine_us\", \"retirement_has_formula\"])\n print()\n\n print(\"3. Incident windows used in the postmortem\")\n windows = [\n {\n \"window\": \"total social_security direct-input risk\",\n \"start\": \"2026-03-04T15:23:42+00:00\",\n \"end\": \"2026-03-14T13:41:06+00:00\",\n \"duration\": duration(\n \"2026-03-04T15:23:42+00:00\",\n \"2026-03-14T13:41:06+00:00\",\n ),\n \"basis\": \"#554 data release -> #589 source fix\",\n },\n {\n \"window\": \"retirement, generated artifacts\",\n \"start\": \"2026-04-30T20:51:13+00:00\",\n \"end\": \"2026-05-12T17:02:15+00:00\",\n \"duration\": duration(\n \"2026-04-30T20:51:13+00:00\",\n \"2026-05-12T17:02:15+00:00\",\n ),\n \"basis\": \"first completed bad run -> first fixed Run Pipeline\",\n },\n {\n \"window\": \"retirement, live/promoted artifacts\",\n \"start\": \"2026-05-04T03:08:33+00:00\",\n \"end\": \"2026-05-12T17:02:15+00:00\",\n \"duration\": duration(\n \"2026-05-04T03:08:33+00:00\",\n \"2026-05-12T17:02:15+00:00\",\n ),\n \"basis\": \"first promoted bad run found -> first fixed Run Pipeline\",\n },\n ]\n print_table(windows, [\"window\", \"start\", \"end\", \"duration\", \"basis\"])\n print()\n\n print(\"4. GitHub Actions evidence for first fixed data update\")\n runs = run(\n \"gh run list --repo PolicyEngine/policyengine-us-data \"\n \"--workflow 'Run Pipeline' --created '2026-05-12..2026-05-13' \"\n \"--limit 10 --json createdAt,conclusion,headSha,url \"\n \"| jq -r '.[] | [.createdAt,.conclusion,.headSha,.url] | @tsv'\"\n )\n print(runs)\n print()\n\n print(\"Conclusions\")\n print(\"- social_security was at risk when policyengine-us-data stopped exporting computed variables in #554.\")\n print(\"- The safer data contract is: export leaf inputs; calculate formula/adds/subtracts variables in policyengine-us.\")\n print(\"- That contract failed again when policyengine-us made social_security_retirement formula-backed without preserving the canonical input path.\")\n print(\"- The May 12 fix restored social_security_retirement as the canonical input and moved reported/data-only paths out of public formulas.\")\n\n\nif __name__ == \"__main__\":\n main()\n",
"execution_count": 2,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Execute the investigation"
},
{
"cell_type": "code",
"metadata": {},
"source": "main()",
"execution_count": 3,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": "Repositories\npolicyengine-us-data: /Users/maxghenis/PolicyEngine/policyengine-us-data\npolicyengine-us: /Users/maxghenis/PolicyEngine/policyengine-us\n\n1. Key source-control events\ndate | repo | sha | data_version | policyengine_us | note \n--------------------------+----------------------+------------+--------------+-----------------+---------------------------------------------------------\n2026-03-04T10:23:08-05:00 | policyengine-us-data | 7025f6a2 | 1.71.1 | 1.587.0 | merged formula/adds/subtracts export pruning \n2026-03-04T15:23:42Z | policyengine-us-data | 6216d02c | 1.71.2 | 1.587.0 | first data package update after #554 \n2026-03-14T06:41:06-07:00 | policyengine-us-data | dd3455a0 | 1.73.0 | 1.587.0 | QRF-impute CPS-only variables including SS subcomponents\n2026-04-17T14:36:52-04:00 | policyengine-us | c5f1b5da1b | | 1.643.0 | made social_security_retirement formula-backed \n2026-04-29T21:04:17Z | policyengine-us-data | 491ac09c | 1.88.3 | 1.674.1 | first bad dependency lock, failed pipeline \n2026-04-30T20:47:53Z | policyengine-us-data | a2f3bb36 | 1.89.1 | 1.678.0 | first completed bad generated data run \n2026-05-04T03:05:29Z | policyengine-us-data | f14931eb | 1.90.1 | 1.678.0 | first promoted/live bad data run found \n2026-05-12T12:24:28-04:00 | policyengine-us | 06a52825cf | | 1.691.2 | restored social_security_retirement as canonical input \n2026-05-12T17:00:28Z | policyengine-us-data | 61a43e95 | 1.112.2 | 1.691.3 | first data package update after the fix \n\n2. PyPI check: first policyengine-us release where social_security_retirement had a formula\npolicyengine_us | retirement_has_formula\n----------------+-----------------------\n1.642.0 | False \n1.643.0 | False \n1.644.0 | True \n1.691.3 | False \n\n3. Incident windows used in the postmortem\nwindow | start | end | duration | basis \n----------------------------------------+---------------------------+---------------------------+-------------+---------------------------------------------------------\ntotal social_security direct-input risk | 2026-03-04T15:23:42+00:00 | 2026-03-14T13:41:06+00:00 | 9d 22h 17m | #554 data release -> #589 source fix \nretirement, generated artifacts | 2026-04-30T20:51:13+00:00 | 2026-05-12T17:02:15+00:00 | 11d 20h 11m | first completed bad run -> first fixed Run Pipeline \nretirement, live/promoted artifacts | 2026-05-04T03:08:33+00:00 | 2026-05-12T17:02:15+00:00 | 8d 13h 53m | first promoted bad run found -> first fixed Run Pipeline\n\n4. GitHub Actions evidence for first fixed data update\n2026-05-13T00:51:51Z\tsuccess\ta3370145c92c84b8f01873323650be2e98f1c1c7\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25771234091\n2026-05-12T18:29:29Z\tsuccess\ta7440e58f6016491620810e7b8b6b95bd3d4629b\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25754210398\n2026-05-12T17:42:58Z\tsuccess\td4871933cdd908d8f124c4fdee66e4d4821c7e9e\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25751771946\n2026-05-12T17:02:15Z\tsuccess\t61a43e952dceaa0194547ab3da34736f78962bdc\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25749650363\n2026-05-12T14:53:27Z\tsuccess\tc3a4624adf47486f26a76f5a1f52a0307c377b98\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25742516772\n2026-05-12T13:04:15Z\tsuccess\ta7e80740a8e8d84f39e0de66fabd4332bbf43053\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25736280595\n2026-05-12T11:49:31Z\tsuccess\t1ef3060e8eac6de49956a03cdb1563f7015c0481\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25732498265\n2026-05-12T07:11:16Z\tsuccess\t889ab4383fe77815be08ef0d8cd970bcdc0d9e74\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25719311417\n2026-05-12T06:39:51Z\tsuccess\t89cd9efe9e858d7de40c8421333009b567022bdd\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25717997086\n2026-05-12T02:59:02Z\tsuccess\t49445139f749a20176164b59e0ca4ff60d238895\thttps://github.com/PolicyEngine/policyengine-us-data/actions/runs/25710573876\n\nConclusions\n- social_security was at risk when policyengine-us-data stopped exporting computed variables in #554.\n- The safer data contract is: export leaf inputs; calculate formula/adds/subtracts variables in policyengine-us.\n- That contract failed again when policyengine-us made social_security_retirement formula-backed without preserving the canonical input path.\n- The May 12 fix restored social_security_retirement as the canonical input and moved reported/data-only paths out of public formulas.\n"
}
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
},
"generated_at": "2026-05-13T12:49:07.160798+00:00"
},
"nbformat": 4,
"nbformat_minor": 5
}
"""Plot total Social Security from H5 contents by data release date.
Two views:
1. Total $B at sim period 2024: stored aggregate vs subcomponent-sum (shows pre-#554 discontinuity)
2. Total $B at 2024, 2025, 2026 (uprating the subcomponent path with SSA COLA)
Annotates key events: #554 merge, #589 merge, #8040 model break, May 12 fix.
"""
from pathlib import Path
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
CSV = Path("/tmp/ss_sweep_results.csv")
OUT_PRIMARY = Path("/tmp/ss_by_release.png")
OUT_THREE_YEAR = Path("/tmp/ss_by_release_3yr.png")
# SSA COLA factors actually applied to benefits:
# - 2024 COLA = 3.2% (raised benefits paid in 2024 vs 2023)
# - 2025 COLA = 2.5% (raised benefits paid in 2025 vs 2024)
# Policyengine-us uprates SS benefits ~ COLA. Use the published 2025 COLA as 2024→2025,
# and a CBO-typical ~2.5% for 2025→2026. Note: approximate.
COLA_2024_TO_2025 = 1.025
COLA_2025_TO_2026 = 1.025
KEY_EVENTS = [
("2026-03-04T15:23Z", "#554 merged", "darkred"),
("2026-03-14T13:41Z", "#589 merged", "darkgreen"),
("2026-04-17T18:36Z", "policyengine-us#8040", "darkorange"),
("2026-05-12T17:02Z", "Fix promoted", "darkblue"),
]
def main():
df = pd.read_csv(CSV)
df = df[df["status"] == "ok"].copy()
df["date"] = pd.to_datetime(df["commit_date"])
df = df.sort_values("date")
print(f"{len(df)} ok rows from {df['date'].min()} to {df['date'].max()}")
# 1) primary: stored vs sum-of-subs at 2024
fig, ax = plt.subplots(figsize=(11, 5.5))
stored = df.copy()
stored.loc[stored["stored_ss_present"] == 0, "stored_ss_total_B"] = float("nan")
ax.plot(
stored["date"],
stored["stored_ss_total_B"],
marker="o",
markersize=4,
label="Stored social_security/2024 (only when present in H5)",
color="C3",
)
ax.plot(
df["date"],
df["subs_sum_total_B"],
marker="o",
markersize=4,
label="Sum of subcomponents/2024",
color="C0",
)
for ts, label, color in KEY_EVENTS:
d = pd.Timestamp(ts)
ax.axvline(d, color=color, linestyle="--", alpha=0.6)
ax.text(
d,
ax.get_ylim()[1] * 0.95 if ax.get_ylim()[1] > 0 else 1,
f" {label}",
color=color,
rotation=90,
va="top",
fontsize=8,
)
ax.set_title("Social Security in policyengine-us-data H5 by release date (sim period 2024)")
ax.set_ylabel("Total Social Security, weighted ($B)")
ax.set_xlabel("Release date (HF tag)")
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
fig.autofmt_xdate()
ax.grid(alpha=0.3)
ax.legend(loc="lower left")
fig.tight_layout()
fig.savefig(OUT_PRIMARY, dpi=140)
print(f"wrote {OUT_PRIMARY}")
# 2) three-year view (uprate subcomponent path)
df["ss_2024_effective_B"] = df.apply(
lambda r: r["stored_ss_total_B"] if r["stored_ss_present"] == 1 else r["subs_sum_total_B"],
axis=1,
)
df["ss_2025_implied_B"] = df["subs_sum_total_B"] * COLA_2024_TO_2025
df["ss_2026_implied_B"] = df["subs_sum_total_B"] * COLA_2024_TO_2025 * COLA_2025_TO_2026
fig, ax = plt.subplots(figsize=(11, 5.5))
ax.plot(df["date"], df["ss_2024_effective_B"], marker="o", markersize=4, label="2024 (sim base year): uses stored if present, else formula", color="C3")
ax.plot(df["date"], df["ss_2025_implied_B"], marker="o", markersize=4, label="2025 (sim formula × 2.5% COLA)", color="C0")
ax.plot(df["date"], df["ss_2026_implied_B"], marker="o", markersize=4, label="2026 (sim formula × 2.5% × 2.5%)", color="C2")
for ts, label, color in KEY_EVENTS:
d = pd.Timestamp(ts)
ax.axvline(d, color=color, linestyle="--", alpha=0.6)
ax.set_title("Implied total Social Security by sim year, by data release date")
ax.set_ylabel("Total Social Security, weighted ($B)")
ax.set_xlabel("Release date (HF tag)")
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
fig.autofmt_xdate()
ax.grid(alpha=0.3)
ax.legend(loc="lower left", fontsize=9)
fig.tight_layout()
fig.savefig(OUT_THREE_YEAR, dpi=140)
print(f"wrote {OUT_THREE_YEAR}")
if __name__ == "__main__":
main()
"""Sweep every HF tag of policyengine/policyengine-us-data, compute weighted Social Security
aggregates from the enhanced_cps_2024.h5, then delete the file before fetching the next.
Outputs /tmp/ss_sweep_results.csv with columns:
version, commit_date, n_persons, sum_weights,
stored_ss_present, stored_ss_recipients_M, stored_ss_total_B,
subs_sum_recipients_M, subs_sum_total_B,
retirement_B, disability_B, survivors_B, dependents_B,
retirement_present, disability_present, survivors_present, dependents_present
"""
import csv
import os
import sys
import time
import urllib.request
from pathlib import Path
import h5py
import numpy as np
H5_PATH = Path("/tmp/h5_samples/_current.h5")
RESULTS = Path("/tmp/ss_sweep_results.csv")
TAGS_CSV = Path("/tmp/us_data_tags.csv")
def parse_version_key(v):
try:
return tuple(int(p) for p in v.split("."))
except Exception:
return (0,)
def load_versions():
out = []
with TAGS_CSV.open() as f:
next(f)
for line in f:
parts = line.rstrip("\n").split(",")
ver, sha, date = parts[0], parts[1], parts[2]
if date.startswith("ERR") or date == "NOT_FOUND":
continue
out.append((ver, sha, date))
out.sort(key=lambda r: parse_version_key(r[0]))
return out
def download_h5(version, dest):
url = f"https://huggingface.co/policyengine/policyengine-us-data/resolve/{version}/enhanced_cps_2024.h5"
req = urllib.request.Request(url, headers={"User-Agent": "ss-sweep/1.0"})
with urllib.request.urlopen(req, timeout=180) as r, dest.open("wb") as out:
while True:
chunk = r.read(1 << 20)
if not chunk:
break
out.write(chunk)
SUBS = ["retirement", "disability", "survivors", "dependents"]
def analyze(path):
with h5py.File(path, "r") as f:
if "person_household_id/2024" not in f:
return None
person_hh = f["person_household_id/2024"][:]
hh_id = f["household_id/2024"][:]
hh_w = f["household_weight/2024"][:]
weight_map = dict(zip(hh_id.tolist(), hh_w.tolist()))
person_w = np.array([weight_map[int(h)] for h in person_hh], dtype=np.float64)
row = {
"n_persons": int(person_w.size),
"sum_weights": float(person_w.sum()),
}
stored_present = "social_security/2024" in f
row["stored_ss_present"] = int(stored_present)
if stored_present:
ss = f["social_security/2024"][:]
row["stored_ss_recipients_M"] = float(((ss > 0) * person_w).sum() / 1e6)
row["stored_ss_total_B"] = float((ss * person_w).sum() / 1e9)
else:
row["stored_ss_recipients_M"] = float("nan")
row["stored_ss_total_B"] = float("nan")
subs_sum = np.zeros_like(person_w)
for sub in SUBS:
key = f"social_security_{sub}/2024"
present = key in f
row[f"{sub}_present"] = int(present)
if present:
vals = f[key][:].astype(np.float64)
row[f"{sub}_B"] = float((vals * person_w).sum() / 1e9)
subs_sum += vals
else:
row[f"{sub}_B"] = float("nan")
row["subs_sum_recipients_M"] = float(((subs_sum > 0) * person_w).sum() / 1e6)
row["subs_sum_total_B"] = float((subs_sum * person_w).sum() / 1e9)
return row
def main():
H5_PATH.parent.mkdir(parents=True, exist_ok=True)
versions = load_versions()
print(f"{len(versions)} versions to process", flush=True)
cols = [
"version",
"commit_date",
"n_persons",
"sum_weights",
"stored_ss_present",
"stored_ss_recipients_M",
"stored_ss_total_B",
"subs_sum_recipients_M",
"subs_sum_total_B",
"retirement_B",
"disability_B",
"survivors_B",
"dependents_B",
"retirement_present",
"disability_present",
"survivors_present",
"dependents_present",
"status",
"error",
]
write_header = not RESULTS.exists()
fout = RESULTS.open("a", newline="")
w = csv.DictWriter(fout, fieldnames=cols)
if write_header:
w.writeheader()
done = set()
if not write_header:
with RESULTS.open() as f:
r = csv.DictReader(f)
for row in r:
if row["status"] == "ok":
done.add(row["version"])
print(f"resume: {len(done)} already processed", flush=True)
t0 = time.time()
for idx, (ver, sha, date) in enumerate(versions, 1):
if ver in done:
continue
row = {"version": ver, "commit_date": date, "status": "?", "error": ""}
try:
if H5_PATH.exists():
H5_PATH.unlink()
t = time.time()
download_h5(ver, H5_PATH)
dt_dl = time.time() - t
t = time.time()
res = analyze(H5_PATH)
dt_an = time.time() - t
if res is None:
row["status"] = "no_2024"
else:
row.update(res)
row["status"] = "ok"
print(
f"[{idx}/{len(versions)}] {ver} dl={dt_dl:.1f}s an={dt_an:.1f}s status={row['status']} stored={row.get('stored_ss_total_B')} subs={row.get('subs_sum_total_B')}",
flush=True,
)
except Exception as e:
row["status"] = "error"
row["error"] = str(e)[:200]
print(f"[{idx}/{len(versions)}] {ver} ERR {e}", flush=True)
finally:
try:
if H5_PATH.exists():
H5_PATH.unlink()
except Exception:
pass
w.writerow(row)
fout.flush()
fout.close()
print(f"DONE in {time.time()-t0:.0f}s. Output: {RESULTS}", flush=True)
if __name__ == "__main__":
main()
version commit_date n_persons sum_weights stored_ss_present stored_ss_recipients_M stored_ss_total_B subs_sum_recipients_M subs_sum_total_B retirement_B disability_B survivors_B dependents_B retirement_present disability_present survivors_present dependents_present status error
1.25.3 2025-05-27T00:06:07.000Z 101726 356118023.46653414 1 75.18176303217172 1540.9732745652466 74.53128755692224 1524.5483282707582 1333.9089254364962 190.63940283426223 0.0 0.0 1 1 1 1 ok
1.26.0 2025-06-09T11:52:51.000Z 101726 348644364.7796898 1 74.5980688530499 1520.7694444197052 72.17650021323185 1505.0066235352735 1302.2545419825738 202.75208155269962 0.0 0.0 1 1 1 1 ok
1.28.1 2025-06-12T18:12:12.000Z 101726 353491204.8724582 1 74.15303285685037 1539.80739029271 71.6292330911561 1511.6633386827298 1307.7572450185567 203.9060936641732 0.0 0.0 1 1 1 1 ok
1.28.2 2025-06-13T12:18:35.000Z 101726 356816545.11809874 1 77.04101911530176 1569.2574159869775 75.38451409775006 1574.6396795004828 1356.2423682931367 218.3973112073459 0.0 0.0 1 1 1 1 ok
1.28.4 2025-06-13T17:43:05.000Z 101726 361264926.32439643 1 74.14132366241707 1531.9804569284174 73.09183293935807 1516.6706858640716 1325.6491954938608 191.02149037021056 0.0 0.0 1 1 1 1 ok
1.29.0 2025-06-14T21:50:25.000Z 101726 354604400.2627739 1 73.20342186346457 1499.1131486701377 71.87296577065031 1480.5478617697104 1280.260544976099 200.28731679361135 0.0 0.0 1 1 1 1 ok
1.29.1 2025-06-18T11:19:14.000Z 101726 354387713.9631823 1 75.79403478378707 1550.240222538709 73.08817916526026 1512.8504637416183 1325.7045690183588 187.14589472325972 0.0 0.0 1 1 1 1 ok
1.30.0 2025-06-19T11:21:38.000Z 101726 354143836.71325946 1 73.9694055525508 1521.24807158798 73.08109156813961 1519.6888467890888 1320.3716335783618 199.31721321072672 0.0 0.0 1 1 1 1 ok
1.30.1 2025-06-19T11:25:29.000Z 101726 356509518.6583538 1 74.74111421647389 1530.8872938121267 73.21661093911713 1539.5342931550833 1346.7042209859706 192.830072169113 0.0 0.0 1 1 1 1 ok
1.30.2 2025-06-19T15:12:46.000Z 101726 356181902.40008146 1 74.07601193395863 1509.0528967980192 70.6179565826448 1480.2663090300803 1314.9609395588843 165.3053694711959 0.0 0.0 1 1 1 1 ok
1.31.0 2025-06-19T22:48:33.000Z 101726 352044794.5991116 1 75.6467200553696 1518.899690589845 72.30186669457402 1504.1092685679391 1297.2640408035058 206.84522776443342 0.0 0.0 1 1 1 1 ok
1.32.0 2025-06-28T13:26:50.000Z 101726 351781197.06310105 1 76.53607400891741 1518.7298731292674 72.76967580366775 1467.6780615644732 1275.4621459591726 192.21591560530035 0.0 0.0 1 1 1 1 ok
1.32.1 2025-07-01T17:39:51.000Z 101726 349087761.17170966 1 74.19884821021952 1503.427182142481 71.0747967778907 1442.073183318323 1271.8751681788294 170.19801513949366 0.0 0.0 1 1 1 1 ok
1.36.1 2025-07-03T09:40:51.000Z 101726 352085357.37828124 1 68.64720632891797 1383.1646945220366 68.60249429316103 1381.400002664405 1258.4157822958255 122.98422036857984 0.0 0.0 1 1 1 1 ok
1.36.2 2025-07-08T22:11:54.000Z 101726 355656966.544722 1 68.84178684388286 1408.2780411898023 68.83392548168558 1407.5217481716725 1272.435604200921 135.08614397075104 0.0 0.0 1 1 1 1 ok
1.37.0 2025-07-09T15:18:36.000Z 101726 357073491.61913747 1 69.07159761860997 1412.893682306525 69.03845513646203 1411.7766280475767 1282.1006093414107 129.67601870616602 0.0 0.0 1 1 1 1 ok
1.38.0 2025-07-16T01:19:19.000Z 101726 352909297.7033328 1 68.10216768261165 1408.3253652786739 68.10189549811173 1408.316318222463 1271.0943062237 137.22201199876335 0.0 0.0 1 1 1 1 ok
1.38.1 2025-07-17T20:25:49.000Z 101726 354850951.5338749 1 67.89103564781135 1391.3237224396487 67.89066266421324 1391.306315906671 1250.6490490470503 140.6572668596203 0.0 0.0 1 1 1 1 ok
1.39.0 2025-07-18T16:01:40.000Z 101726 353645636.7520567 1 68.4683129623422 1423.6846713816276 66.74361103163895 1387.2491310897458 1246.9154307278961 140.33370036184974 0.0 0.0 1 1 1 1 ok
1.39.2 2025-07-24T14:06:35.000Z 101726 351043459.6679572 1 68.80101263926285 1397.0553291772806 65.69136265993845 1329.147050383571 1204.5083114801957 124.63873890337513 0.0 0.0 1 1 1 1 ok
1.40.1 2025-07-24T14:38:40.000Z 101726 344949137.5693096 1 67.06182599239811 1366.4245798848797 63.37694584364548 1287.4265232265084 1157.0074392634506 130.41908396305763 0.0 0.0 1 1 1 1 ok
1.41.1 2025-07-26T19:30:12.000Z 101726 354150867.5706811 1 68.62578281160009 1410.2663039404554 66.01401442362946 1349.807611097202 1212.3252576784412 137.48235341876057 0.0 0.0 1 1 1 1 ok
1.41.2 2025-07-26T21:17:01.000Z 101726 350459608.21614105 1 66.19868128539153 1373.2975574712912 64.99366882968636 1343.9827336655328 1205.5295549371008 138.45317872843196 0.0 0.0 1 1 1 1 ok
1.42.0 2025-07-28T16:58:18.000Z 101726 351158197.87785554 1 68.26802944648564 1387.3484441177407 65.03384221389139 1336.6768103418171 1215.3926652184089 121.28414512340834 0.0 0.0 1 1 1 1 ok
1.42.3 2025-07-30T20:48:52.000Z 101726 347042360.549301 1 67.88141232051238 1372.8840599071552 65.1732849027443 1336.16861166469 1205.967613941626 130.20099772306386 0.0 0.0 1 1 1 1 ok
1.42.4 2025-07-30T22:15:52.000Z 101726 352989867.0189288 1 68.70729306500563 1384.375011679517 64.68263560003179 1322.3777573678 1194.9616254142763 127.41613195352393 0.0 0.0 1 1 1 1 ok
1.42.5 2025-07-30T23:05:34.000Z 55683 336193860.22363967 1 66.95932949704472 1403.2341029565864 63.824580508490975 1318.6884739246636 1181.6608737580414 137.02760016662222 0.0 0.0 1 1 1 1 ok
1.42.6 2025-08-01T11:54:56.000Z 55929 336229872.0856241 1 67.46439035701918 1433.2701349696001 64.99705050574406 1330.459221181915 1185.4768629593855 144.98235822252954 0.0 0.0 1 1 1 1 ok
1.43.0 2025-08-04T19:16:56.000Z 56031 334548308.1295147 1 67.30510484831326 1425.220311963644 62.421269042569996 1296.8471952056093 1171.4909688127564 125.35622639285305 0.0 0.0 1 1 1 1 ok
1.43.1 2025-08-06T19:30:14.000Z 53982 336774451.1101157 1 69.09775099164531 1440.0424077924229 64.73689168518864 1357.4956082455994 1236.6474250773172 120.84818316828223 0.0 0.0 1 1 1 1 ok
1.44.0 2025-08-06T19:30:24.000Z 54016 337527274.058625 1 68.76964324161432 1432.951378216563 64.22613520921804 1342.9687986440724 1226.8861317278847 116.08266691618768 0.0 0.0 1 1 1 1 ok
1.44.1 2025-08-08T10:48:31.000Z 54528 336798563.70178694 1 67.77561424697545 1453.1903674075734 63.808115009321185 1381.8113151915884 1243.2165169455538 138.59479824603454 0.0 0.0 1 1 1 1 ok
1.44.2 2025-08-08T15:45:16.000Z 54807 337404541.4531936 1 67.51322174491023 1423.412234499334 63.241140271250714 1347.7754762078073 1221.0962041933105 126.67927201449709 0.0 0.0 1 1 1 1 ok
1.45.0 2025-08-20T19:20:17.000Z 54189 337009130.46771705 1 69.43605369762358 1459.0558325131328 65.21672020042139 1374.6723683263413 1231.0225713714262 143.64979695491482 0.0 0.0 1 1 1 1 ok
1.46.0 2025-09-10T21:03:19.000Z 53430 333354898.4182364 1 67.64879138515964 1421.117299280751 64.88601799124454 1392.2606040001306 1224.1917951328862 168.06880886724423 0.0 0.0 1 1 1 1 ok
1.46.1 2025-11-12T20:39:59.000Z 54130 334946387.67741126 1 67.15385274961552 1455.6784892330538 64.13665498535477 1390.2244004997929 1212.451768787056 177.77263171273697 0.0 0.0 1 1 1 1 ok
1.47.0 2025-11-20T03:28:03.000Z 54693 337023583.2942041 1 67.37132312243676 1431.932348975347 64.4511212506802 1386.3956597476993 1199.0348603241855 187.36079942351392 0.0 0.0 1 1 1 1 ok
1.47.1 2025-12-03T23:54:04.000Z 55241 335574094.71613854 1 67.29985723835158 1435.9826222915121 64.38196401303396 1377.4680251413306 1189.8156596946133 187.6523654467171 0.0 0.0 1 1 1 1 ok
1.48.0 2025-12-08T21:40:15.000Z 54209 339049626.8956548 1 68.2090367110702 1465.674043983219 65.28778425817639 1423.0494090612326 1235.711076124149 187.3383329370835 0.0 0.0 1 1 1 1 ok
1.49.0 2025-12-19T19:21:56.000Z 54232 335420209.41714215 1 66.53323814665004 1446.1154309482215 63.381550383904475 1375.4687357701462 1197.2364359511803 178.2322998189662 0.0 0.0 1 1 1 1 ok
1.50.0 2025-12-23T16:48:49.000Z 54305 336404682.00986284 1 67.73726227896461 1426.3429532904936 65.12121207916921 1363.322502035063 1164.4839274733433 198.83857456171992 0.0 0.0 1 1 1 1 ok
1.51.0 2026-01-01T20:11:49.000Z 53722 335403590.4052346 1 68.21017801126109 1440.3982066448923 64.7110447388469 1370.424359427999 1171.2556837774225 199.1686756505765 0.0 0.0 1 1 1 1 ok
1.51.1 2026-01-07T02:40:48.000Z 52826 335766318.62622714 1 67.10411933682698 1409.9223084034463 63.91663890491204 1351.1466262583924 1168.4043629890496 182.74226326934277 0.0 0.0 1 1 1 1 ok
1.54.0 2026-01-25T19:17:57.000Z 53281 339546487.52022684 1 67.42742299743314 1422.2199087781305 65.3402376354212 1384.415384218183 1193.85781601666 190.5575682015225 0.0 0.0 1 1 1 1 ok
1.55.0 2026-01-26T17:59:18.000Z 54417 333680701.674548 1 66.58563082428161 1406.6437853303594 64.10838998379687 1351.2185763312148 1161.4709895584158 189.74758677279902 0.0 0.0 1 1 1 1 ok
1.56.0 2026-01-26T23:55:20.000Z 54417 333680701.674548 1 66.58563082428161 1406.6437853303594 64.10838998379687 1351.2185763312148 1161.4709895584158 189.74758677279902 0.0 0.0 1 1 1 1 ok
1.57.0 2026-01-31T04:22:31.000Z 158469 334596579.20650524 1 67.14016545672033 1400.2769200923085 65.25258264459987 1359.3607563616238 1176.9718106857713 182.38894567585265 0.0 0.0 1 1 1 1 ok
1.58.0 2026-01-31T20:58:11.000Z 158506 336049519.52483857 1 67.12330536542831 1399.854525980698 65.21331421861306 1358.1918928369307 1177.66324436728 180.5286484696507 0.0 0.0 1 1 1 1 ok
1.59.0 2026-01-31T20:51:17.000Z 157539 334586024.9492419 1 68.92112746083446 1464.7702338401032 67.91369494542194 1434.576769985609 1042.2241903012707 145.8170538846107 162.5676284123959 83.96789738733179 1 1 1 1 ok
1.60.0 2026-01-31T20:58:01.000Z 157542 333882111.0052895 1 68.32346934416721 1451.2530344815293 67.46565832299733 1421.9985571077436 1031.1842136236435 147.61210832263566 160.09745928687863 83.10477587458588 1 1 1 1 ok
1.61.0 2026-01-31T21:24:39.000Z 160147 332788563.2516105 1 68.18856526515837 1450.5998447647264 66.75277266744449 1419.8993239571512 1032.9282677435497 144.58153503038488 157.92331411184952 84.4662070713671 1 1 1 1 ok
1.61.2 2026-02-01T21:50:03.000Z 160646 333441090.333774 1 68.35452158751201 1450.5884423811856 66.98226463301191 1419.828709297384 1031.1372210632906 144.70976991529247 159.6760628706566 84.30565544814411 1 1 1 1 ok
1.62.0 2026-02-07T01:20:32.000Z 157667 332727419.8564606 1 67.767859856063 1438.8031933841917 67.3232634302281 1422.0371271409952 1030.593481870958 147.00689873695814 158.77748018126087 85.65926635181776 1 1 1 1 ok
1.63.0 2026-02-07T20:53:08.000Z 156892 334061352.09167004 1 68.39171313078454 1445.9961224035037 67.79580241488362 1424.5652465985102 1032.0527267973653 148.14978450950449 158.11372603752875 86.24900925411143 1 1 1 1 ok
1.63.1 2026-02-08T04:02:30.000Z 51175 335692945.4512296 1 68.20561455934178 1445.248772801207 68.1088106322198 1440.143078018888 1048.238490456776 148.76107663165934 158.58653189082415 84.55697903962839 1 1 1 1 ok
1.64.0 2026-02-08T05:12:50.000Z 49941 334301233.9133367 1 67.49428396418921 1427.9806325119011 67.12388945364042 1422.134060452216 1033.0263449515767 147.74257899251867 157.99543743910831 83.36969906901243 1 1 1 1 ok
1.64.1 2026-02-09T18:46:01.000Z 49935 336316366.25025284 1 68.05230530367446 1436.5870820740006 67.82162622222428 1430.7901497286914 1039.9049031384325 149.04525712256452 158.15444377958784 83.68554568810666 1 1 1 1 ok
1.65.0 2026-02-12T06:26:46.000Z 50148 335578773.30580693 1 67.63601101487139 1429.7709283731556 67.45171143747756 1425.065027943972 1035.5579310760102 148.3809958915224 157.47784147926447 83.64825949717509 1 1 1 1 ok
1.66.0 2026-02-12T17:38:37.000Z 49756 335007415.46464604 1 68.21322109047517 1438.0937870343325 67.89147784855489 1428.2772425140547 1039.0073353178568 148.1689743100584 158.230205697909 82.87072718823053 1 1 1 1 ok
1.67.0 2026-02-12T20:07:04.000Z 49925 335079291.95736104 1 67.44449615765087 1430.1971832796532 67.11934872722956 1425.7358949694376 1037.1816329422245 147.04358672489846 158.41768400355988 83.09299129875471 1 1 1 1 ok
1.68.0 2026-02-17T16:13:46.000Z 50204 335023196.0275054 1 67.84027589583383 1433.591580035698 67.38399736934615 1426.8165965609196 1037.2950524897567 148.42990459352546 157.80786185075493 83.28377762688272 1 1 1 1 ok
1.69.0 2026-02-18T22:11:06.000Z 44206 334928562.39900804 1 33.82557476587603 824.4948343342854 66.6287548656856 1451.159164145352 1061.9615587688443 147.6677665713329 158.1558229105456 83.374015894629 1 1 1 1 ok
1.69.1 2026-02-19T07:02:39.000Z 44159 335294471.3443033 1 33.700224898113234 822.8333796404671 67.00595464062397 1444.0559276872077 1054.1968874849415 148.15014120928714 157.6764425245499 84.03245646842923 1 1 1 1 ok
1.69.2 2026-02-19T15:59:37.000Z 284250 333871605.0729275 1 33.676839404254395 809.3915603866806 67.48274002991555 1432.6506179753983 1046.2723943106892 147.5046101569288 156.66267714487964 82.2109363629007 1 1 1 1 ok
1.69.3 2026-02-19T18:03:02.000Z 43240 333917525.052158 1 33.67304567742545 808.8371688207056 67.03713099503436 1436.8607359139266 1050.8140652389707 147.1957719254226 156.62139591912884 82.22950283040447 1 1 1 1 ok
1.69.4 2026-02-24T12:07:07.000Z 43279 333211995.0581193 1 33.487459926008746 805.9980059597149 66.8129214093007 1429.8927287281028 1043.9802878384132 146.4950481823368 157.81365607196437 81.60373663538819 1 1 1 1 ok
1.70.0 2026-02-26T03:32:23.000Z 43236 334087453.8296997 1 33.59359258530246 808.0112117290471 66.892291237129 1434.0016912483952 1047.7756604972653 147.11284290096256 156.75390201648338 82.35928583368417 1 1 1 1 ok
1.71.1 2026-03-04T14:45:44.000Z 44748 333911727.8955541 1 33.891858534710344 809.0620682844383 65.56641913855037 1436.4187147002988 1048.2623951445653 147.52532505098284 157.41006667145646 83.2209278332945 1 1 1 1 ok
1.71.2 2026-03-04T16:56:09.000Z 19549 343980043.1172986 0 nan nan 59.924402157912084 1382.6439806066337 998.6815390148314 174.58314963814124 136.53528238528347 72.84400956837786 1 1 1 1 ok
1.71.3 2026-03-04T21:55:16.000Z 19450 343219948.0601027 0 nan nan 61.23954610063633 1387.3028594856926 1003.6594247026724 178.06118074537594 133.7805068788598 71.80174715878465 1 1 1 1 ok
1.71.4 2026-03-04T23:45:06.000Z 284250 345113115.6640005 0 nan nan 63.31791882450462 1402.5457754987842 1020.328295964828 167.32400770019294 139.02067084615854 75.87280098760455 1 1 1 1 ok
1.72.3 2026-03-10T04:12:12.000Z 101384 336148000.2838849 0 nan nan 67.19062969027858 1441.6670554730015 1051.0020092636996 150.94699282048563 156.74650288102472 82.9715505077915 1 1 1 1 ok
1.73.0 2026-03-12T18:55:29.000Z 101384 335857214.2305056 0 nan nan 66.20108423888 1449.3796854686582 1060.230008983388 151.2479285687733 155.40104323016467 82.50070468633203 1 1 1 1 ok
1.74.0 2026-04-03T14:38:07.000Z 101384 335467199.0293275 0 nan nan 64.0213251023007 1466.2729783438763 1079.0801247130971 148.07028743152978 155.7906580281442 83.33190817110507 1 1 1 1 ok
1.74.1 2026-04-03T13:56:33.000Z 101384 335467199.0293275 0 nan nan 64.0213251023007 1466.2729783438763 1079.0801247130971 148.07028743152978 155.7906580281442 83.33190817110507 1 1 1 1 ok
1.74.2 2026-04-04T14:30:18.000Z 101384 336582143.09864753 0 nan nan 64.27987101235627 1453.6669144219388 1065.3839222342685 147.98543148602275 156.28733169018153 84.01022901146604 1 1 1 1 ok
1.74.3 2026-04-07T19:45:29.000Z 101384 334975126.86086035 0 nan nan 63.69626993735162 1447.7281506957859 1061.8598199809883 147.6578771675772 155.07875279225667 83.1317007549637 1 1 1 1 ok
1.75.0 2026-04-08T16:57:01.000Z 101384 336062115.5259162 0 nan nan 64.34932090706177 1472.8555307715842 1084.7826667223824 148.14471965078107 156.29130290871825 83.63684148970275 1 1 1 1 ok
1.75.1 2026-04-09T03:48:30.000Z 101384 333819374.62224483 0 nan nan 62.32323231355859 1429.6853671900567 1047.1310023738017 145.9856097004077 153.58877803071002 82.97997708513704 1 1 1 1 ok
1.75.2 2026-04-09T03:41:06.000Z 101384 335358638.1331346 0 nan nan 64.46137830907962 1463.4164035752551 1076.2113743932784 147.94181559553869 155.59053812080694 83.67267546563112 1 1 1 1 ok
1.75.3 2026-04-09T03:23:51.000Z 101384 333819374.62224483 0 nan nan 62.32323231355859 1429.6853671900567 1047.1310023738017 145.9856097004077 153.58877803071002 82.97997708513704 1 1 1 1 ok
1.75.4 2026-04-09T04:14:48.000Z 101384 335358638.1331346 0 nan nan 64.46137830907962 1463.4164035752551 1076.2113743932784 147.94181559553869 155.59053812080694 83.67267546563112 1 1 1 1 ok
1.75.5 2026-04-09T13:35:24.000Z 101384 336083030.7295989 0 nan nan 64.5434327364349 1476.1232878605283 1087.4751336000377 148.29118201051364 156.783275600727 83.57369664924991 1 1 1 1 ok
1.75.6 2026-04-09T19:08:37.000Z 101384 336072788.00089 0 nan nan 64.45548059583841 1475.2759295678004 1088.2230747441404 148.0070805392315 155.57089409735192 83.47488018707637 1 1 1 1 ok
1.75.7 2026-04-10T03:01:22.000Z 101384 334534783.5997416 0 nan nan 63.45299157716789 1446.797784175054 1063.025489607201 146.6025390929455 154.1952697697459 82.97448570516175 1 1 1 1 ok
1.75.8 2026-04-10T15:53:04.000Z 101384 335033527.0580803 0 nan nan 64.35377667628792 1475.1788007985633 1087.9112079018096 148.63271278072085 155.64360269204207 82.99127742399075 1 1 1 1 ok
1.76.0 2026-04-10T20:35:24.000Z 101384 334920879.69054824 0 nan nan 63.56510965584421 1448.4028229972866 1061.7610052522004 147.75393026574093 155.75736728589783 83.13052019344755 1 1 1 1 ok
1.78.0 2026-04-12T02:43:29.000Z 101384 334906388.9560204 0 nan nan 64.13468124814385 1476.9824837062151 1089.9718189744353 148.31400635755458 155.40437728627282 83.29228108795219 1 1 1 1 ok
1.78.1 2026-04-12T22:43:47.000Z 101384 334719406.92393947 0 nan nan 64.05588612781602 1472.9760546724472 1086.1758465946768 148.4260177874934 155.0593311785 83.31485911177722 1 1 1 1 ok
1.78.2 2026-04-12T21:59:56.000Z 101384 334719406.92393947 0 nan nan 64.05588612781602 1472.9760546724472 1086.1758465946768 148.4260177874934 155.0593311785 83.31485911177722 1 1 1 1 ok
1.90.1 2026-05-04T17:06:39.000Z 101384 334698066.0388521 0 nan nan 28.16504249555555 544.6508951461363 nan 226.85706718920312 218.27012385948314 99.52370409745012 0 1 1 1 ok
1.110.12 2026-05-11T18:56:49.000Z 101384 337835734.37285745 0 nan nan 25.73140802952223 507.5960818788916 nan 241.47235928031472 177.44938605929238 88.67433653928448 0 1 1 1 ok
1.113.1 2026-05-13T03:56:37.000Z 101384 335653612.83335036 0 66.0821974667698 1549.6196964477028 1178.8151985906234 145.48962362883384 145.743517017344 79.57135721090174 1 1 1 1 ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment