From 18e6f52e8ec6ce51ddd3f2dce025e7010ad8cfc1 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Mon, 23 Mar 2026 23:31:57 +0100 Subject: [PATCH 01/10] Add interactive pipeline diagrams to docs React + ReactFlow app visualizing all 9 pipeline stages (0-8) plus an Overview. Uses ELK.js for automatic graph layout with orthogonal edge routing. Each stage shows inputs, processes, outputs, utilities, and external integrations as color-coded nodes. Co-Authored-By: Claude Opus 4.6 --- docs/pipeline-diagrams/.gitignore | 24 + docs/pipeline-diagrams/README.md | 16 + docs/pipeline-diagrams/eslint.config.js | 29 + docs/pipeline-diagrams/index.html | 13 + docs/pipeline-diagrams/package-lock.json | 3158 +++++++++++++++++ docs/pipeline-diagrams/package.json | 32 + docs/pipeline-diagrams/public/favicon.svg | 1 + docs/pipeline-diagrams/public/icons.svg | 24 + docs/pipeline-diagrams/src/App.jsx | 34 + docs/pipeline-diagrams/src/assets/hero.png | Bin 0 -> 44919 bytes docs/pipeline-diagrams/src/assets/react.svg | 1 + docs/pipeline-diagrams/src/assets/vite.svg | 1 + .../src/components/DiagramPage.jsx | 87 + .../src/components/Sidebar.jsx | 102 + .../src/components/edges/ElkEdge.jsx | 90 + .../src/components/nodes/CustomNodes.jsx | 191 + .../src/constants/edgeStyles.js | 96 + .../src/hooks/useElkLayout.js | 163 + docs/pipeline-diagrams/src/index.css | 13 + docs/pipeline-diagrams/src/main.jsx | 13 + .../pipeline-diagrams/src/stages/Overview.jsx | 244 ++ docs/pipeline-diagrams/src/stages/Stage0.jsx | 366 ++ docs/pipeline-diagrams/src/stages/Stage1.jsx | 574 +++ docs/pipeline-diagrams/src/stages/Stage2.jsx | 377 ++ docs/pipeline-diagrams/src/stages/Stage3.jsx | 402 +++ docs/pipeline-diagrams/src/stages/Stage4.jsx | 376 ++ docs/pipeline-diagrams/src/stages/Stage5.jsx | 573 +++ docs/pipeline-diagrams/src/stages/Stage6.jsx | 430 +++ docs/pipeline-diagrams/src/stages/Stage7.jsx | 572 +++ docs/pipeline-diagrams/src/stages/Stage8.jsx | 488 +++ docs/pipeline-diagrams/vite.config.js | 7 + 31 files changed, 8497 insertions(+) create mode 100644 docs/pipeline-diagrams/.gitignore create mode 100644 docs/pipeline-diagrams/README.md create mode 100644 docs/pipeline-diagrams/eslint.config.js create mode 100644 docs/pipeline-diagrams/index.html create mode 100644 docs/pipeline-diagrams/package-lock.json create mode 100644 docs/pipeline-diagrams/package.json create mode 100644 docs/pipeline-diagrams/public/favicon.svg create mode 100644 docs/pipeline-diagrams/public/icons.svg create mode 100644 docs/pipeline-diagrams/src/App.jsx create mode 100644 docs/pipeline-diagrams/src/assets/hero.png create mode 100644 docs/pipeline-diagrams/src/assets/react.svg create mode 100644 docs/pipeline-diagrams/src/assets/vite.svg create mode 100644 docs/pipeline-diagrams/src/components/DiagramPage.jsx create mode 100644 docs/pipeline-diagrams/src/components/Sidebar.jsx create mode 100644 docs/pipeline-diagrams/src/components/edges/ElkEdge.jsx create mode 100644 docs/pipeline-diagrams/src/components/nodes/CustomNodes.jsx create mode 100644 docs/pipeline-diagrams/src/constants/edgeStyles.js create mode 100644 docs/pipeline-diagrams/src/hooks/useElkLayout.js create mode 100644 docs/pipeline-diagrams/src/index.css create mode 100644 docs/pipeline-diagrams/src/main.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Overview.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage0.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage1.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage2.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage3.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage4.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage5.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage6.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage7.jsx create mode 100644 docs/pipeline-diagrams/src/stages/Stage8.jsx create mode 100644 docs/pipeline-diagrams/vite.config.js diff --git a/docs/pipeline-diagrams/.gitignore b/docs/pipeline-diagrams/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/docs/pipeline-diagrams/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/docs/pipeline-diagrams/README.md b/docs/pipeline-diagrams/README.md new file mode 100644 index 000000000..a36934d87 --- /dev/null +++ b/docs/pipeline-diagrams/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/docs/pipeline-diagrams/eslint.config.js b/docs/pipeline-diagrams/eslint.config.js new file mode 100644 index 000000000..4fa125da2 --- /dev/null +++ b/docs/pipeline-diagrams/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/docs/pipeline-diagrams/index.html b/docs/pipeline-diagrams/index.html new file mode 100644 index 000000000..34d598ec0 --- /dev/null +++ b/docs/pipeline-diagrams/index.html @@ -0,0 +1,13 @@ + + + + + + + pipeline-diagrams + + +
+ + + diff --git a/docs/pipeline-diagrams/package-lock.json b/docs/pipeline-diagrams/package-lock.json new file mode 100644 index 000000000..9d894fccc --- /dev/null +++ b/docs/pipeline-diagrams/package-lock.json @@ -0,0 +1,3158 @@ +{ + "name": "pipeline-diagrams", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pipeline-diagrams", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/vite": "^4.2.2", + "@xyflow/react": "^12.10.1", + "elkjs": "^0.11.1", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.13.1", + "tailwindcss": "^4.2.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "vite": "^8.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", + "integrity": "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.75", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.75.tgz", + "integrity": "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/elkjs": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", + "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", + "license": "EPL-2.0" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/docs/pipeline-diagrams/package.json b/docs/pipeline-diagrams/package.json new file mode 100644 index 000000000..df97ffb4a --- /dev/null +++ b/docs/pipeline-diagrams/package.json @@ -0,0 +1,32 @@ +{ + "name": "pipeline-diagrams", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.2.2", + "@xyflow/react": "^12.10.1", + "elkjs": "^0.11.1", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.13.1", + "tailwindcss": "^4.2.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "vite": "^8.0.1" + } +} diff --git a/docs/pipeline-diagrams/public/favicon.svg b/docs/pipeline-diagrams/public/favicon.svg new file mode 100644 index 000000000..6893eb132 --- /dev/null +++ b/docs/pipeline-diagrams/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/pipeline-diagrams/public/icons.svg b/docs/pipeline-diagrams/public/icons.svg new file mode 100644 index 000000000..e9522193d --- /dev/null +++ b/docs/pipeline-diagrams/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/pipeline-diagrams/src/App.jsx b/docs/pipeline-diagrams/src/App.jsx new file mode 100644 index 000000000..2cdc3af51 --- /dev/null +++ b/docs/pipeline-diagrams/src/App.jsx @@ -0,0 +1,34 @@ +import { Routes, Route } from "react-router-dom"; +import Sidebar from "./components/Sidebar"; +import Overview from "./stages/Overview"; +import Stage0 from "./stages/Stage0"; +import Stage1 from "./stages/Stage1"; +import Stage2 from "./stages/Stage2"; +import Stage3 from "./stages/Stage3"; +import Stage4 from "./stages/Stage4"; +import Stage5 from "./stages/Stage5"; +import Stage6 from "./stages/Stage6"; +import Stage7 from "./stages/Stage7"; +import Stage8 from "./stages/Stage8"; + +export default function App() { + return ( +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+ ); +} diff --git a/docs/pipeline-diagrams/src/assets/hero.png b/docs/pipeline-diagrams/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..cc51a3d20ad4bc961b596a6adfd686685cd84bb0 GIT binary patch literal 44919 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 JPDHLkV1myZcL)Fg literal 0 HcmV?d00001 diff --git a/docs/pipeline-diagrams/src/assets/react.svg b/docs/pipeline-diagrams/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/docs/pipeline-diagrams/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/pipeline-diagrams/src/assets/vite.svg b/docs/pipeline-diagrams/src/assets/vite.svg new file mode 100644 index 000000000..5101b674d --- /dev/null +++ b/docs/pipeline-diagrams/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/docs/pipeline-diagrams/src/components/DiagramPage.jsx b/docs/pipeline-diagrams/src/components/DiagramPage.jsx new file mode 100644 index 000000000..053f68e6a --- /dev/null +++ b/docs/pipeline-diagrams/src/components/DiagramPage.jsx @@ -0,0 +1,87 @@ +import { + ReactFlow, + Controls, + Background, + ReactFlowProvider, + useReactFlow, +} from "@xyflow/react"; +import { useEffect, useRef } from "react"; +import { nodeTypes } from "./nodes/CustomNodes"; +import ElkEdge from "./edges/ElkEdge"; +import useElkLayout from "../hooks/useElkLayout"; + +const edgeTypes = { elk: ElkEdge }; + +function DiagramInner({ title, nodes: initialNodes, edges: initialEdges }) { + const { nodes, edges, layoutDone } = useElkLayout( + initialNodes, + initialEdges, + ); + const { fitView } = useReactFlow(); + const hasFit = useRef(false); + + useEffect(() => { + if (layoutDone && !hasFit.current) { + hasFit.current = true; + // Give ReactFlow a frame to render positioned nodes + requestAnimationFrame(() => { + fitView({ padding: 0.15 }); + }); + } + }, [layoutDone, fitView]); + + if (!layoutDone) { + return ( +
+
+

{title}

+
+
+ Computing layout… +
+
+ ); + } + + return ( +
+
+

{title}

+
+
+ + + + +
+
+ ); +} + +export default function DiagramPage({ title, nodes, edges }) { + return ( + + + + ); +} diff --git a/docs/pipeline-diagrams/src/components/Sidebar.jsx b/docs/pipeline-diagrams/src/components/Sidebar.jsx new file mode 100644 index 000000000..054a7c4e5 --- /dev/null +++ b/docs/pipeline-diagrams/src/components/Sidebar.jsx @@ -0,0 +1,102 @@ +import { NavLink } from "react-router-dom"; +import { EDGE_LEGEND } from "../constants/edgeStyles"; + +const stages = [ + { path: "/", label: "Overview", desc: "Cross-stage data flow" }, + { path: "/stage/0", label: "Stage 0", desc: "Raw Data Download" }, + { path: "/stage/1", label: "Stage 1", desc: "Base Dataset Construction" }, + { path: "/stage/2", label: "Stage 2", desc: "Extended CPS (PUF Clone)" }, + { path: "/stage/3", label: "Stage 3", desc: "Stratified CPS" }, + { path: "/stage/4", label: "Stage 4", desc: "Source Imputation" }, + { path: "/stage/5", label: "Stage 5", desc: "Matrix Build" }, + { path: "/stage/6", label: "Stage 6", desc: "Weight Fitting (L0)" }, + { path: "/stage/7", label: "Stage 7", desc: "Local Area H5 Build" }, + { path: "/stage/8", label: "Stage 8", desc: "Validation & Promotion" }, +]; + +const nodeLegend = [ + { color: "bg-blue-200 border-blue-400", label: "Input" }, + { color: "bg-green-200 border-green-500", label: "Output" }, + { color: "bg-orange-200 border-orange-400", label: "Process" }, + { color: "bg-purple-200 border-purple-400", label: "Utility" }, + { color: "bg-red-200 border-red-300 border-dashed", label: "Missing" }, + { color: "bg-yellow-200 border-yellow-400", label: "External" }, + { color: "bg-pink-200 border-pink-400", label: "US-Specific" }, +]; + +export default function Sidebar() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/components/edges/ElkEdge.jsx b/docs/pipeline-diagrams/src/components/edges/ElkEdge.jsx new file mode 100644 index 000000000..e7a116553 --- /dev/null +++ b/docs/pipeline-diagrams/src/components/edges/ElkEdge.jsx @@ -0,0 +1,90 @@ +import { EdgeLabelRenderer, getSmoothStepPath } from "@xyflow/react"; + +/** + * Custom edge component that renders using ELK's computed bend points + * when available, falling back to smoothstep for edges without routes. + */ +export default function ElkEdge({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + style = {}, + markerEnd, + label, + animated, + data, +}) { + let edgePath; + let labelX; + let labelY; + + if (data?.elkRoute) { + const { startPoint, endPoint, bendPoints = [] } = data.elkRoute; + const points = [startPoint, ...bendPoints, endPoint]; + + edgePath = points + .map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`) + .join(" "); + + // Label at midpoint of the path + const mid = Math.floor(points.length / 2); + labelX = points[mid].x; + labelY = points[mid].y; + } else { + // Fallback to smoothstep + const [path, lx, ly] = getSmoothStepPath({ + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + }); + edgePath = path; + labelX = lx; + labelY = ly; + } + + return ( + <> + + {label && ( + +
+ + {label} + +
+
+ )} + + ); +} diff --git a/docs/pipeline-diagrams/src/components/nodes/CustomNodes.jsx b/docs/pipeline-diagrams/src/components/nodes/CustomNodes.jsx new file mode 100644 index 000000000..0acc92a5b --- /dev/null +++ b/docs/pipeline-diagrams/src/components/nodes/CustomNodes.jsx @@ -0,0 +1,191 @@ +import { Handle, Position } from "@xyflow/react"; + +const baseStyle = + "rounded-lg shadow-sm px-3 py-2 text-xs min-w-[180px] max-w-[280px]"; + +const handleClass = "!w-2 !h-2 !bg-gray-400"; + +/** + * All nodes get 8 handles — source + target on each of 4 sides. + * Edge definitions use sourceHandle / targetHandle to pick a specific one. + * + * Handle ID convention: + * "sr" = source-right, "tl" = target-left (horizontal flow →) + * "sb" = source-bottom, "tt" = target-top (vertical flow ↓) + * "st" = source-top, "tb" = target-bottom (upward flow ↑) + * "sl" = source-left, "tr" = target-right (leftward flow ←) + */ +function AllHandles() { + return ( + <> + + + + + + + + + + ); +} + +function NodeShell({ children, bg, border, dashed, selected }) { + return ( +
+ {children} + +
+ ); +} + +function NodeBody({ data }) { + return ( + <> +
+ {data.label} +
+ {data.subtitle && ( +
+ {data.subtitle} +
+ )} + {data.details && ( +
    + {data.details.map((d, i) => ( +
  • {d}
  • + ))} +
+ )} + + ); +} + +export function InputNode({ data, selected }) { + return ( + +
+ Input +
+ +
+ ); +} + +export function OutputNode({ data, selected }) { + return ( + +
+ Output +
+ +
+ ); +} + +export function ProcessNode({ data, selected }) { + return ( + +
+ Process +
+ +
+ ); +} + +export function UtilityNode({ data, selected }) { + return ( + +
+ Utility +
+ +
+ ); +} + +export function MissingNode({ data, selected }) { + return ( + +
+ Missing Utility +
+ +
+ ); +} + +export function ExternalNode({ data, selected }) { + return ( + +
+ External +
+ +
+ ); +} + +export function USSpecificNode({ data, selected }) { + return ( + +
+ US-Specific +
+ +
+ ); +} + +export function DataTableNode({ data }) { + return ( +
+ +
+ {data.label} +
+ + + + {data.columns.map((col, i) => ( + + ))} + + + + {data.rows.map((row, i) => ( + + {row.map((cell, j) => ( + + ))} + + ))} + +
+ {col} +
+ {cell} +
+
+ ); +} + +export const nodeTypes = { + input_data: InputNode, + output_data: OutputNode, + process: ProcessNode, + utility: UtilityNode, + missing: MissingNode, + external: ExternalNode, + us_specific: USSpecificNode, + data_table: DataTableNode, +}; diff --git a/docs/pipeline-diagrams/src/constants/edgeStyles.js b/docs/pipeline-diagrams/src/constants/edgeStyles.js new file mode 100644 index 000000000..05be27f8e --- /dev/null +++ b/docs/pipeline-diagrams/src/constants/edgeStyles.js @@ -0,0 +1,96 @@ +/** + * Shared edge style presets for pipeline diagrams. + * + * Each preset defines a visual style (color, dash, width) and + * a default label so the relationship type is immediately clear. + * + * Usage in stage files: + * import { dataFlow, producesArtifact, usesUtility } from "../constants/edgeStyles"; + * const edges = [ + * { id: "e1", source: "a", target: "b", ...dataFlow }, + * { id: "e2", source: "b", target: "c", ...producesArtifact }, + * ]; + * + * Override the default label by placing `label` AFTER the spread: + * { id: "e3", source: "a", target: "b", ...dataFlow, label: "custom label" } + */ + +// Solid dark slate — primary pipeline data movement +export const dataFlow = { + style: { stroke: "#334155", strokeWidth: 2 }, + label: "data", +}; + +// Solid green, animated — step produces an output artifact +export const producesArtifact = { + style: { stroke: "#16a34a", strokeWidth: 2 }, + animated: true, + label: "produces", +}; + +// Dashed purple — a utility/library used by a process +export const usesUtility = { + style: { stroke: "#7c3aed", strokeWidth: 1.5, strokeDasharray: "6 3" }, + label: "uses", +}; + +// Dotted amber — external data source provides targets or reference data +export const externalSource = { + style: { stroke: "#b45309", strokeWidth: 1.5, strokeDasharray: "3 3" }, + label: "source", +}; + +// Long-dash red — a process executes on this infrastructure +export const runsOn = { + style: { stroke: "#dc2626", strokeWidth: 1.5, strokeDasharray: "8 4" }, + label: "runs on", +}; + +// Thin dotted gray — informational / reference link +export const informational = { + style: { stroke: "#9ca3af", strokeWidth: 1, strokeDasharray: "2 2" }, + label: "", +}; + +/** + * All edge categories for the legend component. + */ +export const EDGE_LEGEND = [ + { + label: "Data flow", + color: "#334155", + dash: false, + width: 2, + }, + { + label: "Produces artifact", + color: "#16a34a", + dash: false, + width: 2, + animated: true, + }, + { + label: "Uses utility", + color: "#7c3aed", + dash: true, + width: 1.5, + }, + { + label: "External source", + color: "#b45309", + dash: true, + width: 1.5, + }, + { + label: "Runs on infra", + color: "#dc2626", + dash: true, + width: 1.5, + }, + { + label: "Informational", + color: "#9ca3af", + dash: true, + width: 1, + }, +]; diff --git a/docs/pipeline-diagrams/src/hooks/useElkLayout.js b/docs/pipeline-diagrams/src/hooks/useElkLayout.js new file mode 100644 index 000000000..3eb05ec29 --- /dev/null +++ b/docs/pipeline-diagrams/src/hooks/useElkLayout.js @@ -0,0 +1,163 @@ +import { useEffect, useRef, useState } from "react"; +import ELK from "elkjs/lib/elk.bundled.js"; + +const elk = new ELK(); + +const ELK_OPTIONS = { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.edgeRouting": "ORTHOGONAL", + "elk.spacing.nodeNode": "60", + "elk.layered.spacing.nodeNodeBetweenLayers": "80", + "elk.layered.spacing.edgeNodeBetweenLayers": "30", + "elk.spacing.edgeEdge": "20", + "elk.spacing.edgeNode": "30", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", +}; + +/** + * Estimate node dimensions based on content. + * Matches the CSS: min-w-[180px] max-w-[280px] for normal nodes, + * min-w-[400px] for data_table nodes. + */ +function estimateNodeSize(node) { + if (node.type === "data_table") { + const cols = node.data?.columns?.length || 3; + const rows = node.data?.rows?.length || 2; + return { + width: Math.max(500, cols * 130), + height: 70 + rows * 26, + }; + } + + // Normal nodes: header (type badge) + label + subtitle + details + let height = 16; // padding top + height += 18; // type badge ("Input", "Process", etc.) + height += 20; // label + if (node.data?.subtitle) height += 16; + if (node.data?.details) height += node.data.details.length * 16; + height += 14; // padding bottom + + return { width: 240, height: Math.max(70, height) }; +} + +/** + * Auto-assign sourceHandle/targetHandle based on relative positions. + */ +function assignHandles(sourcePos, targetPos) { + const dx = targetPos.x - sourcePos.x; + const dy = targetPos.y - sourcePos.y; + + if (Math.abs(dx) >= Math.abs(dy)) { + return dx >= 0 + ? { sourceHandle: "sr", targetHandle: "tl" } + : { sourceHandle: "sl", targetHandle: "tr" }; + } + return dy >= 0 + ? { sourceHandle: "sb", targetHandle: "tt" } + : { sourceHandle: "st", targetHandle: "tb" }; +} + +/** + * Hook that runs ELK layout using estimated node sizes. + * + * @param {Array} initialNodes - Nodes WITHOUT position property + * @param {Array} initialEdges - Edges WITHOUT sourceHandle/targetHandle + * @returns {{ nodes, edges, layoutDone }} + */ +export default function useElkLayout(initialNodes, initialEdges) { + const [layoutedNodes, setLayoutedNodes] = useState([]); + const [layoutedEdges, setLayoutedEdges] = useState([]); + const [layoutDone, setLayoutDone] = useState(false); + const hasRun = useRef(false); + + useEffect(() => { + if (hasRun.current) return; + hasRun.current = true; + + async function runLayout() { + const graph = { + id: "root", + layoutOptions: ELK_OPTIONS, + children: initialNodes.map((node) => { + const size = estimateNodeSize(node); + return { + id: node.id, + width: size.width, + height: size.height, + }; + }), + edges: initialEdges.map((edge) => ({ + id: edge.id, + sources: [edge.source], + targets: [edge.target], + })), + }; + + try { + const result = await elk.layout(graph); + + // Build position map + const positionMap = {}; + for (const child of result.children) { + positionMap[child.id] = { x: child.x, y: child.y }; + } + + // Build edge route map + const routeMap = {}; + for (const edge of result.edges || []) { + if (edge.sections && edge.sections.length > 0) { + routeMap[edge.id] = edge.sections[0]; + } + } + + // Apply positions + const positioned = initialNodes.map((node) => ({ + ...node, + position: positionMap[node.id] || { x: 0, y: 0 }, + })); + + // Assign handles + inject ELK routes + const enriched = initialEdges.map((edge) => { + const srcPos = positionMap[edge.source] || { x: 0, y: 0 }; + const tgtPos = positionMap[edge.target] || { x: 0, y: 0 }; + const handles = assignHandles(srcPos, tgtPos); + const route = routeMap[edge.id]; + + return { + ...edge, + ...handles, + type: "elk", + data: { + ...(edge.data || {}), + elkRoute: route || null, + }, + }; + }); + + setLayoutedNodes(positioned); + setLayoutedEdges(enriched); + setLayoutDone(true); + } catch (err) { + console.error("ELK layout failed:", err); + // Fallback: grid layout so nodes are at least visible + setLayoutedNodes( + initialNodes.map((node, i) => ({ + ...node, + position: { + x: (i % 4) * 280, + y: Math.floor(i / 4) * 160, + }, + })), + ); + setLayoutedEdges(initialEdges); + setLayoutDone(true); + } + } + + runLayout(); + }, [initialNodes, initialEdges]); + + return { nodes: layoutedNodes, edges: layoutedEdges, layoutDone }; +} diff --git a/docs/pipeline-diagrams/src/index.css b/docs/pipeline-diagrams/src/index.css new file mode 100644 index 000000000..74ca3c252 --- /dev/null +++ b/docs/pipeline-diagrams/src/index.css @@ -0,0 +1,13 @@ +@import "tailwindcss"; +@import "@xyflow/react/dist/style.css"; + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; +} + +#root { + height: 100vh; + width: 100vw; +} diff --git a/docs/pipeline-diagrams/src/main.jsx b/docs/pipeline-diagrams/src/main.jsx new file mode 100644 index 000000000..a0552a127 --- /dev/null +++ b/docs/pipeline-diagrams/src/main.jsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App.jsx"; +import "./index.css"; + +createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/docs/pipeline-diagrams/src/stages/Overview.jsx b/docs/pipeline-diagrams/src/stages/Overview.jsx new file mode 100644 index 000000000..773b1024b --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Overview.jsx @@ -0,0 +1,244 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + runsOn, + externalSource, + informational, +} from "../constants/edgeStyles"; + +const nodes = [ + // Stage nodes (vertical column, x=400, 200px gaps) + { + id: "stage0", + type: "process", + data: { label: "Stage 0: Raw Data Download" }, + }, + { + id: "stage1", + type: "process", + data: { label: "Stage 1: Base Dataset Construction" }, + }, + { + id: "stage2", + type: "process", + data: { label: "Stage 2: Extended CPS (PUF Clone)" }, + }, + { + id: "stage3", + type: "process", + data: { label: "Stage 3: Stratified CPS" }, + }, + { + id: "stage4", + type: "process", + data: { label: "Stage 4: Source Imputation" }, + }, + { + id: "stage5", + type: "process", + data: { label: "Stage 5: Matrix Build" }, + }, + { + id: "stage6", + type: "process", + data: { label: "Stage 6: Weight Fitting (L0)" }, + }, + { + id: "stage7", + type: "process", + data: { label: "Stage 7: Local Area H5 Build" }, + }, + { + id: "stage8", + type: "process", + data: { label: "Stage 8: Validation & Promotion" }, + }, + + // Annotation nodes + { + id: "modal1", + type: "external", + data: { + label: "Modal (CPU)", + subtitle: "65GB RAM, 8 CPU", + details: ["Stages 5-6 build phase"], + }, + }, + { + id: "modal2", + type: "external", + data: { + label: "Modal (GPU)", + subtitle: "T4/A10/A100/H100", + details: ["Stage 6 fit phase"], + }, + }, + { + id: "modal3", + type: "external", + data: { + label: "Modal (Workers)", + subtitle: "8 containers, 16GB each", + details: ["Stage 7 parallel build"], + }, + }, + { + id: "hf1", + type: "external", + data: { + label: "HuggingFace Hub", + subtitle: "policyengine/policyengine-us-data", + details: ["Download + Upload at every stage"], + }, + }, + { + id: "local", + type: "us_specific", + data: { + label: "Local Machine", + subtitle: "make data / make pipeline", + details: ["Stages 0-3 run locally"], + }, + }, +]; + +const edges = [ + // Consecutive stage edges with artifact labels + // All stage-to-stage: same column x=400, y increasing -> sb/tt + { + id: "e-s0-s1", + source: "stage0", + target: "stage1", + ...dataFlow, + label: "census_cps_2024.h5, irs_puf_2015.h5, census_acs_2022.h5", + }, + { + id: "e-s1-s2", + source: "stage1", + target: "stage2", + ...dataFlow, + label: "CPS_2024, PUF_2024, ACS_2022", + }, + { + id: "e-s2-s3", + source: "stage2", + target: "stage3", + ...dataFlow, + label: "extended_cps_2024.h5", + }, + { + id: "e-s3-s4", + source: "stage3", + target: "stage4", + ...dataFlow, + label: "stratified_extended_cps_2024.h5", + }, + { + id: "e-s4-s5", + source: "stage4", + target: "stage5", + ...dataFlow, + label: "source_imputed_stratified_extended_cps.h5", + }, + { + id: "e-s5-s6", + source: "stage5", + target: "stage6", + ...dataFlow, + label: "calibration_package.pkl (X_sparse + targets)", + }, + { + id: "e-s6-s7", + source: "stage6", + target: "stage7", + ...dataFlow, + label: "calibration_weights.npy, geography.npz", + }, + { + id: "e-s7-s8", + source: "stage7", + target: "stage8", + ...dataFlow, + label: "51 state + 435 district + 1 city H5 files", + }, + + // Modal edges: modal (x=850) -> stages (x=400) = right to left -> sl/tr + { + id: "e-modal1-s5", + source: "modal1", + target: "stage5", + ...runsOn, + }, + { + id: "e-modal2-s6", + source: "modal2", + target: "stage6", + ...runsOn, + }, + { + id: "e-modal3-s7", + source: "modal3", + target: "stage7", + ...runsOn, + }, + + // HuggingFace edges + // hf1 (x=-100, y=800) -> stage0 (x=400, y=0): route UP from hf1 + // then RIGHT to stage0, keeping the vertical segment on the far left + { + id: "e-hf1-s0", + source: "hf1", + target: "stage0", + ...externalSource, + label: "download artifacts", + }, + // stage8 (x=400, y=1600) -> hf1 (x=-100, y=800): route LEFT from + // stage8 then arrive at bottom of hf1, avoiding overlap with the + // hf1->stage0 edge which exits from hf1's top + { + id: "e-s8-hf1", + source: "stage8", + target: "hf1", + ...externalSource, + label: "upload final datasets", + }, + + // Informational edges: local machine runs stages 0-3 + // Each edge uses a distinct source handle on `local` to avoid overlaps + { + id: "e-local-s0", + source: "local", + target: "stage0", + ...informational, + label: "runs locally", + }, + { + id: "e-local-s1", + source: "local", + target: "stage1", + ...informational, + }, + { + id: "e-local-s2", + source: "local", + target: "stage2", + ...informational, + }, + { + id: "e-local-s3", + source: "local", + target: "stage3", + ...informational, + }, +]; + +export default function Overview() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage0.jsx b/docs/pipeline-diagrams/src/stages/Stage0.jsx new file mode 100644 index 000000000..160fd620a --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage0.jsx @@ -0,0 +1,366 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, +} from "../constants/edgeStyles"; + +const nodes = [ + // INPUT nodes (left column, x=0) + // HTTP sources grouped near download_http (y=100) + { + id: "cps_url", + type: "input_data", + data: { + label: "Census CPS ASEC", + subtitle: "www2.census.gov", + details: [ + "ZIP with CSV files", + "Person + Family + Household", + "Public HTTPS, no auth", + ], + }, + }, + { + id: "acs_url", + type: "input_data", + data: { + label: "Census ACS PUMS", + subtitle: "www2.census.gov", + details: [ + "Person + Household CSVs", + "1-Year estimates", + "Public HTTPS", + ], + }, + }, + { + id: "scf_url", + type: "input_data", + data: { + label: "Federal Reserve SCF", + subtitle: "federalreserve.gov", + details: [ + "Stata format (.dta)", + "Survey of Consumer Finances", + "Public HTTPS", + ], + }, + }, + // HF sources grouped near download_hf (y=450) + { + id: "hf_private", + type: "external", + data: { + label: "HuggingFace Private Repo", + subtitle: "policyengine/irs-soi-puf", + details: [ + "Requires HUGGING_FACE_TOKEN", + "IRS PUF + Demographics", + ], + }, + }, + { + id: "hf_public", + type: "external", + data: { + label: "HuggingFace Public Repo", + subtitle: "policyengine/policyengine-us-data", + details: [ + "SIPP, block-CD distributions", + "Population projections", + ], + }, + }, + + // PROCESS nodes (center column, staggered x to avoid vertical overlaps) + { + id: "download_http", + type: "process", + data: { + label: "HTTP Download + ZIP Extract", + subtitle: "requests.get() with streaming", + details: ["Progress bar via tqdm"], + }, + }, + { + id: "download_hf", + type: "process", + data: { + label: "HuggingFace Hub Download", + subtitle: "huggingface_hub.hf_hub_download()", + details: ["Token-authenticated for private repos"], + }, + }, + { + id: "csv_parse", + type: "process", + data: { + label: "CSV/Stata Parsing", + subtitle: "pandas read_csv / read_stata", + details: [ + "Entity table construction", + "Tax unit grouping by TAX_ID", + "SPM unit grouping by SPM_ID", + ], + }, + }, + + // OUTPUT nodes (right column, x=900) + // Group 1: download_http direct output (near y=120) + { + id: "out_scf", + type: "output_data", + data: { + label: "SCF raw data", + subtitle: "Stata -> DataFrame", + details: ["Auto loans, net worth"], + }, + }, + + // Group 2: download_hf direct outputs (near y=450) + { + id: "out_sipp", + type: "output_data", + data: { + label: "pu2023_slim.csv", + subtitle: "CSV (pipe-delimited)", + details: ["SIPP microdata"], + }, + }, + { + id: "out_block", + type: "output_data", + data: { + label: "block_cd_distributions.csv.gz", + subtitle: "Compressed CSV", + details: [ + "Census block populations", + "Block-to-CD crosswalk", + ], + }, + }, + { + id: "out_pop", + type: "output_data", + data: { + label: "np2023_d5_mid.csv", + subtitle: "CSV", + details: ["Census population projections"], + }, + }, + + // Group 3: csv_parse outputs (near y=780) + { + id: "out_cps", + type: "output_data", + data: { + label: "census_cps_2024.h5", + subtitle: "HDF5 (pandas HDFStore)", + details: [ + "5 entity tables", + "Person, Family, Household", + "Tax Unit, SPM Unit", + ], + }, + }, + { + id: "out_acs", + type: "output_data", + data: { + label: "census_acs_2022.h5", + subtitle: "HDF5 (pandas HDFStore)", + details: ["Person + Household tables"], + }, + }, + { + id: "out_puf", + type: "output_data", + data: { + label: "irs_puf_2015.h5", + subtitle: "HDF5 (pandas HDFStore)", + details: ["PUF + Demographics tables"], + }, + }, + { + id: "out_soi", + type: "output_data", + data: { + label: "soi.csv", + subtitle: "CSV", + details: ["SOI aggregate statistics"], + }, + }, + + // US-Specific + { + id: "us_agencies", + type: "us_specific", + data: { + label: "US Federal Data Sources", + subtitle: "All inputs from US agencies", + details: [ + "Census Bureau (CPS, ACS)", + "IRS (PUF, SOI)", + "Federal Reserve (SCF)", + "Census (SIPP, pop projections)", + ], + }, + }, + + // Utility — placed right next to its target (out_cps) at the same y + { + id: "util_storage", + type: "utility", + data: { + label: "STORAGE_FOLDER", + subtitle: "policyengine_us_data/storage/", + details: ["All downloads cached here"], + }, + }, +]; + +const edges = [ + // HTTP sources -> download_http + // Each uses a different target handle to avoid convergence overlaps + { + id: "e-cps-dl", + source: "cps_url", + target: "download_http", + ...externalSource, + label: "CPS ASEC ZIP", + }, + { + id: "e-acs-dl", + source: "acs_url", + target: "download_http", + ...externalSource, + label: "ACS PUMS CSV", + }, + { + id: "e-scf-dl", + source: "scf_url", + target: "download_http", + ...externalSource, + label: "SCF .dta", + }, + + // HF sources -> download_hf + // Different target handles prevent shared arrival segments + { + id: "e-hfpriv-dl", + source: "hf_private", + target: "download_hf", + ...externalSource, + label: "PUF (private)", + }, + { + id: "e-hfpub-dl", + source: "hf_public", + target: "download_hf", + ...externalSource, + label: "SIPP, block, pop", + }, + + // Downloads -> CSV parsing + // Staggered x positions (430 vs 470) plus different target handles + // ensure distinct vertical paths + { + id: "e-http-csv", + source: "download_http", + target: "csv_parse", + ...dataFlow, + label: "raw files", + }, + { + id: "e-hf-csv", + source: "download_hf", + target: "csv_parse", + ...dataFlow, + label: "raw files", + }, + + // csv_parse -> outputs (fan-out with 4 distinct source handles) + // Outputs are spread around csv_parse's y=780 + { + id: "e-csv-cps", + source: "csv_parse", + target: "out_cps", + ...producesArtifact, + label: "census_cps_2024.h5", + }, + { + id: "e-csv-acs", + source: "csv_parse", + target: "out_acs", + ...producesArtifact, + label: "census_acs_2022.h5", + }, + { + id: "e-csv-puf", + source: "csv_parse", + target: "out_puf", + ...producesArtifact, + label: "irs_puf_2015.h5", + }, + { + id: "e-csv-soi", + source: "csv_parse", + target: "out_soi", + ...producesArtifact, + label: "soi.csv", + }, + + // download_hf -> direct outputs (fan-out with varied source handles) + // Outputs are spread around download_hf's y=450 + { + id: "e-hf-sipp", + source: "download_hf", + target: "out_sipp", + ...producesArtifact, + label: "pu2023_slim.csv", + }, + { + id: "e-hf-block", + source: "download_hf", + target: "out_block", + ...producesArtifact, + label: "block_cd_distributions.csv.gz", + }, + { + id: "e-hf-pop", + source: "download_hf", + target: "out_pop", + ...producesArtifact, + label: "np2023_d5_mid.csv", + }, + + // download_http -> SCF output (same y, straight horizontal) + { + id: "e-http-scf", + source: "download_http", + target: "out_scf", + ...producesArtifact, + label: "SCF raw data", + }, + + // Utility connection — short horizontal edge, same y + { + id: "e-storage-cps", + source: "util_storage", + target: "out_cps", + ...usesUtility, + label: "caches to disk", + }, +]; + +export default function Stage0() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage1.jsx b/docs/pipeline-diagrams/src/stages/Stage1.jsx new file mode 100644 index 000000000..04598c9b8 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage1.jsx @@ -0,0 +1,574 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, +} from "../constants/edgeStyles"; + +const nodes = [ + // ===================== + // CPS BUILD TRACK + // ===================== + + // Input nodes (x=0, y-aligned with their target process nodes) + { + id: "in_census_cps", + type: "input_data", + data: { + label: "census_cps_2024.h5", + subtitle: "From Stage 0", + }, + }, + { + id: "in_census_cps_prev", + type: "input_data", + data: { + label: "census_cps_2023.h5", + subtitle: "Previous year for income matching", + }, + }, + { + id: "in_acs", + type: "input_data", + data: { + label: "ACS_2022", + subtitle: "Training data for rent QRF", + }, + }, + { + id: "in_sipp", + type: "input_data", + data: { + label: "SIPP 2023", + subtitle: "Training data for tips QRF", + }, + }, + { + id: "in_scf", + type: "input_data", + data: { + label: "SCF 2022", + subtitle: "Training data for auto loans QRF", + }, + }, + + // CPS Process nodes (x=450, vertical chain, 160px gaps) + { + id: "add_ids", + type: "process", + data: { + label: "add_id_variables()", + subtitle: "person_id, household_id, tax_unit_id, spm_unit_id", + details: [ + "Entity linkage keys", + "marital_unit_id from A_LINENO", + ], + }, + }, + { + id: "add_personal", + type: "process", + data: { + label: "add_personal_variables()", + subtitle: "age, sex, disability, occupation", + details: [ + "80+ ages randomized to 80-84", + "12 overtime occupation flags", + ], + }, + }, + { + id: "add_income", + type: "process", + data: { + label: "add_personal_income_variables()", + subtitle: "30+ income types with splits", + details: [ + "SS classified by reason codes", + "Retirement split by account type", + "Capital gains 88% LT / 12% ST", + ], + }, + }, + { + id: "add_prev", + type: "process", + data: { + label: "add_previous_year_income()", + subtitle: "Cross-year PERIDNUM linking", + }, + }, + { + id: "add_ssn", + type: "us_specific", + data: { + label: "add_ssn_card_type()", + subtitle: "US immigration classification", + details: [ + "14 ASEC conditions", + "Undocumented target: 13M", + "SSN card types: 0-3", + ], + }, + }, + { + id: "add_spm", + type: "process", + data: { + label: "add_spm_variables()", + subtitle: "SPM thresholds + transfers", + details: ["SNAP, housing, energy subsidies"], + }, + }, + { + id: "add_hh", + type: "process", + data: { + label: "add_household_variables()", + subtitle: "state_fips, county_fips", + details: ["NYC flag from county FIPS"], + }, + }, + { + id: "add_rent", + type: "process", + data: { + label: "add_rent()", + subtitle: "QRF from ACS_2022", + details: [ + "10K sampled household heads", + "Imputes rent + real_estate_taxes", + ], + }, + }, + { + id: "add_tips", + type: "process", + data: { + label: "add_tips()", + subtitle: "QRF from SIPP 2023", + details: [ + "Tips + liquid assets", + "Models cached as pickle", + ], + }, + }, + { + id: "add_auto", + type: "process", + data: { + label: "add_auto_loan()", + subtitle: "QRF from SCF 2022", + details: [ + "net_worth, auto_loan_balance", + "auto_loan_interest", + ], + }, + }, + { + id: "add_takeup", + type: "us_specific", + data: { + label: "add_takeup()", + subtitle: "9 benefit programs", + details: [ + "SNAP, ACA, Medicaid, EITC", + "SSI, TANF, WIC, Head Start", + "State-specific Medicaid rates", + ], + }, + }, + { + id: "downsample", + type: "process", + data: { + label: "Downsampling", + subtitle: "frac=0.5 for CPS_2024", + details: ["Uses Microsimulation.subsample()"], + }, + }, + + // CPS Output (x=450, below chain) + { + id: "out_cps", + type: "output_data", + data: { + label: "cps_2024.h5", + subtitle: "Dataset.ARRAYS format", + details: ["~65K households (half-sample)"], + }, + }, + + // ===================== + // PUF BUILD TRACK + // ===================== + { + id: "in_puf_raw", + type: "input_data", + data: { + label: "irs_puf_2015.h5", + }, + }, + { + id: "puf_preprocess", + type: "process", + data: { + label: "preprocess_puf()", + subtitle: "60+ IRS variable renames", + details: ["E00200 -> employment_income"], + }, + }, + { + id: "puf_qbi", + type: "process", + data: { + label: "QBI simulation", + subtitle: "W-2 wages, UBIA, SSTB", + }, + }, + { + id: "puf_demo", + type: "process", + data: { + label: "impute_missing_demographics()", + subtitle: "QRF for age, gender, earn split", + }, + }, + { + id: "puf_uprate", + type: "process", + data: { + label: "Uprating 2015->2021->2024", + subtitle: "SOI growth rates by variable", + details: ["Population-adjusted"], + }, + }, + { + id: "out_puf", + type: "output_data", + data: { + label: "puf_2024.h5", + subtitle: "Dataset.ARRAYS", + }, + }, + + // ===================== + // UTILITIES — unique utils placed next to targets. + // Shared util_qrf placed outward at x=1600, fans to all QRF targets. + // ===================== + + // util_rng -> add_income (y=320): left side + { + id: "util_rng", + type: "utility", + data: { + label: "seeded_rng()", + subtitle: "Deterministic per-variable RNG", + }, + }, + // util_retire -> add_income (y=320): right side + { + id: "util_retire", + type: "utility", + data: { + label: "get_retirement_limits()", + subtitle: "IRS contribution limits", + }, + }, + // util_spm -> add_spm (y=800): right side + { + id: "util_spm", + type: "utility", + data: { + label: "spm-calculator", + subtitle: "SPM threshold computation", + }, + }, + // util_qrf: single QRF node fans out to add_rent, add_tips, add_auto, puf_demo + { + id: "util_qrf", + type: "utility", + data: { + label: "microimpute QRF", + subtitle: "fit_predict() for sequential imputation", + }, + }, + // util_takeup -> add_takeup (y=1600): right side + { + id: "util_takeup", + type: "utility", + data: { + label: "load_take_up_rate()", + subtitle: "YAML parameter files", + }, + }, + // util_uprate -> puf_uprate (y=720): right of PUF chain + { + id: "util_uprate", + type: "utility", + data: { + label: "uprating.py", + subtitle: "PE uprating factors table", + }, + }, + + // ===================== + // MISSING — placed near related utilities + // ===================== + { + id: "miss_registry", + type: "missing", + data: { + label: "Unified imputation registry", + subtitle: "Track which QRFs ran on what", + }, + }, + { + id: "miss_lineage", + type: "missing", + data: { + label: "Variable lineage tracker", + subtitle: "Source provenance per variable", + }, + }, +]; + +const edges = [ + // ===================== + // Input -> process edges + // Each input is y-aligned with its target, so edges are purely + // horizontal (sr -> tl) and never cross the vertical chain. + // ===================== + { + id: "e-in-cps-ids", + source: "in_census_cps", + target: "add_ids", + ...dataFlow, + label: "census_cps_2024.h5", + }, + { + id: "e-in-prev-prev", + source: "in_census_cps_prev", + target: "add_prev", + ...dataFlow, + label: "census_cps_2023.h5", + }, + { + id: "e-in-acs-rent", + source: "in_acs", + target: "add_rent", + ...dataFlow, + label: "ACS training data", + }, + { + id: "e-in-sipp-tips", + source: "in_sipp", + target: "add_tips", + ...dataFlow, + label: "SIPP training data", + }, + { + id: "e-in-scf-auto", + source: "in_scf", + target: "add_auto", + ...dataFlow, + label: "SCF training data", + }, + + // ===================== + // CPS vertical process chain (all x=450, sb -> tt) + // ===================== + { + id: "e-ids-personal", + source: "add_ids", + target: "add_personal", + ...dataFlow, + }, + { + id: "e-personal-income", + source: "add_personal", + target: "add_income", + ...dataFlow, + }, + { + id: "e-income-prev", + source: "add_income", + target: "add_prev", + ...dataFlow, + }, + { + id: "e-prev-ssn", + source: "add_prev", + target: "add_ssn", + ...dataFlow, + }, + { + id: "e-ssn-spm", + source: "add_ssn", + target: "add_spm", + ...dataFlow, + }, + { + id: "e-spm-hh", + source: "add_spm", + target: "add_hh", + ...dataFlow, + }, + { + id: "e-hh-rent", + source: "add_hh", + target: "add_rent", + ...dataFlow, + }, + { + id: "e-rent-tips", + source: "add_rent", + target: "add_tips", + ...dataFlow, + }, + { + id: "e-tips-auto", + source: "add_tips", + target: "add_auto", + ...dataFlow, + }, + { + id: "e-auto-takeup", + source: "add_auto", + target: "add_takeup", + ...dataFlow, + }, + { + id: "e-takeup-downsample", + source: "add_takeup", + target: "downsample", + ...dataFlow, + }, + + // CPS output + { + id: "e-downsample-out", + source: "downsample", + target: "out_cps", + ...producesArtifact, + label: "cps_2024.h5", + }, + + // ===================== + // PUF vertical process chain (all x=1100, sb -> tt) + // ===================== + { + id: "e-puf-raw-pre", + source: "in_puf_raw", + target: "puf_preprocess", + ...dataFlow, + label: "irs_puf_2015.h5", + }, + { + id: "e-puf-pre-qbi", + source: "puf_preprocess", + target: "puf_qbi", + ...dataFlow, + }, + { + id: "e-puf-qbi-demo", + source: "puf_qbi", + target: "puf_demo", + ...dataFlow, + }, + { + id: "e-puf-demo-uprate", + source: "puf_demo", + target: "puf_uprate", + ...dataFlow, + }, + { + id: "e-puf-uprate-out", + source: "puf_uprate", + target: "out_puf", + ...producesArtifact, + label: "puf_2024.h5", + }, + + // ===================== + // Utility connections — each utility is right next to its target, + // short horizontal edges only. + // ===================== + + // util_rng (x=-250) -> add_income (x=450): left side, sr -> tl + { + id: "e-util-rng-income", + source: "util_rng", + target: "add_income", + ...usesUtility, + }, + + // util_retire (x=750) -> add_income (x=450): right side, sl -> tr + { + id: "e-util-retire-income", + source: "util_retire", + target: "add_income", + ...usesUtility, + }, + + // util_spm (x=750) -> add_spm (x=450): right side, sl -> tr + { + id: "e-util-spm-spm", + source: "util_spm", + target: "add_spm", + ...usesUtility, + }, + + // Single QRF utility fans out to 4 targets + { + id: "e-util-qrf-rent", + source: "util_qrf", + target: "add_rent", + ...usesUtility, + }, + { + id: "e-util-qrf-tips", + source: "util_qrf", + target: "add_tips", + ...usesUtility, + }, + { + id: "e-util-qrf-auto", + source: "util_qrf", + target: "add_auto", + ...usesUtility, + }, + + // util_takeup (x=750) -> add_takeup (x=450): right side, sl -> tr + { + id: "e-util-takeup-takeup", + source: "util_takeup", + target: "add_takeup", + ...usesUtility, + }, + + { + id: "e-util-qrf-demo", + source: "util_qrf", + target: "puf_demo", + ...usesUtility, + }, + + // util_uprate (x=1350) -> puf_uprate (x=1100): right of PUF, sl -> tr + { + id: "e-util-uprate-puf", + source: "util_uprate", + target: "puf_uprate", + ...usesUtility, + }, +]; + +export default function Stage1() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage2.jsx b/docs/pipeline-diagrams/src/stages/Stage2.jsx new file mode 100644 index 000000000..975414b02 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage2.jsx @@ -0,0 +1,377 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + informational, +} from "../constants/edgeStyles"; + +const nodes = [ + // ===================== + // INPUT nodes (x=0) + // ===================== + { + id: "in_cps", + type: "input_data", + data: { + label: "CPS_2024_Full", + subtitle: "From Stage 1 (full sample)", + }, + }, + { + id: "in_puf", + type: "input_data", + data: { + label: "PUF_2024", + subtitle: "From Stage 1", + }, + }, + { + id: "in_blocks", + type: "input_data", + data: { + label: "block_cd_distributions.csv.gz", + subtitle: "Census block populations", + }, + }, + + // ===================== + // PROCESS nodes (center flow, x=450) + // ===================== + { + id: "geo_assign", + type: "process", + data: { + label: "Geography Assignment", + subtitle: "assign_random_geography()", + details: [ + "Population-weighted block draw", + "Derives state FIPS from block GEOID", + ], + }, + }, + { + id: "record_double", + type: "process", + data: { + label: "Record Doubling", + subtitle: "puf_clone_dataset()", + details: [ + "CPS half: original values", + "PUF half: zero weight initially", + "IDs offset by max(original)", + ], + }, + }, + { + id: "qrf_pass1", + type: "process", + data: { + label: "QRF Pass 1: Full Imputation", + subtitle: "64 income variables", + details: [ + "Training: PUF ~20K records", + "Predictors: 7 demographic vars", + "PUF half gets imputed values", + "CPS half keeps originals", + ], + }, + }, + { + id: "qrf_pass2", + type: "process", + data: { + label: "QRF Pass 2: Override Imputation", + subtitle: "51 variables (both halves)", + details: [ + "Partnership, S-corp income", + "Charitable, mortgage interest", + "Credits, deductions", + ], + }, + }, + { + id: "retire_impute", + type: "process", + data: { + label: "Retirement Contribution Imputation", + subtitle: "QRF from CPS retirement data", + details: [ + "traditional_401k, roth_401k", + "traditional_ira, roth_ira", + "IRS contribution limits applied", + ], + }, + }, + { + id: "ss_reconcile", + type: "process", + data: { + label: "SS Sub-component Reconciliation", + subtitle: "reconcile_ss_subcomponents()", + details: [ + "Retirement / Disability", + "Survivors / Dependents", + "Scaled to match PUF total", + ], + }, + }, + { + id: "cps_only", + type: "process", + data: { + label: "CPS-Only Variable Re-imputation", + subtitle: "80 variables for PUF half", + details: [ + "Transfers: UC, TANF, SSI, WIC", + "SPM: total income, thresholds", + "Medical expenses, hours", + "Retirement distributions", + ], + }, + }, + { + id: "formula_drop", + type: "process", + data: { + label: "Formula Variable Dropping", + subtitle: "_drop_formula_variables()", + details: [ + "Remove PE-computed variables", + "Rename: employment_income ->", + " employment_income_before_lsr", + ], + }, + }, + + // ===================== + // OUTPUT (x=1000) + // ===================== + { + id: "out_ext", + type: "output_data", + data: { + label: "extended_cps_2024.h5", + subtitle: "Dataset.TIME_PERIOD_ARRAYS", + details: [ + "~260K households (doubled)", + "CPS half + PUF half", + "Time-period dict structure", + ], + }, + }, + + // ===================== + // US-SPECIFIC + // ===================== + { + id: "us_filing", + type: "us_specific", + data: { + label: "IRS Filing Structure", + subtitle: "Tax units, filing statuses", + details: [ + "SINGLE, JOINT, HOH, SEPARATE", + "Dependent classification", + ], + }, + }, + + // ===================== + // UTILITIES (individual utils next to targets, shared QRF further out) + // ===================== + { + id: "util_puf_clone", + type: "utility", + data: { + label: "puf_clone_dataset()", + subtitle: "calibration/puf_impute.py", + }, + }, + { + id: "util_retire_constraints", + type: "utility", + data: { + label: "apply_retirement_constraints()", + subtitle: "IRS limits enforcement", + }, + }, + { + id: "util_reconcile", + type: "utility", + data: { + label: "reconcile_ss_subcomponents()", + subtitle: "calibration/puf_impute.py", + }, + }, + { + id: "util_qrf", + type: "utility", + data: { + label: "microimpute QRF", + subtitle: "fit_predict() for sequential imputation", + }, + }, + + // ===================== + // MISSING + // ===================== + { + id: "miss_quality", + type: "missing", + data: { + label: "Imputation quality metrics", + subtitle: "R-squared, coverage rates per variable", + }, + }, + { + id: "miss_provenance", + type: "missing", + data: { + label: "Variable provenance tracker", + subtitle: + "Record which donor survey each value came from", + }, + }, +]; + +const edges = [ + // Inputs -> process steps + { + id: "e-cps-geo", + source: "in_cps", + target: "geo_assign", + ...dataFlow, + label: "CPS records", + }, + { + id: "e-blocks-geo", + source: "in_blocks", + target: "geo_assign", + ...dataFlow, + label: "block populations", + }, + { + id: "e-puf-record", + source: "in_puf", + target: "record_double", + ...dataFlow, + label: "PUF records", + }, + { + id: "e-cps-record", + source: "in_cps", + target: "record_double", + ...dataFlow, + label: "CPS records", + }, + + // Process chain + { + id: "e-geo-record", + source: "geo_assign", + target: "record_double", + ...dataFlow, + }, + { + id: "e-record-qrf1", + source: "record_double", + target: "qrf_pass1", + ...dataFlow, + }, + { + id: "e-qrf1-qrf2", + source: "qrf_pass1", + target: "qrf_pass2", + ...dataFlow, + }, + { + id: "e-qrf2-retire", + source: "qrf_pass2", + target: "retire_impute", + ...dataFlow, + }, + { + id: "e-retire-ss", + source: "retire_impute", + target: "ss_reconcile", + ...dataFlow, + }, + { + id: "e-ss-cpsonly", + source: "ss_reconcile", + target: "cps_only", + ...dataFlow, + }, + { + id: "e-cpsonly-drop", + source: "cps_only", + target: "formula_drop", + ...dataFlow, + }, + + // Process -> output + { + id: "e-drop-out", + source: "formula_drop", + target: "out_ext", + ...producesArtifact, + }, + + // US-specific connection + { + id: "e-us-record", + source: "us_filing", + target: "record_double", + ...informational, + }, + + // Utility connections + { + id: "e-util-clone-record", + source: "util_puf_clone", + target: "record_double", + ...usesUtility, + }, + { + id: "e-util-retire-retire", + source: "util_retire_constraints", + target: "retire_impute", + ...usesUtility, + }, + { + id: "e-util-reconcile-ss", + source: "util_reconcile", + target: "ss_reconcile", + ...usesUtility, + }, + // Single QRF utility fans out to 3 targets + { + id: "e-util-qrf-qrf1", + source: "util_qrf", + target: "qrf_pass1", + ...usesUtility, + }, + { + id: "e-util-qrf-qrf2", + source: "util_qrf", + target: "qrf_pass2", + ...usesUtility, + }, + { + id: "e-util-qrf-cpsonly", + source: "util_qrf", + target: "cps_only", + ...usesUtility, + }, +]; + +export default function Stage2() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage3.jsx b/docs/pipeline-diagrams/src/stages/Stage3.jsx new file mode 100644 index 000000000..01c9dcf80 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage3.jsx @@ -0,0 +1,402 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, +} from "../constants/edgeStyles"; + +const nodes = [ + // ============================================= + // STRATIFICATION PATH (top, y=0-400) + // ============================================= + + // INPUT (x=0) + { + id: "in_ext_cps", + type: "input_data", + data: { + label: "extended_cps_2024.h5", + subtitle: "From Stage 2 (~260K HH)", + }, + }, + + // PROCESS - Stratification (x=450) + { + id: "calc_agi", + type: "process", + data: { + label: "Calculate AGI", + subtitle: "Microsimulation.calculate()", + details: [ + "adjusted_gross_income mapped to household", + ], + }, + }, + { + id: "strat_top", + type: "process", + data: { + label: "Retain Top 1% by AGI", + subtitle: "All high-income households kept", + details: ["Preserves tail representation"], + }, + }, + { + id: "strat_sample", + type: "process", + data: { + label: "Uniform Sample Remaining 99%", + subtitle: "Target: ~12,000 households", + details: [ + "Optional 1.5x oversample of bottom 25%", + "Weights NOT adjusted", + ], + }, + }, + + // OUTPUT - Stratification (x=1000) + { + id: "out_strat", + type: "output_data", + data: { + label: "stratified_extended_cps_2024.h5", + subtitle: "~12K households", + details: ["Input to Stages 4-6"], + }, + }, + + // ============================================= + // ENHANCED CPS REWEIGHTING PATH (bottom, y=650+) + // ============================================= + + // INPUT (x=0) + { + id: "in_ext_half", + type: "input_data", + data: { + label: "ExtendedCPS_2024_Half", + subtitle: "Half-sample from Stage 2", + }, + }, + + // PROCESS - Build loss matrix (x=450) + { + id: "build_loss", + type: "process", + data: { + label: "build_loss_matrix()", + subtitle: "Hundreds of calibration targets", + details: ["Returns (matrix, target_vector)"], + }, + }, + + // EXTERNAL TARGET SOURCES — 2 rows of 4, 250px horizontal gaps + // Row 1: y=880 + { + id: "t_soi", + type: "external", + data: { + label: "IRS SOI", + subtitle: "AGI, income types, filer counts", + details: ["By AGI band / filing status"], + }, + }, + { + id: "t_census", + type: "external", + data: { + label: "Census Population", + subtitle: "86 single-year age groups", + details: ["np2023_d5_mid.csv"], + }, + }, + { + id: "t_cbo", + type: "external", + data: { + label: "CBO Budget", + subtitle: "Income tax, SNAP, SS, SSI, UC", + }, + }, + { + id: "t_state", + type: "us_specific", + data: { + label: "State Targets", + subtitle: "50 states + DC", + details: [ + "Population, AGI, ACA, SNAP", + "Age groups, real estate taxes", + ], + }, + }, + // Row 2: y=1080 + { + id: "t_hhs", + type: "external", + data: { + label: "HHS/CMS Medicaid", + subtitle: "$900B spending, 72.4M enrolled", + }, + }, + { + id: "t_usda", + type: "external", + data: { + label: "USDA SNAP", + subtitle: "State-level participation", + }, + }, + { + id: "t_treasury", + type: "external", + data: { + label: "Treasury EITC", + subtitle: "Total + by child count", + }, + }, + { + id: "t_jct", + type: "external", + data: { + label: "JCT Tax Expenditures", + subtitle: "SALT, medical, charitable", + details: ["Counterfactual simulations"], + }, + }, + + // PROCESS - Reweighting (x=450) + { + id: "reweight", + type: "process", + data: { + label: "reweight()", + subtitle: "PyTorch Adam + L0 HardConcrete", + details: [ + "500 epochs, lr=0.2", + "L0 gates encourage sparsity", + "Minimize relative squared error", + ], + }, + }, + { + id: "weight_validate", + type: "process", + data: { + label: "Weight Validation", + subtitle: "No NaN, no negatives", + details: ["100M < total HH < 200M"], + }, + }, + + // OUTPUT - Enhanced (x=1000) + { + id: "out_enhanced", + type: "output_data", + data: { + label: "enhanced_cps_2024.h5", + subtitle: "Reweighted dataset", + details: [ + "Production simulation dataset", + "Dataset.TIME_PERIOD_ARRAYS", + ], + }, + }, + + // UTILITY nodes (placed next to their target process nodes) + { + id: "util_strat", + type: "utility", + data: { + label: "create_stratified_cps.py", + subtitle: "calibration/", + }, + }, + { + id: "util_loss", + type: "utility", + data: { + label: "build_loss_matrix()", + subtitle: "utils/loss.py", + }, + }, + { + id: "util_l0", + type: "utility", + data: { + label: "HardConcrete L0", + subtitle: "utils/l0.py", + }, + }, + { + id: "util_reweight", + type: "utility", + data: { + label: "reweight()", + subtitle: "PyTorch-based optimization", + }, + }, + + // MISSING + { + id: "miss_target_dash", + type: "missing", + data: { + label: "Target validation dashboard", + subtitle: "Visualize target fit quality", + }, + }, +]; + +const edges = [ + // Stratification path + { + id: "e-ext-agi", + source: "in_ext_cps", + target: "calc_agi", + ...dataFlow, + }, + { + id: "e-agi-top", + source: "calc_agi", + target: "strat_top", + ...dataFlow, + }, + { + id: "e-top-sample", + source: "strat_top", + target: "strat_sample", + ...dataFlow, + }, + { + id: "e-top-out", + source: "strat_top", + target: "out_strat", + ...dataFlow, + label: "top 1%", + }, + { + id: "e-sample-out", + source: "strat_sample", + target: "out_strat", + ...dataFlow, + label: "sampled 99%", + }, + + // Enhanced CPS path - input to build_loss + { + id: "e-half-loss", + source: "in_ext_half", + target: "build_loss", + ...dataFlow, + }, + + // External targets -> build_loss + { + id: "e-soi-loss", + source: "t_soi", + target: "build_loss", + ...externalSource, + }, + { + id: "e-census-loss", + source: "t_census", + target: "build_loss", + ...externalSource, + }, + { + id: "e-cbo-loss", + source: "t_cbo", + target: "build_loss", + ...externalSource, + }, + { + id: "e-hhs-loss", + source: "t_hhs", + target: "build_loss", + ...externalSource, + }, + { + id: "e-usda-loss", + source: "t_usda", + target: "build_loss", + ...externalSource, + }, + { + id: "e-treasury-loss", + source: "t_treasury", + target: "build_loss", + ...externalSource, + }, + { + id: "e-jct-loss", + source: "t_jct", + target: "build_loss", + ...externalSource, + }, + // State targets -> build_loss + { + id: "e-state-loss", + source: "t_state", + target: "build_loss", + ...externalSource, + }, + + // build_loss -> reweight -> validate -> output + { + id: "e-loss-reweight", + source: "build_loss", + target: "reweight", + ...dataFlow, + label: "(matrix, targets)", + }, + { + id: "e-reweight-validate", + source: "reweight", + target: "weight_validate", + ...dataFlow, + }, + { + id: "e-validate-out", + source: "weight_validate", + target: "out_enhanced", + ...producesArtifact, + }, + + // Utility connections (placed next to target nodes, short horizontal edges) + { + id: "e-util-strat", + source: "util_strat", + target: "strat_sample", + ...usesUtility, + }, + { + id: "e-util-loss", + source: "util_loss", + target: "build_loss", + ...usesUtility, + }, + { + id: "e-util-l0", + source: "util_l0", + target: "reweight", + ...usesUtility, + }, + { + id: "e-util-reweight", + source: "util_reweight", + target: "reweight", + ...usesUtility, + }, +]; + +export default function Stage3() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage4.jsx b/docs/pipeline-diagrams/src/stages/Stage4.jsx new file mode 100644 index 000000000..9b013590d --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage4.jsx @@ -0,0 +1,376 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, +} from "../constants/edgeStyles"; + +const nodes = [ + // ============================================= + // INPUT nodes (x=0) + // ============================================= + { + id: "in_strat", + type: "input_data", + data: { + label: "stratified_extended_cps_2024.h5", + subtitle: "From Stage 3 (~12K HH)", + }, + }, + { + id: "in_acs", + type: "input_data", + data: { + label: "ACS_2022", + subtitle: "American Community Survey", + details: ["Has state_fips predictor"], + }, + }, + { + id: "in_sipp_slim", + type: "external", + data: { + label: "SIPP 2023 (slim)", + subtitle: "HuggingFace download", + details: [ + "pu2023_slim.csv", + "No geographic identifiers", + ], + }, + }, + { + id: "in_sipp_full", + type: "external", + data: { + label: "SIPP 2023 (full)", + subtitle: "HuggingFace download", + details: [ + "pu2023.csv (pipe-delimited)", + "Filtered to MONTHCODE=12", + ], + }, + }, + { + id: "in_scf", + type: "input_data", + data: { + label: "SCF_2022", + subtitle: "Survey of Consumer Finances", + details: [ + "50% random subsample", + "No geographic identifiers", + ], + }, + }, + + // ============================================= + // PROCESS nodes (center, x=500) + // ============================================= + { + id: "geo_assign", + type: "process", + data: { + label: "Geography Assignment", + subtitle: "assign_random_geography(n_clones=1)", + details: [ + "Census block from global distribution", + "State FIPS from block GEOID", + ], + }, + }, + { + id: "acs_qrf", + type: "process", + data: { + label: "ACS QRF Imputation", + subtitle: "10 predictors incl. state_fips", + details: [ + "10K sampled ACS household heads", + "Imputes: rent, real_estate_taxes", + "Also creates pre_subsidy_rent", + ], + }, + }, + { + id: "sipp_tips", + type: "process", + data: { + label: "SIPP Tips QRF", + subtitle: "4 predictors, NO state", + details: [ + "10K sampled records", + "Imputes: tip_income", + ], + }, + }, + { + id: "sipp_assets", + type: "process", + data: { + label: "SIPP Assets QRF", + subtitle: "5 predictors, NO state", + details: [ + "20K sampled records", + "Imputes: bank_account_assets", + "stock_assets, bond_assets", + ], + }, + }, + { + id: "scf_qrf", + type: "process", + data: { + label: "SCF QRF Imputation", + subtitle: "8 predictors, NO state", + details: [ + "Imputes: net_worth", + "auto_loan_balance", + "auto_loan_interest", + ], + }, + }, + + // ============================================= + // OUTPUT nodes (x=1000) + // ============================================= + { + id: "out_imputed", + type: "output_data", + data: { + label: "source_imputed_stratified_extended_cps.h5", + subtitle: "HDF5", + details: [ + "Enriched with ACS/SIPP/SCF vars", + "Uploaded to HuggingFace", + ], + }, + }, + { + id: "out_hf", + type: "external", + data: { + label: "HuggingFace Upload", + subtitle: "calibration/ prefix", + details: ["Available for Stage 5"], + }, + }, + + // ============================================= + // UTILITY nodes (right-side column, x=1250) + // ============================================= + { + id: "util_clone_assign", + type: "utility", + data: { + label: "clone_and_assign.py", + subtitle: "Geography assignment", + }, + }, + { + id: "util_qrf", + type: "utility", + data: { + label: "microimpute QRF", + subtitle: "fit_predict() for sequential imputation", + }, + }, + { + id: "util_source_impute", + type: "utility", + data: { + label: "source_impute.py", + subtitle: "ACS/SIPP/SCF orchestrator", + }, + }, + + // ============================================= + // MISSING nodes + // ============================================= + { + id: "miss_donor", + type: "missing", + data: { + label: "Donor survey registry", + subtitle: + "Track which survey provides each variable", + }, + }, + { + id: "miss_confidence", + type: "missing", + data: { + label: "Imputation confidence scores", + subtitle: + "Uncertainty estimates per imputed value", + }, + }, + + // ============================================= + // US-SPECIFIC nodes + // ============================================= + { + id: "us_sipp", + type: "us_specific", + data: { + label: "SIPP Column Conventions", + subtitle: "TXAMT=tips, TPTOTINC=income", + details: ["US Census SIPP survey format"], + }, + }, +]; + +const edges = [ + // Input -> Geography assignment + { + id: "e-strat-geo", + source: "in_strat", + target: "geo_assign", + ...dataFlow, + }, + + // Geography -> ACS QRF (provides state_fips) + { + id: "e-geo-acs", + source: "geo_assign", + target: "acs_qrf", + ...dataFlow, + label: "state_fips", + }, + + // Donor surveys -> QRF steps + // ACS is an internal survey dataset -> dataFlow + { + id: "e-acs-qrf", + source: "in_acs", + target: "acs_qrf", + ...dataFlow, + }, + // SIPP slim is external repo -> externalSource + { + id: "e-sipp-slim-tips", + source: "in_sipp_slim", + target: "sipp_tips", + ...externalSource, + }, + // SIPP full is external repo -> externalSource + { + id: "e-sipp-full-assets", + source: "in_sipp_full", + target: "sipp_assets", + ...externalSource, + }, + // SCF is external repo -> externalSource + { + id: "e-scf-qrf", + source: "in_scf", + target: "scf_qrf", + ...externalSource, + }, + + // Dataset flows through chain: geo_assign -> acs_qrf -> sipp_tips -> sipp_assets -> scf_qrf + { + id: "e-chain-acs-tips", + source: "acs_qrf", + target: "sipp_tips", + ...dataFlow, + label: "chain", + }, + { + id: "e-chain-tips-assets", + source: "sipp_tips", + target: "sipp_assets", + ...dataFlow, + label: "chain", + }, + { + id: "e-chain-assets-scf", + source: "sipp_assets", + target: "scf_qrf", + ...dataFlow, + label: "chain", + }, + + // QRF steps -> output (distributed arrival handles to avoid overlaps) + { + id: "e-acs-out", + source: "acs_qrf", + target: "out_imputed", + ...producesArtifact, + }, + { + id: "e-tips-out", + source: "sipp_tips", + target: "out_imputed", + ...producesArtifact, + }, + { + id: "e-assets-out", + source: "sipp_assets", + target: "out_imputed", + ...producesArtifact, + }, + { + id: "e-scf-out", + source: "scf_qrf", + target: "out_imputed", + ...producesArtifact, + }, + + // Output -> HuggingFace upload + { + id: "e-out-hf", + source: "out_imputed", + target: "out_hf", + ...producesArtifact, + }, + + // Utility connections (short horizontal edges from right-side column) + { + id: "e-util-clone", + source: "util_clone_assign", + target: "geo_assign", + ...usesUtility, + }, + // Single QRF utility fans out to 4 targets + { + id: "e-util-qrf-acs", + source: "util_qrf", + target: "acs_qrf", + ...usesUtility, + }, + { + id: "e-util-qrf-tips", + source: "util_qrf", + target: "sipp_tips", + ...usesUtility, + }, + { + id: "e-util-qrf-assets", + source: "util_qrf", + target: "sipp_assets", + ...usesUtility, + }, + { + id: "e-util-qrf-scf", + source: "util_qrf", + target: "scf_qrf", + ...usesUtility, + }, + { + id: "e-util-source", + source: "util_source_impute", + target: "scf_qrf", + ...usesUtility, + }, +]; + +export default function Stage4() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage5.jsx b/docs/pipeline-diagrams/src/stages/Stage5.jsx new file mode 100644 index 000000000..589f69abe --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage5.jsx @@ -0,0 +1,573 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, + informational, +} from "../constants/edgeStyles"; + +const nodes = [ + // ============================================= + // INPUT nodes (x=0) + // ============================================= + { + id: "in_cps", + type: "input_data", + data: { + label: "source_imputed_stratified_extended_cps.h5", + subtitle: "From Stage 4 (~12K HH)", + }, + }, + { + id: "in_db", + type: "external", + data: { + label: "policy_data.db", + subtitle: "SQLite calibration database", + details: [ + "10-step ETL from 7 federal sources", + "strata + stratum_constraints + targets", + "target_overview SQL view", + ], + }, + }, + { + id: "in_blocks", + type: "input_data", + data: { + label: "block_cd_distributions.csv.gz", + subtitle: "Census block populations", + }, + }, + { + id: "in_aca", + type: "input_data", + data: { + label: "aca_ptc_multipliers_2022_2024.csv", + subtitle: "State ACA PTC adjustments", + }, + }, + { + id: "in_config", + type: "input_data", + data: { + label: "target_config.yaml", + subtitle: "Active target include list", + }, + }, + + // ============================================= + // PROCESS flow (x=500) + // ============================================= + { + id: "target_resolve", + type: "process", + data: { + label: "Target Resolution", + subtitle: "SQL query to target_overview", + details: [ + "National + State + District targets", + "~8,000 rows total", + ], + }, + }, + { + id: "target_uprate", + type: "process", + data: { + label: "Target Uprating", + subtitle: "CPI-U for dollars, pop growth for counts", + details: ["Matches data period to sim year 2024"], + }, + }, + { + id: "geo_build", + type: "process", + data: { + label: "Geography Index Build", + subtitle: "state_to_cols, cd_to_cols maps", + details: ["Which columns belong to each geo"], + }, + }, + { + id: "constraint_resolve", + type: "process", + data: { + label: "Constraint Resolution", + subtitle: "Non-geographic constraints from DB", + details: [ + "e.g., age >= 25 AND age < 35", + "medicaid_enrolled == True", + ], + }, + }, + { + id: "state_precomp", + type: "us_specific", + data: { + label: "Per-State Precomputation", + subtitle: "51 fresh Microsimulations", + details: [ + "One per state (50 + DC)", + "Clears cache between states", + "Computes ~50 vars per household", + "Most expensive step", + ], + }, + }, + { + id: "county_precomp", + type: "us_specific", + data: { + label: "Per-County Precomputation", + subtitle: "ACA PTC only (~3,143 counties)", + details: [ + "County affects PTC amount", + "Optional (county_level flag)", + ], + }, + }, + { + id: "clone_assembly", + type: "process", + data: { + label: "Clone Assembly", + subtitle: "430 clones x 12K records", + details: [ + "= 5.16M columns total", + "Each clone = different geography", + "Avoids CD collisions across clones", + ], + }, + }, + { + id: "takeup_rerand", + type: "process", + data: { + label: "Block-Level Takeup Re-randomization", + subtitle: "Seeded on (block_geoid, hh_id)", + details: [ + "SNAP, Medicaid, TANF, SSI, ACA", + "Ensures calibration consistency", + ], + }, + }, + { + id: "sparse_build", + type: "process", + data: { + label: "Sparse Matrix Construction", + subtitle: "COO triples -> CSR matrix", + details: [ + "Shape: (n_targets, 5.16M)", + "Extremely sparse (~0.02% nonzero)", + ], + }, + }, + + // ============================================= + // DATA TABLE - Example matrix rows (x=1100) + // ============================================= + { + id: "example_table", + type: "data_table", + data: { + label: "Example Matrix Rows (X)", + columns: [ + "Row", + "Target Description", + "Sample Values [col 0..5]", + "Target Value", + ], + rows: [ + [ + "0", + "person_count, age 0-5, CD 101 (AK)", + "[0, 0, 2, 0, 1, 0, ...]", + "8,500", + ], + [ + "500", + "SNAP $, state 06 (CA)", + "[0, 0, $4800, 0, $2400, ...]", + "$7.2B", + ], + [ + "7000", + "AGI, national", + "[$85K, $42K, $120K, ...]", + "$15.1T", + ], + [ + "7500", + "tax_unit_count, dividend_income > 0", + "[0, 1, 0, 1, 0, ...]", + "24.8M", + ], + ], + }, + }, + + // ============================================= + // OUTPUT (x=1100) + // ============================================= + { + id: "out_pkg", + type: "output_data", + data: { + label: "calibration_package.pkl", + subtitle: "Python pickle", + details: [ + "X_sparse (CSR matrix)", + "targets_df (DataFrame)", + "initial_weights, cd_geoid", + "block_geoid, metadata", + ], + }, + }, + + // ============================================= + // DB ETL sub-diagram (bottom, y=1800-1980) + // ============================================= + { + id: "etl_census", + type: "external", + data: { + label: "Census ACS 5-Year", + subtitle: "Age distributions, SNAP", + }, + }, + { + id: "etl_irs", + type: "external", + data: { + label: "IRS SOI", + subtitle: "Tax variables, EITC, AGI brackets", + }, + }, + { + id: "etl_usda", + type: "external", + data: { + label: "USDA FNS", + subtitle: "SNAP participation by state", + }, + }, + { + id: "etl_cdc", + type: "external", + data: { + label: "CDC VSRR", + subtitle: "Pregnancy prevalence", + }, + }, + { + id: "etl_cbo", + type: "external", + data: { + label: "CBO/CMS", + subtitle: "Budget projections, Medicaid", + }, + }, + { + id: "etl_db", + type: "process", + data: { + label: "10-Step ETL Pipeline", + subtitle: "make database", + details: [ + "Creates policy_data.db", + "Hierarchical stratum model", + "US > State > CD", + ], + }, + }, + + // ============================================= + // UTILITY nodes (x=850, same y as target process) + // ============================================= + { + id: "util_sql", + type: "utility", + data: { + label: "sqlalchemy", + subtitle: "Database queries", + }, + }, + { + id: "util_pool", + type: "utility", + data: { + label: "ProcessPoolExecutor", + subtitle: "Parallel state computation", + }, + }, + { + id: "util_takeup", + type: "utility", + data: { + label: "compute_block_takeup_for_entities()", + subtitle: "utils/takeup.py", + }, + }, + { + id: "util_scipy", + type: "utility", + data: { + label: "scipy.sparse", + subtitle: "CSR/COO matrix construction", + }, + }, + + // ============================================= + // MISSING nodes (x=850, below utilities) + // ============================================= + { + id: "miss_dash", + type: "missing", + data: { + label: "Target validation dashboard", + subtitle: "Visual target fit exploration", + }, + }, + { + id: "miss_sparsity", + type: "missing", + data: { + label: "Matrix sparsity analyzer", + subtitle: "Profile matrix structure", + }, + }, +]; + +const edges = [ + // Main processing flow: inputs -> target resolution chain + // in_cps (0,0) -> target_resolve (500,0): right + { + id: "e-cps-resolve", + source: "in_cps", + target: "target_resolve", + ...dataFlow, + }, + // in_db (0,200) -> target_resolve (500,0): right (dx=500 > dy=200) + { + id: "e-db-resolve", + source: "in_db", + target: "target_resolve", + ...externalSource, + label: "SQL targets", + }, + // in_config (0,-100) -> target_resolve (500,0): right and slightly down, arrive at top (tt avoids 3-way overlap at tl) + { + id: "e-config-resolve", + source: "in_config", + target: "target_resolve", + ...dataFlow, + label: "include list", + }, + // target_resolve (500,0) -> target_uprate (500,180): same column, down + { + id: "e-resolve-uprate", + source: "target_resolve", + target: "target_uprate", + ...dataFlow, + }, + // target_uprate (500,180) -> geo_build (500,360): same column, down + { + id: "e-uprate-geo", + source: "target_uprate", + target: "geo_build", + ...dataFlow, + }, + // geo_build (500,360) -> constraint_resolve (500,540): same column, down + { + id: "e-geo-constraint", + source: "geo_build", + target: "constraint_resolve", + ...dataFlow, + }, + // in_db (0,200) -> constraint_resolve (500,540): down then right (sb->tl avoids overlap with in_db->target_resolve which uses sr->tl) + { + id: "e-db-constraint", + source: "in_db", + target: "constraint_resolve", + ...externalSource, + label: "stratum_constraints", + }, + + // Constraint -> state precomputation + // constraint_resolve (500,540) -> state_precomp (500,720): same column, down + { + id: "e-constraint-state", + source: "constraint_resolve", + target: "state_precomp", + ...dataFlow, + }, + // in_cps (0,0) -> state_precomp (500,720): down then right (sb->tl avoids overlap with in_cps->target_resolve which uses sr->tl) + { + id: "e-cps-state", + source: "in_cps", + target: "state_precomp", + ...dataFlow, + label: "household data", + }, + + // State -> county precomputation + // state_precomp (500,720) -> county_precomp (500,920): same column, down + { + id: "e-state-county", + source: "state_precomp", + target: "county_precomp", + ...dataFlow, + }, + // in_aca (0,620) -> county_precomp (500,920): right (dx=500 > dy=300) + { + id: "e-aca-county", + source: "in_aca", + target: "county_precomp", + ...dataFlow, + label: "PTC multipliers", + }, + + // County -> clone assembly + // county_precomp (500,920) -> clone_assembly (500,1100): same column, down + { + id: "e-county-clone", + source: "county_precomp", + target: "clone_assembly", + ...dataFlow, + }, + // in_blocks (0,420) -> clone_assembly (500,1100): right (dx=500, dy=680; dy>dx but per instructions inputs->process = sr->tl) + { + id: "e-blocks-clone", + source: "in_blocks", + target: "clone_assembly", + ...dataFlow, + label: "block populations", + }, + + // Clone assembly -> takeup re-randomization + // clone_assembly (500,1100) -> takeup_rerand (500,1300): same column, down + { + id: "e-clone-takeup", + source: "clone_assembly", + target: "takeup_rerand", + ...dataFlow, + }, + + // Takeup -> sparse build + // takeup_rerand (500,1300) -> sparse_build (500,1500): same column, down + { + id: "e-takeup-sparse", + source: "takeup_rerand", + target: "sparse_build", + ...dataFlow, + }, + + // Sparse build -> output + // sparse_build (500,1500) -> out_pkg (1100,1500): right + { + id: "e-sparse-out", + source: "sparse_build", + target: "out_pkg", + ...producesArtifact, + }, + + // Sparse build -> example table + // sparse_build (500,1500) -> example_table (1100,700): up then right (st->tb avoids sharing sr with sparse_build->out_pkg) + { + id: "e-sparse-table", + source: "sparse_build", + target: "example_table", + ...informational, + label: "matrix rows", + }, + + // ETL sources -> ETL pipeline (distributed across handles to avoid overlapping lines) + // etl_census (0,1800) -> etl_db (500,1980): far left, go right to arrive at left side + { + id: "e-etl-census", + source: "etl_census", + target: "etl_db", + ...externalSource, + }, + // etl_irs (250,1800) -> etl_db (500,1980): go right then down, arrive at top (distinct from etl_census at tl and etl_usda at tt) + { + id: "e-etl-irs", + source: "etl_irs", + target: "etl_db", + ...externalSource, + }, + // etl_usda (500,1800) -> etl_db (500,1980): directly above, straight down + { + id: "e-etl-usda", + source: "etl_usda", + target: "etl_db", + ...externalSource, + }, + // etl_cdc (750,1800) -> etl_db (500,1980): right-ish, go down then arrive at right side + { + id: "e-etl-cdc", + source: "etl_cdc", + target: "etl_db", + ...externalSource, + }, + // etl_cbo (1000,1800) -> etl_db (500,1980): far right, go left to arrive at right side + { + id: "e-etl-cbo", + source: "etl_cbo", + target: "etl_db", + ...externalSource, + }, + + // ETL pipeline -> policy_data.db input + // etl_db (500,1980) -> in_db (0,200): going left then up (sl->tb avoids overlap with ETL source edges arriving at st/tt) + { + id: "e-etl-db-in", + source: "etl_db", + target: "in_db", + ...producesArtifact, + label: "produces", + }, + + // Utility connections (all at x=850 -> process at x=500, short horizontal edge) + // util_sql (850,0) -> target_resolve (500,0): same y, left + { + id: "e-util-sql", + source: "util_sql", + target: "target_resolve", + ...usesUtility, + }, + // util_pool (850,720) -> state_precomp (500,720): same y, left + { + id: "e-util-pool", + source: "util_pool", + target: "state_precomp", + ...usesUtility, + }, + // util_takeup (850,1300) -> takeup_rerand (500,1300): same y, left + { + id: "e-util-takeup", + source: "util_takeup", + target: "takeup_rerand", + ...usesUtility, + }, + // util_scipy (850,1500) -> sparse_build (500,1500): same y, left + { + id: "e-util-scipy", + source: "util_scipy", + target: "sparse_build", + ...usesUtility, + }, +]; + +export default function Stage5() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage6.jsx b/docs/pipeline-diagrams/src/stages/Stage6.jsx new file mode 100644 index 000000000..1469bfc80 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage6.jsx @@ -0,0 +1,430 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + runsOn, + informational, +} from "../constants/edgeStyles"; + +const nodes = [ + // Inputs (left) + { + id: "in_pkg", + type: "input_data", + data: { + label: "calibration_package.pkl", + subtitle: "From Stage 5", + details: ["X_sparse matrix", "targets_df", "initial_weights"], + }, + }, + { + id: "in_config", + type: "input_data", + data: { + label: "target_config.yaml", + subtitle: "Include/exclude rules", + }, + }, + + // Architecture context (external, top) + { + id: "modal_gpu", + type: "external", + data: { + label: "Modal GPU Container", + subtitle: "T4 / A10 / A100 / H100", + details: [ + "32GB RAM, 8 CPU", + "4-hour timeout", + "policyengine-us-data-fit-weights", + ], + }, + }, + + // Process flow (center, x=500) + { + id: "init_weights", + type: "process", + data: { + label: "Compute Initial Weights", + subtitle: "Population-proportional per CD", + details: [ + "Sum person_count targets per CD", + "weight = CD_pop / n_active_cols", + "Gives reasonable starting point", + ], + }, + }, + { + id: "create_model", + type: "process", + data: { + label: "Create SparseCalibrationWeights", + subtitle: "l0-python model", + details: [ + "n_features = 5.16M", + "init_keep_prob = 0.999", + "log_weight_jitter_sd = 0.05", + ], + }, + }, + { + id: "gate_detail", + type: "data_table", + data: { + label: "HardConcrete Gate Parameters", + columns: ["Parameter", "Value", "Purpose"], + rows: [ + ["beta", "0.35", "Gate temperature (sharpness)"], + ["gamma", "-0.1", "Hard-sigmoid lower bound"], + ["zeta", "1.1", "Hard-sigmoid upper bound"], + ["lambda_l0 (local)", "1e-8", "Keep ~3-4M records"], + ["lambda_l0 (national)", "1e-4", "Keep ~50K records"], + ["lambda_l2", "1e-12", "L2 regularization"], + ["learning_rate", "0.15", "Adam optimizer lr"], + ["epochs", "100", "Training iterations"], + ], + }, + }, + { + id: "fit_model", + type: "process", + data: { + label: "model.fit()", + subtitle: "Adam optimizer training loop", + details: [ + "Loss = relative_squared_error", + " + lambda_l0 * L0(w)", + " + lambda_l2 * ||w||^2", + "Forward: y_pred = X @ (w * gate)", + "Backward: gradients on w + gate", + "Per-epoch diagnostic logging", + ], + }, + }, + { + id: "extract", + type: "process", + data: { + label: "Extract Weights", + subtitle: "model.get_weights(deterministic=True)", + details: [ + "Gate thresholded (not sampled)", + "Produces exact zeros", + "Nonzero count = active records", + ], + }, + }, + { + id: "preset_compare", + type: "data_table", + data: { + label: "Two Calibration Presets", + columns: ["Preset", "lambda_l0", "Active Records", "Use Case"], + rows: [ + [ + "local", + "1e-8", + "~3-4M", + "Per-state/district H5 files", + ], + ["national", "1e-4", "~50K", "Web app simulation"], + ], + }, + }, + + // Outputs (right, x=1100) + { + id: "out_weights", + type: "output_data", + data: { + label: "calibration_weights.npy", + subtitle: "Shape: (n_records * n_clones,)", + details: [ + "One weight per (hh, clone) pair", + "Most entries are zero (sparse)", + ], + }, + }, + { + id: "out_geo", + type: "output_data", + data: { + label: "geography.npz", + subtitle: "NumPy compressed archive", + details: [ + "block_geoid, cd_geoid", + "county_fips, state_fips", + ], + }, + }, + { + id: "out_diag", + type: "output_data", + data: { + label: "unified_diagnostics.csv", + subtitle: "Per-target error analysis", + }, + }, + { + id: "out_log", + type: "output_data", + data: { + label: "calibration_log.csv", + subtitle: "Per-epoch training log", + details: [ + "Weight distribution buckets", + "Sparsity % per epoch", + ], + }, + }, + { + id: "out_config", + type: "output_data", + data: { + label: "unified_run_config.json", + subtitle: "Hyperparameters + checksums", + details: ["Git provenance", "Artifact SHA256 hashes"], + }, + }, + { + id: "out_hf", + type: "external", + data: { + label: "HuggingFace Upload", + subtitle: "calibration/ prefix", + details: ["Available for Stage 7"], + }, + }, + + // Utilities (right-side column at x=850, next to their targets) + { + id: "util_l0", + type: "utility", + data: { + label: "l0-python", + subtitle: "SparseCalibrationWeights", + details: [ + "HardConcrete gates", + "Differentiable L0 norm", + ], + }, + }, + { + id: "util_pytorch", + type: "utility", + data: { + label: "PyTorch", + subtitle: "Adam optimizer, autograd", + details: ["CUDA GPU acceleration"], + }, + }, + { + id: "util_modal", + type: "utility", + data: { + label: "Modal", + subtitle: "Remote GPU execution", + details: ["Secrets: huggingface-token"], + }, + }, + + // Missing (right-side column at x=850, below utilities) + { + id: "miss_tune", + type: "missing", + data: { + label: "Hyperparameter tuning framework", + subtitle: "Automated lambda/lr search", + }, + }, + { + id: "miss_converge", + type: "missing", + data: { + label: "Convergence monitor", + subtitle: "Early stopping, loss plateaus", + }, + }, + { + id: "miss_early_stop", + type: "missing", + data: { + label: "Early stopping", + subtitle: "Halt when loss stabilizes", + }, + }, +]; + +const edges = [ + // Main data flow + // in_pkg (0,0) -> init_weights (500,0): right + { + id: "e-in_pkg-init_weights", + source: "in_pkg", + target: "init_weights", + ...dataFlow, + }, + // in_config (0,200) -> init_weights (500,0): up-right, arrive at top + { + id: "e-in_config-init_weights", + source: "in_config", + target: "init_weights", + ...dataFlow, + }, + // init_weights (500,0) -> create_model (500,200): same column, down + { + id: "e-init_weights-create_model", + source: "init_weights", + target: "create_model", + ...dataFlow, + }, + // create_model (500,200) -> fit_model (500,400): same column, down + { + id: "e-create_model-fit_model", + source: "create_model", + target: "fit_model", + ...dataFlow, + }, + // fit_model (500,400) -> extract (500,620): same column, down + { + id: "e-fit_model-extract", + source: "fit_model", + target: "extract", + ...dataFlow, + }, + // extract (500,620) -> out_weights (1100,620): right + { + id: "e-extract-out_weights", + source: "extract", + target: "out_weights", + ...producesArtifact, + }, + + // Informational edges (data table -> process): right to left + // gate_detail (1100,0) -> create_model (500,200): left (dx=600 > dy=200) + { + id: "e-gate_detail-create_model", + source: "gate_detail", + target: "create_model", + ...informational, + label: "parameters", + }, + // preset_compare (1100,400) -> fit_model (500,400): left (same y) + { + id: "e-preset_compare-fit_model", + source: "preset_compare", + target: "fit_model", + ...informational, + label: "preset config", + }, + + // Modal GPU -> fit + // modal_gpu (500,-120) -> fit_model (500,400): same column, down + { + id: "e-modal_gpu-fit_model", + source: "modal_gpu", + target: "fit_model", + ...runsOn, + label: "runs on", + }, + + // Output connections + // extract (500,620) -> out_geo (1100,780): down then right, arrive at top + { + id: "e-extract-out_geo", + source: "extract", + target: "out_geo", + ...producesArtifact, + }, + // fit_model (500,400) -> out_diag (1100,940): right (dx=600 > dy=540) + { + id: "e-fit_model-out_diag", + source: "fit_model", + target: "out_diag", + ...producesArtifact, + }, + // fit_model (500,400) -> out_log (1100,1100): down from fit_model then right + { + id: "e-fit_model-out_log", + source: "fit_model", + target: "out_log", + ...producesArtifact, + }, + // fit_model (500,400) -> out_config (1100,1260): down from fit_model, arrive at top + { + id: "e-fit_model-out_config", + source: "fit_model", + target: "out_config", + ...producesArtifact, + }, + // out_weights (1100,620) -> out_hf (1400,940): down from weights, arrive at top of HF + { + id: "e-out_weights-out_hf", + source: "out_weights", + target: "out_hf", + ...producesArtifact, + }, + // out_geo (1100,780) -> out_hf (1400,940): down from geo, arrive at left of HF + { + id: "e-out_geo-out_hf", + source: "out_geo", + target: "out_hf", + ...producesArtifact, + }, + // out_diag (1100,940) -> out_hf (1400,940): right (same y) + { + id: "e-out_diag-out_hf", + source: "out_diag", + target: "out_hf", + ...producesArtifact, + }, + // out_log (1100,1100) -> out_hf (1400,940): up from log, arrive at bottom of HF + { + id: "e-out_log-out_hf", + source: "out_log", + target: "out_hf", + ...producesArtifact, + }, + // out_config (1100,1260) -> out_hf (1400,940): right from config, arrive at right side of HF + { + id: "e-out_config-out_hf", + source: "out_config", + target: "out_hf", + ...producesArtifact, + }, + + // Utility connections (short horizontal edges, same y-level) + // util_l0 (850,200) -> create_model (500,200): left, same y + { + id: "e-util_l0-create_model", + source: "util_l0", + target: "create_model", + ...usesUtility, + }, + // util_pytorch (850,400) -> fit_model (500,400): left, same y + { + id: "e-util_pytorch-fit_model", + source: "util_pytorch", + target: "fit_model", + ...usesUtility, + }, + // util_modal (850,-120) -> modal_gpu (500,-120): left, same y + { + id: "e-util_modal-modal_gpu", + source: "util_modal", + target: "modal_gpu", + ...usesUtility, + }, +]; + +export default function Stage6() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage7.jsx b/docs/pipeline-diagrams/src/stages/Stage7.jsx new file mode 100644 index 000000000..b984c7103 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage7.jsx @@ -0,0 +1,572 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, + runsOn, +} from "../constants/edgeStyles"; + +const nodes = [ + // Inputs (left, x=0, 180px apart) + { + id: "in_weights", + type: "input_data", + data: { + label: "calibration_weights.npy", + subtitle: "From Stage 6", + }, + }, + { + id: "in_geo", + type: "input_data", + data: { + label: "geography.npz", + subtitle: "Block/CD/state assignments", + }, + }, + { + id: "in_dataset", + type: "input_data", + data: { + label: "source_imputed_stratified_extended_cps.h5", + subtitle: "From Stage 4", + }, + }, + { + id: "in_db", + type: "external", + data: { + label: "policy_data.db", + subtitle: "CD list for partitioning", + }, + }, + { + id: "in_rents", + type: "input_data", + data: { + label: "national_and_district_rents_2023.csv", + subtitle: "SPM geographic adjustment", + }, + }, + { + id: "in_run_config", + type: "input_data", + data: { + label: "unified_run_config.json", + subtitle: "Checksums for validation", + }, + }, + + // Architecture (external, top right) + { + id: "modal_coord", + type: "external", + data: { + label: "Modal Coordinator", + subtitle: "coordinate_publish()", + details: [ + "8GB RAM, 24hr timeout", + "Orchestrates 3 phases", + ], + }, + }, + + // Process flow (center, x=450) + { + id: "download", + type: "process", + data: { + label: "Download Calibration Inputs", + subtitle: "download_calibration_inputs()", + details: [ + "From HuggingFace Hub", + "Validates checksums", + ], + }, + }, + { + id: "partition", + type: "process", + data: { + label: "Partition Work", + subtitle: "partition_work()", + details: [ + "8 workers default", + "Weights: state=5, city=3, dist=1", + "Round-robin distribution", + ], + }, + }, + { + id: "phase1", + type: "us_specific", + data: { + label: "Phase 1: States", + subtitle: "51 state H5 files (50 + DC)", + details: [ + "8 workers in parallel", + "AL.h5, AK.h5, ..., WY.h5", + ], + }, + }, + { + id: "phase2", + type: "us_specific", + data: { + label: "Phase 2: Districts", + subtitle: "~435 congressional district H5s", + details: [ + "8 workers in parallel", + "NC-01.h5, CA-52.h5, ...", + ], + }, + }, + { + id: "phase3", + type: "us_specific", + data: { + label: "Phase 3: Cities", + subtitle: "NYC (5 counties, 13 CDs)", + details: [ + "City probability filtering", + "P(target counties | CD)", + ], + }, + }, + + // build_h5 + sub-steps (x=900, 200px apart) + { + id: "build_h5", + type: "process", + data: { + label: "build_h5() — Core Logic", + subtitle: "18-step H5 construction", + details: [ + "1. Load base simulation", + "2. Reshape weight matrix", + "3. CD subset filtering", + "4. Identify active clones", + "5. Build entity membership maps", + "6. Build clone index arrays", + "7. Assign new entity IDs", + "8. Derive geography from blocks", + "9. Calculate entity weights", + "10. Clone variable arrays", + "11. Override IDs + weights + geo", + "12. Recalculate SPM thresholds", + "13. Apply calibration takeup", + "14. Write H5 file", + "15. Verify counts", + ], + }, + }, + { + id: "worker", + type: "external", + data: { + label: "Modal Worker Container", + subtitle: "build_areas_worker", + details: [ + "16GB RAM, 4 CPU each", + "4-hour timeout", + "Subprocess isolation", + ], + }, + }, + { + id: "geo_derive", + type: "process", + data: { + label: "derive_geography_from_blocks()", + subtitle: "15 geographic variables", + details: [ + "state, county, tract, CBSA", + "SLDU, SLDL, place, VTD", + "PUMA, ZCTA from block GEOID", + ], + }, + }, + { + id: "spm_recalc", + type: "process", + data: { + label: "SPM Threshold Recalculation", + subtitle: "calculate_spm_thresholds_vectorized()", + details: [ + "Local median rents", + "Family composition", + "Tenure type", + ], + }, + }, + { + id: "takeup_apply", + type: "process", + data: { + label: "Takeup Re-application", + subtitle: "apply_block_takeup_to_arrays()", + details: [ + "9 takeup variables", + "Block-level seeded draws", + "Consistent with calibration", + ], + }, + }, + + // Outputs (right, x=1350, 180px apart) + { + id: "out_states", + type: "output_data", + data: { + label: "states/*.h5", + subtitle: "51 files", + details: [ + "AL.h5 through WY.h5", + "Full simulation variables", + ], + }, + }, + { + id: "out_districts", + type: "output_data", + data: { + label: "districts/*.h5", + subtitle: "~435 files", + details: [ + "NC-01.h5, CA-52.h5, ...", + "Per-CD subsets", + ], + }, + }, + { + id: "out_cities", + type: "output_data", + data: { + label: "cities/*.h5", + subtitle: "1 file (NYC)", + details: ["NYC.h5"], + }, + }, + { + id: "out_manifest", + type: "output_data", + data: { + label: "manifest.json", + subtitle: "SHA256 checksums", + details: ["Generated during validation"], + }, + }, + { + id: "out_volume", + type: "external", + data: { + label: "Modal Volume", + subtitle: "local-area-staging", + details: [ + "Persistent across containers", + "Enables resumability", + ], + }, + }, + + // Utilities (x=1150, same y as targets in x=900 column) + { + id: "util_build", + type: "utility", + data: { + label: "publish_local_area.build_h5()", + subtitle: "calibration/publish_local_area.py", + }, + }, + { + id: "util_geo", + type: "utility", + data: { + label: "derive_geography_from_blocks()", + subtitle: "calibration/block_assignment.py", + }, + }, + { + id: "util_spm", + type: "utility", + data: { + label: "calculate_spm_thresholds_vectorized()", + subtitle: "calibration/calibration_utils.py", + }, + }, + { + id: "util_takeup", + type: "utility", + data: { + label: "apply_block_takeup_to_arrays()", + subtitle: "utils/takeup.py", + }, + }, + { + id: "util_manifest", + type: "utility", + data: { + label: "generate_manifest()", + subtitle: "utils/manifest.py", + }, + }, + + // Missing (near utilities) + { + id: "miss_incremental", + type: "missing", + data: { + label: "Incremental rebuild", + subtitle: "Only rebuild changed states", + }, + }, + { + id: "miss_diff", + type: "missing", + data: { + label: "Diff-based validation", + subtitle: "Compare against previous build", + }, + }, +]; + +const edges = [ + // Inputs -> download (dataFlow) + // Handles distributed across tt/tl/tb/tr to avoid overlapping arrivals + // in_weights (0,0) -> download (450,100): above download, arrive top + { + id: "e-in_weights-download", + source: "in_weights", + target: "download", + ...dataFlow, + }, + // in_geo (0,180) -> download (450,100): near same y, arrive left + { + id: "e-in_geo-download", + source: "in_geo", + target: "download", + ...dataFlow, + }, + // in_dataset (0,360) -> download (450,100): below download, arrive bottom + { + id: "e-in_dataset-download", + source: "in_dataset", + target: "download", + ...dataFlow, + }, + // in_rents (0,720) -> download (450,100): far below, go up from top then right, arrive right side + { + id: "e-in_rents-download", + source: "in_rents", + target: "download", + ...dataFlow, + }, + // in_run_config (0,900) -> download (450,100): far below, go up from top then right, arrive bottom + { + id: "e-in_run_config-download", + source: "in_run_config", + target: "download", + ...dataFlow, + }, + + // policy_data.db -> partition (externalSource) + // in_db (0,540) -> partition (450,300): right (dx=450 > dy=240) + { + id: "e-in_db-partition", + source: "in_db", + target: "partition", + ...externalSource, + label: "CD list", + }, + + // download -> partition -> phases (dataFlow) + // download (450,100) -> partition (450,300): same column, down + { + id: "e-download-partition", + source: "download", + target: "partition", + ...dataFlow, + }, + // partition -> phase1 -> phase2 -> phase3 (sequential chain, no overlapping vertical segments) + // partition (450,300) -> phase1 (450,550): same column, down + { + id: "e-partition-phase1", + source: "partition", + target: "phase1", + ...dataFlow, + }, + // phase1 (450,550) -> phase2 (450,800): same column, down + { + id: "e-phase1-phase2", + source: "phase1", + target: "phase2", + ...dataFlow, + }, + // phase2 (450,800) -> phase3 (450,1050): same column, down + { + id: "e-phase2-phase3", + source: "phase2", + target: "phase3", + ...dataFlow, + }, + + // Phases -> build_h5 (dataFlow with label "calls") + // Handles distributed across tl/tt/tr to avoid overlapping arrivals + // phase1 (450,550) -> build_h5 (900,200): right and up, arrive left + { + id: "e-phase1-build_h5", + source: "phase1", + target: "build_h5", + ...dataFlow, + label: "calls", + }, + // phase2 (450,800) -> build_h5 (900,200): right and up, arrive top + { + id: "e-phase2-build_h5", + source: "phase2", + target: "build_h5", + ...dataFlow, + label: "calls", + }, + // phase3 (450,1050) -> build_h5 (900,200): right and up, arrive right + { + id: "e-phase3-build_h5", + source: "phase3", + target: "build_h5", + ...dataFlow, + label: "calls", + }, + + // build_h5 -> geo_derive -> spm_recalc -> takeup_apply (sequential chain, no overlapping vertical segments) + // build_h5 (900,200) -> geo_derive (900,900): same column, down + { + id: "e-build_h5-geo_derive", + source: "build_h5", + target: "geo_derive", + ...dataFlow, + label: "step 8", + }, + // geo_derive (900,900) -> spm_recalc (900,1100): same column, down + { + id: "e-geo_derive-spm_recalc", + source: "geo_derive", + target: "spm_recalc", + ...dataFlow, + label: "step 12", + }, + // spm_recalc (900,1100) -> takeup_apply (900,1300): same column, down + { + id: "e-spm_recalc-takeup_apply", + source: "spm_recalc", + target: "takeup_apply", + ...dataFlow, + label: "step 13", + }, + + // Worker infrastructure (runsOn) + // modal_coord (900,-100) -> worker (900,700): same column, down + { + id: "e-modal_coord-worker", + source: "modal_coord", + target: "worker", + ...runsOn, + label: "orchestrates", + }, + // worker (900,700) -> build_h5 (900,200): same column, going up + { + id: "e-worker-build_h5", + source: "worker", + target: "build_h5", + ...runsOn, + label: "runs", + }, + + // Phase -> outputs (producesArtifact) + // phase1 (450,550) -> out_states (1350,550): right (same y) + { + id: "e-phase1-out_states", + source: "phase1", + target: "out_states", + ...producesArtifact, + }, + // phase2 (450,800) -> out_districts (1350,730): right (dx=900 > dy=70) + { + id: "e-phase2-out_districts", + source: "phase2", + target: "out_districts", + ...producesArtifact, + }, + // phase3 (450,1050) -> out_cities (1350,910): right (dx=900 > dy=140) + { + id: "e-phase3-out_cities", + source: "phase3", + target: "out_cities", + ...producesArtifact, + }, + + // build_h5 -> manifest/volume (producesArtifact) + // Handles distributed to avoid overlapping arrivals + // build_h5 (900,200) -> out_manifest (1350,1090): right and down, arrive left + { + id: "e-build_h5-out_manifest", + source: "build_h5", + target: "out_manifest", + ...producesArtifact, + }, + // build_h5 (900,200) -> out_volume (1350,1270): right and down, arrive top + { + id: "e-build_h5-out_volume", + source: "build_h5", + target: "out_volume", + ...producesArtifact, + }, + + // Utility connections (usesUtility) + // All utilities placed right next to targets with short horizontal edges (sl->tr) + // util_build (1150,200) -> build_h5 (900,200): short left edge, same y + { + id: "e-util_build-build_h5", + source: "util_build", + target: "build_h5", + ...usesUtility, + }, + // util_geo (1150,900) -> geo_derive (900,900): short left edge, same y + { + id: "e-util_geo-geo_derive", + source: "util_geo", + target: "geo_derive", + ...usesUtility, + }, + // util_spm (1150,1100) -> spm_recalc (900,1100): short left edge, same y + { + id: "e-util_spm-spm_recalc", + source: "util_spm", + target: "spm_recalc", + ...usesUtility, + }, + // util_takeup (1150,1300) -> takeup_apply (900,1300): short left edge, same y + { + id: "e-util_takeup-takeup_apply", + source: "util_takeup", + target: "takeup_apply", + ...usesUtility, + }, + // util_manifest (1600,1090) -> out_manifest (1350,1090): short left edge, same y + { + id: "e-util_manifest-out_manifest", + source: "util_manifest", + target: "out_manifest", + ...usesUtility, + }, +]; + +export default function Stage7() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/src/stages/Stage8.jsx b/docs/pipeline-diagrams/src/stages/Stage8.jsx new file mode 100644 index 000000000..1f5048117 --- /dev/null +++ b/docs/pipeline-diagrams/src/stages/Stage8.jsx @@ -0,0 +1,488 @@ +import DiagramPage from "../components/DiagramPage"; +import { + dataFlow, + producesArtifact, + usesUtility, + externalSource, +} from "../constants/edgeStyles"; + +const nodes = [ + // Inputs (left, x=0, 200px apart) + { + id: "in_h5s", + type: "input_data", + data: { + label: "51 state + 435 district + 1 city H5s", + subtitle: "From Stage 7 (Modal volume)", + }, + }, + { + id: "in_db", + type: "external", + data: { + label: "policy_data.db", + subtitle: "Validation targets", + }, + }, + { + id: "in_run_config", + type: "input_data", + data: { + label: "unified_run_config.json", + subtitle: "Expected checksums", + }, + }, + + // 7 Validation Layers (vertical chain, x=450, 200px apart) + { + id: "v1", + type: "process", + data: { + label: "Layer 1: Manifest Verification", + subtitle: "SHA256 checksums per file", + details: [ + "generate_manifest() + verify_manifest()", + "Catches corruption/incomplete writes", + ], + }, + }, + { + id: "v2", + type: "process", + data: { + label: "Layer 2: Structural Sanity", + subtitle: "sanity_checks.py", + details: [ + "Weight non-negativity", + "Entity ID uniqueness", + "No NaN/Inf in monetary vars", + "Person-to-household mapping", + "Boolean takeup validation", + "Per-capita range checks", + ], + }, + }, + { + id: "v3", + type: "process", + data: { + label: "Layer 3: Target-Based Validation", + subtitle: "validate_staging.py", + details: [ + "Full Microsimulation per area", + "Compare sim values to targets", + "Subprocess isolation for memory", + "Outputs CSV with per-target errors", + ], + }, + }, + { + id: "v4", + type: "process", + data: { + label: "Layer 4: Smoke Test", + subtitle: "check_staging_sums.py", + details: [ + "Sum 20 key vars across 51 states", + "Compare to national references", + "GDP ~$29T, pop ~335M", + ], + }, + }, + { + id: "v5", + type: "process", + data: { + label: "Layer 5: National H5 Validation", + subtitle: "validate_national_h5.py", + details: [ + ">30% deviation flagged", + "Hardcoded reference values", + "Also runs sanity checks", + ], + }, + }, + { + id: "v6", + type: "process", + data: { + label: "Layer 6: Pre-Upload Validation", + subtitle: "upload_completed_datasets.py", + details: [ + "File size minimums (100MB/50MB)", + "H5 structure check", + "Microsim aggregate checks", + "emp_income > $5T, weights 100-200M", + ], + }, + }, + { + id: "v7", + type: "process", + data: { + label: "Layer 7: Package Validation", + subtitle: "validate_package.py", + details: [ + "Matrix achievability", + "Target ratio analysis", + "Zero-row detection", + ], + }, + }, + + // Deployment flow (x=900, 200px apart) + { + id: "staging_upload", + type: "process", + data: { + label: "Upload to Staging", + subtitle: "upload_to_staging_hf()", + details: [ + "Batches of 50 files/commit", + "Exponential backoff retry", + "staging/{path} in HF repo", + ], + }, + }, + { + id: "atomic_promote", + type: "process", + data: { + label: "Atomic Promotion", + subtitle: "promote_staging_to_production_hf()", + details: [ + "Single commit with CommitOperationCopy", + "staging/{path} -> {path}", + "All-or-nothing deployment", + ], + }, + }, + { + id: "gcs_upload", + type: "external", + data: { + label: "GCS Parallel Upload", + subtitle: "gs://policyengine-us-data/", + details: [ + "Version metadata on each blob", + "google.auth.default() credentials", + ], + }, + }, + { + id: "staging_cleanup", + type: "process", + data: { + label: "Staging Cleanup", + subtitle: "cleanup_staging_hf()", + details: [ + "CommitOperationDelete", + "Removes staging/ files", + ], + }, + }, + { + id: "pipeline_mirror", + type: "process", + data: { + label: "Pipeline Artifact Mirror", + subtitle: "mirror_to_pipeline()", + details: [ + "manifest_only=True", + "SHA256 checksums only", + "Files too large to double-upload", + ], + }, + }, + + // Outputs (right, x=1350, 200px apart) + { + id: "out_hf_prod", + type: "external", + data: { + label: "HuggingFace Production", + subtitle: "policyengine/policyengine-us-data", + details: [ + "states/, districts/, cities/", + "Final published datasets", + ], + }, + }, + { + id: "out_gcs", + type: "external", + data: { + label: "Google Cloud Storage", + subtitle: "gs://policyengine-us-data/", + details: [ + "CDN/backup storage", + "Version metadata", + ], + }, + }, + { + id: "out_pipeline", + type: "external", + data: { + label: "Pipeline Artifact Repo", + subtitle: "policyengine-us-data-pipeline", + details: [ + "stage_7_local_area/manifest.json", + "Audit trail", + ], + }, + }, + + // Utilities (placed next to their targets) + { + id: "util_manifest", + type: "utility", + data: { + label: "manifest.py", + subtitle: "generate_manifest(), verify_manifest()", + }, + }, + { + id: "util_sanity", + type: "utility", + data: { + label: "sanity_checks.py", + subtitle: "run_sanity_checks()", + }, + }, + { + id: "util_validate", + type: "utility", + data: { + label: "validate_staging.py", + subtitle: "Full target comparison", + }, + }, + { + id: "util_upload", + type: "utility", + data: { + label: "data_upload.py", + subtitle: "staging/promote/cleanup", + }, + }, + { + id: "util_pipeline", + type: "utility", + data: { + label: "pipeline_artifacts.py", + subtitle: "mirror_to_pipeline()", + }, + }, + + // Missing (near utilities) + { + id: "miss_regression", + type: "missing", + data: { + label: "Automated regression tests", + subtitle: "Compare to previous release", + }, + }, + { + id: "miss_rollback", + type: "missing", + data: { + label: "Promotion rollback", + subtitle: "Undo a bad promotion", + }, + }, +]; + +const edges = [ + // Inputs -> first validation layer (dataFlow) + // in_h5s (0,0) -> v1 (450,0): right (same y) + { + id: "e-in_h5s-v1", + source: "in_h5s", + target: "v1", + ...dataFlow, + }, + // in_run_config (0,400) -> v1 (450,0): up-right (arrive at top to avoid overlap with in_h5s->v1 on tl) + { + id: "e-in_run_config-v1", + source: "in_run_config", + target: "v1", + ...dataFlow, + label: "checksums", + }, + + // policy_data.db -> V3 (externalSource) + // in_db (0,200) -> v3 (450,450): right (dx=450 > dy=250) + { + id: "e-in_db-v3", + source: "in_db", + target: "v3", + ...externalSource, + label: "targets", + }, + + // Validation chain (dataFlow) + // v1 (450,0) -> v2 (450,200): same column, down + { + id: "e-v1-v2", + source: "v1", + target: "v2", + ...dataFlow, + label: "", + }, + // v2 (450,200) -> v3 (450,450): same column, down + { + id: "e-v2-v3", + source: "v2", + target: "v3", + ...dataFlow, + label: "", + }, + // v3 (450,450) -> v4 (450,680): same column, down + { + id: "e-v3-v4", + source: "v3", + target: "v4", + ...dataFlow, + label: "", + }, + // v4 (450,680) -> v5 (450,880): same column, down + { + id: "e-v4-v5", + source: "v4", + target: "v5", + ...dataFlow, + label: "", + }, + // v5 (450,880) -> v6 (450,1080): same column, down + { + id: "e-v5-v6", + source: "v5", + target: "v6", + ...dataFlow, + label: "", + }, + // v6 (450,1080) -> v7 (450,1300): same column, down + { + id: "e-v6-v7", + source: "v6", + target: "v7", + ...dataFlow, + label: "", + }, + + // Validation -> deployment (dataFlow with label) + // v7 (450,1300) -> staging_upload (900,0): right then up (L-shaped path on right side) + { + id: "e-v7-staging_upload", + source: "v7", + target: "staging_upload", + ...dataFlow, + label: "all pass", + }, + + // Deployment chain (dataFlow) + // staging_upload (900,0) -> atomic_promote (900,200): same column, down + { + id: "e-staging_upload-atomic_promote", + source: "staging_upload", + target: "atomic_promote", + ...dataFlow, + label: "", + }, + // atomic_promote (900,200) -> gcs_upload (900,400): same column, down + { + id: "e-atomic_promote-gcs_upload", + source: "atomic_promote", + target: "gcs_upload", + ...dataFlow, + label: "", + }, + // gcs_upload (900,400) -> staging_cleanup (900,600): same column, down + { + id: "e-gcs_upload-staging_cleanup", + source: "gcs_upload", + target: "staging_cleanup", + ...dataFlow, + label: "", + }, + // staging_cleanup (900,600) -> pipeline_mirror (900,800): same column, down + { + id: "e-staging_cleanup-pipeline_mirror", + source: "staging_cleanup", + target: "pipeline_mirror", + ...dataFlow, + label: "", + }, + + // Deployment -> outputs (producesArtifact) + // atomic_promote (900,200) -> out_hf_prod (1350,200): right (same y) + { + id: "e-atomic_promote-out_hf_prod", + source: "atomic_promote", + target: "out_hf_prod", + ...producesArtifact, + }, + // gcs_upload (900,400) -> out_gcs (1350,400): right (same y) + { + id: "e-gcs_upload-out_gcs", + source: "gcs_upload", + target: "out_gcs", + ...producesArtifact, + }, + // pipeline_mirror (900,800) -> out_pipeline (1350,800): right (same y) + { + id: "e-pipeline_mirror-out_pipeline", + source: "pipeline_mirror", + target: "out_pipeline", + ...producesArtifact, + }, + + // Utility connections (usesUtility) — short horizontal edges, sl→tr + // util_manifest (700,0) -> v1 (450,0): left (same y) + { + id: "e-util_manifest-v1", + source: "util_manifest", + target: "v1", + ...usesUtility, + }, + // util_sanity (700,200) -> v2 (450,200): left (same y) + { + id: "e-util_sanity-v2", + source: "util_sanity", + target: "v2", + ...usesUtility, + }, + // util_validate (700,450) -> v3 (450,450): left (same y) + { + id: "e-util_validate-v3", + source: "util_validate", + target: "v3", + ...usesUtility, + }, + // util_upload (1150,0) -> staging_upload (900,0): left (same y) + { + id: "e-util_upload-staging_upload", + source: "util_upload", + target: "staging_upload", + ...usesUtility, + }, + // util_pipeline (1150,800) -> pipeline_mirror (900,800): left (same y) + { + id: "e-util_pipeline-pipeline_mirror", + source: "util_pipeline", + target: "pipeline_mirror", + ...usesUtility, + }, +]; + +export default function Stage8() { + return ( + + ); +} diff --git a/docs/pipeline-diagrams/vite.config.js b/docs/pipeline-diagrams/vite.config.js new file mode 100644 index 000000000..0ce1839d2 --- /dev/null +++ b/docs/pipeline-diagrams/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); From 1b03720c186b24540b127d5961c5264c60696e12 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Tue, 24 Mar 2026 00:16:09 +0100 Subject: [PATCH 02/10] Add UK pipeline diagrams with cross-country comparison - Add PolicyEngine UK pipeline (10 stages: Overview + Stage 0-8) - Add country selector dropdown (US / UK) in sidebar - Add uk_specific node type (teal) for UK-only processing - Add absent node type (gray dashed) for cross-referencing - Show UK-only steps as absent in US stages (Stage 1, 4) - Show US-only steps as absent in UK stages - Update routes from / to /us and /uk prefixes - Rename app title to "PolicyEngine data pipelines" Co-Authored-By: Claude Opus 4.6 --- docs/pipeline-diagrams/index.html | 2 +- docs/pipeline-diagrams/src/App.jsx | 43 ++- .../src/components/Sidebar.jsx | 71 +++- .../src/components/nodes/CustomNodes.jsx | 31 ++ docs/pipeline-diagrams/src/stages/Stage1.jsx | 31 ++ docs/pipeline-diagrams/src/stages/Stage4.jsx | 58 +++ .../src/stages/uk/Overview.jsx | 145 ++++++++ .../src/stages/uk/Stage0.jsx | 228 ++++++++++++ .../src/stages/uk/Stage1.jsx | 350 ++++++++++++++++++ .../src/stages/uk/Stage2.jsx | 181 +++++++++ .../src/stages/uk/Stage3.jsx | 48 +++ .../src/stages/uk/Stage4.jsx | 321 ++++++++++++++++ .../src/stages/uk/Stage5.jsx | 231 ++++++++++++ .../src/stages/uk/Stage6.jsx | 184 +++++++++ .../src/stages/uk/Stage7.jsx | 191 ++++++++++ .../src/stages/uk/Stage8.jsx | 241 ++++++++++++ 16 files changed, 2324 insertions(+), 32 deletions(-) create mode 100644 docs/pipeline-diagrams/src/stages/uk/Overview.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage0.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage1.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage2.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage3.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage4.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage5.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage6.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage7.jsx create mode 100644 docs/pipeline-diagrams/src/stages/uk/Stage8.jsx diff --git a/docs/pipeline-diagrams/index.html b/docs/pipeline-diagrams/index.html index 34d598ec0..8024e1ca1 100644 --- a/docs/pipeline-diagrams/index.html +++ b/docs/pipeline-diagrams/index.html @@ -4,7 +4,7 @@ - pipeline-diagrams + PolicyEngine data pipelines
diff --git a/docs/pipeline-diagrams/src/App.jsx b/docs/pipeline-diagrams/src/App.jsx index 2cdc3af51..fa7be8228 100644 --- a/docs/pipeline-diagrams/src/App.jsx +++ b/docs/pipeline-diagrams/src/App.jsx @@ -1,4 +1,4 @@ -import { Routes, Route } from "react-router-dom"; +import { Routes, Route, Navigate } from "react-router-dom"; import Sidebar from "./components/Sidebar"; import Overview from "./stages/Overview"; import Stage0 from "./stages/Stage0"; @@ -10,6 +10,16 @@ import Stage5 from "./stages/Stage5"; import Stage6 from "./stages/Stage6"; import Stage7 from "./stages/Stage7"; import Stage8 from "./stages/Stage8"; +import UKOverview from "./stages/uk/Overview"; +import UKStage0 from "./stages/uk/Stage0"; +import UKStage1 from "./stages/uk/Stage1"; +import UKStage2 from "./stages/uk/Stage2"; +import UKStage3 from "./stages/uk/Stage3"; +import UKStage4 from "./stages/uk/Stage4"; +import UKStage5 from "./stages/uk/Stage5"; +import UKStage6 from "./stages/uk/Stage6"; +import UKStage7 from "./stages/uk/Stage7"; +import UKStage8 from "./stages/uk/Stage8"; export default function App() { return ( @@ -17,16 +27,27 @@ export default function App() {
- } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } />
diff --git a/docs/pipeline-diagrams/src/components/Sidebar.jsx b/docs/pipeline-diagrams/src/components/Sidebar.jsx index 054a7c4e5..4580aad63 100644 --- a/docs/pipeline-diagrams/src/components/Sidebar.jsx +++ b/docs/pipeline-diagrams/src/components/Sidebar.jsx @@ -1,17 +1,30 @@ -import { NavLink } from "react-router-dom"; +import { NavLink, useNavigate, useLocation } from "react-router-dom"; import { EDGE_LEGEND } from "../constants/edgeStyles"; -const stages = [ - { path: "/", label: "Overview", desc: "Cross-stage data flow" }, - { path: "/stage/0", label: "Stage 0", desc: "Raw Data Download" }, - { path: "/stage/1", label: "Stage 1", desc: "Base Dataset Construction" }, - { path: "/stage/2", label: "Stage 2", desc: "Extended CPS (PUF Clone)" }, - { path: "/stage/3", label: "Stage 3", desc: "Stratified CPS" }, - { path: "/stage/4", label: "Stage 4", desc: "Source Imputation" }, - { path: "/stage/5", label: "Stage 5", desc: "Matrix Build" }, - { path: "/stage/6", label: "Stage 6", desc: "Weight Fitting (L0)" }, - { path: "/stage/7", label: "Stage 7", desc: "Local Area H5 Build" }, - { path: "/stage/8", label: "Stage 8", desc: "Validation & Promotion" }, +const usStages = [ + { path: "/us", label: "Overview", desc: "Cross-stage data flow" }, + { path: "/us/stage/0", label: "Stage 0", desc: "Raw Data Download" }, + { path: "/us/stage/1", label: "Stage 1", desc: "Base Dataset Construction" }, + { path: "/us/stage/2", label: "Stage 2", desc: "Extended CPS (PUF Clone)" }, + { path: "/us/stage/3", label: "Stage 3", desc: "Stratified CPS" }, + { path: "/us/stage/4", label: "Stage 4", desc: "Source Imputation" }, + { path: "/us/stage/5", label: "Stage 5", desc: "Matrix Build" }, + { path: "/us/stage/6", label: "Stage 6", desc: "Weight Fitting (L0)" }, + { path: "/us/stage/7", label: "Stage 7", desc: "Local Area H5 Build" }, + { path: "/us/stage/8", label: "Stage 8", desc: "Validation & Promotion" }, +]; + +const ukStages = [ + { path: "/uk", label: "Overview", desc: "Cross-stage data flow" }, + { path: "/uk/stage/0", label: "Stage 0", desc: "Raw Data Download" }, + { path: "/uk/stage/1", label: "Stage 1", desc: "Base Dataset Construction" }, + { path: "/uk/stage/2", label: "Stage 2", desc: "Income Enhancement (SPI)" }, + { path: "/uk/stage/3", label: "Stage 3", desc: "Stratification", absent: true }, + { path: "/uk/stage/4", label: "Stage 4", desc: "Source Imputation" }, + { path: "/uk/stage/5", label: "Stage 5", desc: "Matrix Build" }, + { path: "/uk/stage/6", label: "Stage 6", desc: "Weight Fitting (Torch)" }, + { path: "/uk/stage/7", label: "Stage 7", desc: "Local Area Calibration" }, + { path: "/uk/stage/8", label: "Stage 8", desc: "Validation & Output" }, ]; const nodeLegend = [ @@ -22,18 +35,31 @@ const nodeLegend = [ { color: "bg-red-200 border-red-300 border-dashed", label: "Missing" }, { color: "bg-yellow-200 border-yellow-400", label: "External" }, { color: "bg-pink-200 border-pink-400", label: "US-Specific" }, + { color: "bg-teal-200 border-teal-400", label: "UK-Specific" }, + { color: "bg-gray-100 border-gray-300 border-dashed", label: "Absent" }, ]; export default function Sidebar() { + const navigate = useNavigate(); + const location = useLocation(); + const isUK = location.pathname.startsWith("/uk"); + const country = isUK ? "uk" : "us"; + const stages = isUK ? ukStages : usStages; + return (