From e70ac3ee8553918dfb5809a3e36d6c672a0579a2 Mon Sep 17 00:00:00 2001 From: lee youngjun Date: Sun, 21 Jun 2026 01:19:12 +0900 Subject: [PATCH 1/8] fix: restore frontend dev scripts --- package-lock.json | 1939 ++++++++++++++++++++++++++++++++++++++++----- package.json | 21 +- 2 files changed, 1763 insertions(+), 197 deletions(-) diff --git a/package-lock.json b/package-lock.json index 092b8de..b6f6070 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,418 @@ { - "name": "BioDockLab", + "name": "biodocklab", + "version": "2.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "biodocklab", + "version": "2.7.0", "license": "UNLICENSED", "dependencies": { "lucide-react": "^1.16.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "recharts": "^3.8.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.0.0", + "vite": "^8.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@reduxjs/toolkit": { @@ -46,106 +451,540 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "license": "MIT" - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "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-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "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==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-color": "*" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-time": "*" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-path": "*" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "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-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "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-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-color": { @@ -166,145 +1005,634 @@ "node": ">=12" } }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "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-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "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/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/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "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/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/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/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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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" - }, + "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" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", + "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" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, + "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" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "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": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" + "yallist": "^3.0.2" } }, - "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==", + "node_modules/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", "license": "ISC", - "engines": { - "node": ">=12" + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "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/es-toolkit": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", - "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "node_modules/nanoid": { + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.14.tgz", + "integrity": "sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, - "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "engines": { + "node": ">=18" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/lucide-react": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", - "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, "node_modules/react": { @@ -312,7 +1640,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -322,7 +1649,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -360,6 +1686,16 @@ } } }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/recharts": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", @@ -411,12 +1747,72 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "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", - "peer": true + "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/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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -424,6 +1820,76 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "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==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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/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", @@ -454,6 +1920,91 @@ "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "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.18", + "esbuild": "^0.27.0 || ^0.28.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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" } } } diff --git a/package.json b/package.json index b9ab6d8..20fda12 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,23 @@ { + "name": "biodocklab", + "version": "2.7.0", + "private": true, + "license": "UNLICENSED", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1 --port 5173", + "build": "vite build", + "preview": "vite preview --host 127.0.0.1 --port 4173" + }, "dependencies": { "lucide-react": "^1.16.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "recharts": "^3.8.1" }, - "private": true, - "license": "UNLICENSED" -} \ No newline at end of file + "devDependencies": { + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.0.0", + "vite": "^8.0.0" + } +} From 7cada75f68e197f4740f9e3746f32b650ca2d09a Mon Sep 17 00:00:00 2001 From: lee youngjun Date: Sun, 21 Jun 2026 01:37:26 +0900 Subject: [PATCH 2/8] fix: stabilize digital twin API endpoints --- backend/main.py | 380 ++++++++++++++++++++++++- sample_data/digital_twin_findings.json | 42 +++ 2 files changed, 407 insertions(+), 15 deletions(-) create mode 100644 sample_data/digital_twin_findings.json diff --git a/backend/main.py b/backend/main.py index 6af4ee2..3795c5e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,35 +1,385 @@ -from fastapi import FastAPI from pathlib import Path import json -app = FastAPI(title="BioDockLab API", version="2.1.0") +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +ROOT_DIR = Path(__file__).resolve().parents[1] +DATA_DIR = ROOT_DIR / "sample_data" +CORE_DATA_DIR = ROOT_DIR / "data" / "sample" + +app = FastAPI( + title="BioDockLab API", + version="2.7.0", + description="Patient digital twin, vital sign, clinical decision support API", +) + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://127.0.0.1:5173", + "http://127.0.0.1:5174", + "http://localhost:5173", + "http://localhost:5174", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) -DATA_PATH = Path(__file__).resolve().parent.parent / "data" / "sample" / "bio_experiments.json" +def load_json(path: Path, fallback): + if not path.exists(): + return fallback -def load_data(): - with open(DATA_PATH, "r", encoding="utf-8") as f: - return json.load(f) + try: + with path.open("r", encoding="utf-8") as file: + return json.load(file) + except Exception: + return fallback @app.get("/") def root(): return { "project": "BioDockLab", - "version": "2.1", - "message": "Bio AI research software platform API" + "version": "2.7", + "message": "Patient digital twin hospital AI platform API", + } + + +@app.get("/health") +def health(): + return { + "status": "ok", + "service": "biodocklab-api", + "version": "2.7.0", } +@app.get("/roles") +def get_roles(): + return [ + { + "id": "patient", + "name": "Patient", + "description": "검사 결과와 설명 리포트를 확인하는 사용자", + "focus": "알 권리, 결과 이해, 상담 준비", + }, + { + "id": "nurse", + "name": "Nurse", + "description": "바이탈사인과 인수인계 정보를 확인하는 사용자", + "focus": "바이탈사인, 투약 전 확인, 상태 변화", + }, + { + "id": "doctor", + "name": "Doctor", + "description": "검사 결과와 위험도 평가를 기반으로 판단을 보조받는 사용자", + "focus": "진단 보조, 치료 방향, 위험도 해석", + }, + { + "id": "pharmacist", + "name": "Pharmacist", + "description": "처방, 약물상호작용, 유전자 적합성을 검토하는 사용자", + "focus": "처방 검토, 상호작용, 안전성", + }, + { + "id": "security", + "name": "Security Officer", + "description": "접근 로그와 민감정보 사용을 감사하는 사용자", + "focus": "접근 제어, 감사 로그, 보안 정책", + }, + ] + + +@app.get("/role-detail/{role_id}") +def get_role_detail(role_id: str): + role_details = { + "patient": { + "title": "Patient Right-to-Know View", + "summary": "환자가 자신의 검사 결과, 위험도, 설명 리포트를 이해할 수 있도록 제공하는 화면", + "data_scope": ["검사 결과 요약", "위험도 설명", "의료진 상담 준비 자료", "환자 친화형 리포트"], + "restricted": ["타 환자 정보", "내부 의료진 메모", "보안 로그"], + }, + "nurse": { + "title": "Nurse Vital Sign & Handoff View", + "summary": "간호사가 바이탈사인, 인수인계, 상태 변화를 빠르게 확인하는 화면", + "data_scope": ["바이탈사인", "투약 전 확인", "인수인계 메모", "상태 변화 알림"], + "restricted": ["관리자 보안 정책", "연구자 전용 실험 데이터"], + }, + "doctor": { + "title": "Doctor Decision Support View", + "summary": "의사가 검사 결과와 AI 위험도 분석을 기반으로 판단을 보조받는 화면", + "data_scope": ["검사 결과", "AI 위험도 평가", "치료 방향 후보", "환자 상담 자료"], + "restricted": ["원무 서류 처리 내역", "시스템 내부 보안 설정"], + }, + "security": { + "title": "Security Audit View", + "summary": "보안관리자가 접근 기록, 권한 위반, 민감정보 접근을 감사하는 화면", + "data_scope": ["접근 로그", "권한 정책", "위험 이벤트", "민감정보 접근 기록"], + "restricted": ["환자 친화형 설명 화면 조작", "실험 결과 임의 수정"], + }, + } + + return role_details.get( + role_id, + { + "title": "Unknown Role", + "summary": "등록되지 않은 역할이다.", + "data_scope": [], + "restricted": [], + }, + ) + + @app.get("/experiments") def get_experiments(): - return load_data() + fallback = [ + { + "id": "ORG-001", + "domain": "Organoid", + "sample": "intestinal organoid", + "condition": "growth factor A + drug candidate X", + "success_rate": 78, + "risk_level": "Medium", + }, + { + "id": "CFPS-001", + "domain": "CFPS", + "sample": "cell-free protein synthesis", + "condition": "enzyme mix B + amino acid pool", + "success_rate": 84, + "risk_level": "Low", + }, + { + "id": "DT-001", + "domain": "Digital Twin", + "sample": "virtual patient model", + "condition": "treatment response prediction", + "success_rate": 69, + "risk_level": "High", + }, + ] + + data = load_json(CORE_DATA_DIR / "bio_experiments.json", fallback) + + if isinstance(data, dict): + return data.get("experiments", fallback) + + return data -@app.get("/experiments/{experiment_id}") -def get_experiment(experiment_id: str): - experiments = load_data() +@app.get("/analysis") +def get_analysis(): + experiments = get_experiments() + results = [] + for exp in experiments: - if exp["id"] == experiment_id: - return exp - return {"error": "Experiment not found"} \ No newline at end of file + success_rate = exp.get("success_rate", 0) + risk_level = exp.get("risk_level", "Medium") + + if success_rate >= 80 and risk_level == "Low": + priority = "High Priority" + recommendation = "Proceed to validation report" + elif success_rate >= 70: + priority = "Review" + recommendation = "Compare with baseline and request secondary review" + else: + priority = "Needs Improvement" + recommendation = "Adjust experiment condition and rerun analysis" + + results.append( + { + "id": exp.get("id"), + "priority": priority, + "risk_level": risk_level, + "recommendation": recommendation, + } + ) + + return results + + +@app.get("/platform-summary") +def get_platform_summary(): + experiments = get_experiments() + analysis = get_analysis() + roles = get_roles() + + high_priority = sum(1 for item in analysis if item["priority"] == "High Priority") + avg_success = round( + sum(exp.get("success_rate", 0) for exp in experiments) / max(len(experiments), 1), + 1, + ) + + return { + "roles": len(roles), + "experiments": len(experiments), + "high_priority": high_priority, + "average_success_rate": avg_success, + "research_directions": ["Organoid", "Surgery AI", "Quantum Biocomputing", "Digital Twin", "CFPS"], + } + + +@app.get("/vital-signs") +def get_vital_signs(): + return load_json(DATA_DIR / "vital_signs.json", []) + + +@app.get("/patient-reports") +def get_patient_reports(): + return load_json(DATA_DIR / "patient_reports.json", []) + + +@app.get("/patient-reports/{patient_id}") +def get_patient_report(patient_id: str): + reports = get_patient_reports() + + for report in reports: + if report.get("patient_id") == patient_id: + return report + + return { + "patient_id": patient_id, + "title": "Report Not Found", + "summary": "해당 환자 설명 리포트가 없다.", + "explanation": "", + "right_to_know": [], + } + + +@app.get("/hospital-summary") +def get_hospital_summary(): + vital_signs = get_vital_signs() + watch_count = sum(1 for item in vital_signs if item.get("status") == "Watch") + stable_count = sum(1 for item in vital_signs if item.get("status") == "Stable") + + avg_spo2 = round( + sum(item.get("spo2", 0) for item in vital_signs) / max(len(vital_signs), 1), + 1, + ) + + return { + "patients": len(vital_signs), + "stable": stable_count, + "watch": watch_count, + "average_spo2": avg_spo2, + } + + +@app.get("/doctor-decisions") +def get_doctor_decisions(): + return load_json(DATA_DIR / "doctor_decisions.json", []) + + +@app.get("/doctor-decisions/{patient_id}") +def get_doctor_decision(patient_id: str): + decisions = get_doctor_decisions() + + for decision in decisions: + if decision.get("patient_id") == patient_id: + return decision + + return { + "patient_id": patient_id, + "risk_level": "Unknown", + "clinical_summary": "해당 환자 판단 보조 데이터가 없다.", + "decision_support": [], + "next_action": "No action", + } + + +@app.get("/clinical-timeline") +def get_clinical_timeline(): + return load_json(DATA_DIR / "clinical_timeline.json", []) + + +@app.get("/clinical-timeline/{patient_id}") +def get_clinical_timeline_by_patient(patient_id: str): + timeline = get_clinical_timeline() + return [item for item in timeline if item.get("patient_id") == patient_id] + + +@app.get("/hospital-audit-events") +def get_hospital_audit_events(): + return load_json(DATA_DIR / "hospital_audit_events.json", []) + + +@app.get("/hospital-audit-summary") +def get_hospital_audit_summary(): + events = get_hospital_audit_events() + allowed = sum(1 for item in events if item.get("result") == "Allowed") + denied = sum(1 for item in events if item.get("result") == "Denied") + + return { + "events": len(events), + "allowed": allowed, + "denied": denied, + } + + +@app.get("/digital-twin-findings") +def get_digital_twin_findings(): + fallback = [ + { + "patient_id": "P-002", + "model_type": "human-trauma-digital-twin", + "overall_risk": "High", + "summary": "혈압 저하, 심박수 증가, 산소포화도 저하가 함께 관찰되어 내부 출혈 가능성을 시각적으로 추적한다.", + "findings": [ + { + "id": "BLEED-ABD-001", + "region": "Left Upper Abdomen", + "label": "복부 상부 출혈 의심", + "severity": "High", + "description": "저혈압과 빈맥이 동반되어 복부 내부 출혈 가능성이 있다.", + "x": 50, + "y": 44, + }, + { + "id": "BLEED-PEL-002", + "region": "Pelvic Region", + "label": "골반 부위 출혈 의심", + "severity": "High", + "description": "골반 부위 손상 또는 내부 출혈 가능성을 모니터링한다.", + "x": 50, + "y": 58, + }, + { + "id": "INJ-FEM-003", + "region": "Left Femur", + "label": "좌측 대퇴부 손상", + "severity": "Medium", + "description": "대퇴부 손상과 출혈 가능성을 함께 추적한다.", + "x": 42, + "y": 75, + }, + ], + "patient_explanation": [ + "현재 몸 안쪽에서 출혈이 의심되는 위치를 모형으로 보여준다.", + "빨간색은 의료진이 빠르게 확인해야 하는 위험 위치를 의미한다.", + "이 정보는 환자가 자신의 상태를 이해하고 의료진에게 질문할 수 있도록 돕는다.", + ], + } + ] + + return load_json(DATA_DIR / "digital_twin_findings.json", fallback) + + +@app.get("/digital-twin-findings/{patient_id}") +def get_digital_twin_finding_by_patient(patient_id: str): + twins = get_digital_twin_findings() + + for twin in twins: + if twin.get("patient_id") == patient_id: + return twin + + return { + "patient_id": patient_id, + "model_type": "unknown", + "overall_risk": "Unknown", + "summary": "해당 환자의 디지털 트윈 데이터가 없다.", + "findings": [], + "patient_explanation": [], + } diff --git a/sample_data/digital_twin_findings.json b/sample_data/digital_twin_findings.json new file mode 100644 index 0000000..ab065cd --- /dev/null +++ b/sample_data/digital_twin_findings.json @@ -0,0 +1,42 @@ +[ + { + "patient_id": "P-002", + "model_type": "human-trauma-digital-twin", + "overall_risk": "High", + "summary": "혈압 저하, 심박수 증가, 산소포화도 저하가 함께 관찰되어 내부 출혈 가능성을 시각적으로 추적한다.", + "findings": [ + { + "id": "BLEED-ABD-001", + "region": "Left Upper Abdomen", + "label": "복부 상부 출혈 의심", + "severity": "High", + "description": "저혈압과 빈맥이 동반되어 복부 내부 출혈 가능성이 있다.", + "x": 50, + "y": 44 + }, + { + "id": "BLEED-PEL-002", + "region": "Pelvic Region", + "label": "골반 부위 출혈 의심", + "severity": "High", + "description": "골반 부위 손상 또는 내부 출혈 가능성을 모니터링한다.", + "x": 50, + "y": 58 + }, + { + "id": "INJ-FEM-003", + "region": "Left Femur", + "label": "좌측 대퇴부 손상", + "severity": "Medium", + "description": "대퇴부 손상과 출혈 가능성을 함께 추적한다.", + "x": 42, + "y": 75 + } + ], + "patient_explanation": [ + "현재 몸 안쪽에서 출혈이 의심되는 위치를 모형으로 보여준다.", + "빨간색은 의료진이 빠르게 확인해야 하는 위험 위치를 의미한다.", + "이 정보는 환자가 자신의 상태를 이해하고 의료진에게 질문할 수 있도록 돕는다." + ] + } +] From e54d161ee1a8a6a6d23ebb4bc268607b991ac6fa Mon Sep 17 00:00:00 2001 From: lee youngjun Date: Sun, 21 Jun 2026 01:46:21 +0900 Subject: [PATCH 3/8] feat: rebuild BioDockLab v3 hospital main dashboard --- index.html | 12 ++ src/app/main.jsx | 421 ++++++++++++++++++++++++++++++++++++++ src/app/style.css | 501 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 934 insertions(+) create mode 100644 index.html create mode 100644 src/app/main.jsx create mode 100644 src/app/style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..f1344a3 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + BioDockLab v2.7 + + +
+ + + diff --git a/src/app/main.jsx b/src/app/main.jsx new file mode 100644 index 0000000..c0d8aeb --- /dev/null +++ b/src/app/main.jsx @@ -0,0 +1,421 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { createRoot } from "react-dom/client"; +import "./style.css"; + +const API_BASE = "http://127.0.0.1:8000"; + +const fallbackVitals = [ + { + patient_id: "P-001", + name: "Sample Patient A", + age: 42, + heart_rate: 88, + blood_pressure: "124/82", + temperature: 36.8, + spo2: 97, + respiratory_rate: 18, + status: "Stable", + note: "수술 후 회복 관찰 중. 산소포화도 안정적." + }, + { + patient_id: "P-002", + name: "Sample Patient B", + age: 67, + heart_rate: 112, + blood_pressure: "148/92", + temperature: 37.9, + spo2: 92, + respiratory_rate: 24, + status: "Watch", + note: "호흡수 증가 및 산소포화도 저하. 간호사 재확인 필요." + }, + { + patient_id: "P-003", + name: "Sample Patient C", + age: 55, + heart_rate: 76, + blood_pressure: "118/78", + temperature: 36.5, + spo2: 98, + respiratory_rate: 16, + status: "Stable", + note: "바이탈 안정. 정기 관찰 유지." + } +]; + +const fallbackReports = [ + { + patient_id: "P-002", + title: "주의 관찰 설명 리포트", + summary: "산소포화도 저하와 호흡수 증가가 관찰되어 의료진 확인이 필요하다.", + explanation: "환자가 자신의 상태를 이해할 수 있도록 위험 신호를 쉬운 언어로 설명합니다.", + right_to_know: [ + "산소포화도가 낮아졌다는 의미를 이해할 수 있다.", + "호흡수가 증가했을 때 어떤 확인이 필요한지 알 수 있다.", + "의료진에게 현재 증상과 불편감을 정확히 설명할 수 있다." + ] + } +]; + +const fallbackDecisions = [ + { + patient_id: "P-002", + risk_level: "Medium-High", + clinical_summary: "산소포화도 저하와 호흡수 증가가 관찰되어 의료진 확인이 필요하다.", + decision_support: [ + "간호사 바이탈 재확인", + "산소포화도 추세 확인", + "호흡 불편감 문진", + "필요 시 담당의 알림" + ], + next_action: "Clinical review required" + } +]; + +const fallbackTimeline = [ + { + time: "08:10", + type: "Vital Sign Alert", + role: "Nurse", + title: "산소포화도 저하 감지", + description: "SpO₂ 92%, 호흡수 24/min으로 주의 관찰 기준에 진입했다." + }, + { + time: "08:18", + type: "Nurse Handoff", + role: "Nurse", + title: "간호사 재확인 요청", + description: "호흡 불편감 문진 및 산소포화도 추세 확인이 필요하다." + }, + { + time: "08:27", + type: "Doctor Decision", + role: "Doctor", + title: "Clinical review required", + description: "담당 의료진 확인과 추가 모니터링이 필요하다." + }, + { + time: "08:35", + type: "Patient Report", + role: "Patient", + title: "환자 설명 리포트 생성", + description: "환자가 현재 상태를 이해할 수 있도록 쉬운 설명 리포트를 제공한다." + } +]; + +const fallbackTwin = { + patient_id: "P-002", + model_type: "prototype-risk-map", + overall_risk: "High", + summary: "실제 3D 인체 모델이 아니라, 바이탈사인 기반 위험 위치를 시각화하는 초기 디지털 트윈 모형입니다.", + findings: [ + { + id: "BLEED-ABD-001", + label: "복부 출혈 의심", + region: "Abdomen", + severity: "High", + description: "저혈압과 빈맥이 동반되어 내부 출혈 가능성을 표시합니다.", + x: 50, + y: 47 + }, + { + id: "BLEED-PEL-002", + label: "골반 부위 출혈 의심", + region: "Pelvic Region", + severity: "High", + description: "골반 손상 가능성을 함께 추적합니다.", + x: 50, + y: 59 + }, + { + id: "INJ-FEM-003", + label: "대퇴부 손상 가능성", + region: "Left Femur", + severity: "Medium", + description: "대퇴부 손상과 출혈 가능성을 추적합니다.", + x: 43, + y: 76 + } + ] +}; + +async function getJson(path, fallback) { + try { + const res = await fetch(`${API_BASE}${path}`); + if (!res.ok) return fallback; + return await res.json(); + } catch { + return fallback; + } +} + +function App() { + const [apiStatus, setApiStatus] = useState("fallback mode"); + const [vitals, setVitals] = useState(fallbackVitals); + const [reports, setReports] = useState(fallbackReports); + const [decisions, setDecisions] = useState(fallbackDecisions); + const [timeline, setTimeline] = useState(fallbackTimeline); + const [digitalTwin, setDigitalTwin] = useState(fallbackTwin); + const [selectedPatientId, setSelectedPatientId] = useState("P-002"); + + useEffect(() => { + async function load() { + const [health, vitalData, reportData, decisionData, timelineData, twinData] = + await Promise.all([ + getJson("/health", null), + getJson("/vital-signs", fallbackVitals), + getJson("/patient-reports", fallbackReports), + getJson("/doctor-decisions", fallbackDecisions), + getJson("/clinical-timeline/P-002", fallbackTimeline), + getJson("/digital-twin-findings/P-002", fallbackTwin) + ]); + + setApiStatus(health ? "connected" : "fallback mode"); + setVitals(vitalData.length ? vitalData : fallbackVitals); + setReports(reportData.length ? reportData : fallbackReports); + setDecisions(decisionData.length ? decisionData : fallbackDecisions); + setTimeline(timelineData.length ? timelineData : fallbackTimeline); + setDigitalTwin(twinData?.findings ? twinData : fallbackTwin); + } + + load(); + }, []); + + const selectedPatient = useMemo(() => { + return vitals.find((item) => item.patient_id === selectedPatientId) || vitals[0]; + }, [vitals, selectedPatientId]); + + const selectedReport = useMemo(() => { + return reports.find((item) => item.patient_id === selectedPatientId) || reports[0]; + }, [reports, selectedPatientId]); + + const selectedDecision = useMemo(() => { + return decisions.find((item) => item.patient_id === selectedPatientId) || decisions[0]; + }, [decisions, selectedPatientId]); + + const watchCount = vitals.filter((item) => item.status === "Watch").length; + const reportsReady = reports.length; + const doctorReviews = decisions.filter((item) => item.risk_level !== "Low").length; + + return ( +
+ + +
+
+
+ BioDockLab v3 · Hospital Main Dashboard +

병원 역할 기반 의료 데이터 플랫폼

+

+ 환자, 간호사, 의사, 보안관리자가 각자 필요한 의료 데이터를 확인하고 + 환자의 알 권리와 의료진 판단을 연결하는 제품 화면입니다. +

+
+ +
+ Backend API + {apiStatus} + v2 data science core +
+
+ +
+
+ Active Patients + {vitals.length} +

현재 관리 중인 환자

+
+
+ Watch Cases + {watchCount} +

주의 관찰 필요

+
+
+ Doctor Review + {doctorReviews} +

의사 확인 필요

+
+
+ Reports Ready + {reportsReady} +

환자 설명 리포트

+
+
+ +
+
+
+

Patient List

+ 오늘 모니터링 대상 +
+ + {vitals.map((patient) => ( + + ))} +
+ +
+
+
+ {selectedPatient.patient_id} +

{selectedPatient.name}

+

{selectedPatient.note}

+
+ + {selectedPatient.status} + +
+ +
+
+ Heart Rate + {selectedPatient.heart_rate} + bpm +
+
+ Blood Pressure + {selectedPatient.blood_pressure} + mmHg +
+
+ Temperature + {selectedPatient.temperature}℃ + body temp +
+
+ SpO₂ + {selectedPatient.spo2}% + oxygen +
+
+ +
+ Doctor Decision Support +

{selectedDecision?.next_action}

+

{selectedDecision?.clinical_summary}

+
    + {selectedDecision?.decision_support?.map((item) => ( +
  • {item}
  • + ))} +
+
+
+ + +
+ +
+
+
+

Clinical Timeline

+ 바이탈 → 판단 → 설명 리포트 흐름 +
+ + {timeline.map((item) => ( +
+ {item.time} +
+ {item.type} · {item.role} +

{item.title}

+
+
+ ))} +
+ +
+
+

Digital Twin Findings

+ 의료진 확인용 위험 부위 요약 +
+ + {digitalTwin.findings.map((finding) => ( +
+
+ {finding.label} + {finding.region} +

{finding.description}

+
+ + {finding.severity} + +
+ ))} +
+
+
+
+ ); +} + +createRoot(document.getElementById("root")).render(); diff --git a/src/app/style.css b/src/app/style.css new file mode 100644 index 0000000..f67681c --- /dev/null +++ b/src/app/style.css @@ -0,0 +1,501 @@ +:root { + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: #172033; + background: #f4f7fb; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: #f4f7fb; +} + +button { + font: inherit; +} + +.v3-shell { + min-height: 100vh; + display: grid; + grid-template-columns: 260px 1fr; +} + +.sidebar { + height: 100vh; + position: sticky; + top: 0; + background: #ffffff; + border-right: 1px solid #e1e9f3; + padding: 24px 18px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.brand { + display: flex; + align-items: center; + gap: 12px; +} + +.brand-mark { + width: 44px; + height: 44px; + border-radius: 14px; + background: linear-gradient(135deg, #2563eb, #14b8a6); + color: white; + display: grid; + place-items: center; + font-weight: 900; +} + +.brand strong { + display: block; + font-size: 18px; + color: #0f172a; +} + +.brand span { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; +} + +.side-nav { + display: grid; + gap: 8px; +} + +.side-nav button { + border: 0; + text-align: left; + padding: 12px 14px; + border-radius: 14px; + background: transparent; + color: #475569; + cursor: pointer; +} + +.side-nav button.active { + background: #eff6ff; + color: #1d4ed8; + font-weight: 800; +} + +.side-card { + margin-top: auto; + padding: 16px; + border-radius: 18px; + background: #f0fdfa; + border: 1px solid #ccfbf1; +} + +.side-card strong, +.side-card span { + display: block; +} + +.side-card span { + margin-top: 8px; + color: #0f766e; + line-height: 1.5; + font-size: 13px; +} + +.content { + padding: 34px; +} + +.header { + display: flex; + justify-content: space-between; + gap: 24px; + align-items: flex-start; + margin-bottom: 24px; +} + +.eyebrow { + color: #2563eb; + font-size: 13px; + font-weight: 900; +} + +h1 { + margin: 10px 0; + font-size: 38px; + line-height: 1.1; + letter-spacing: -0.04em; +} + +.header p { + max-width: 840px; + color: #64748b; + line-height: 1.6; +} + +.api-card { + min-width: 220px; + padding: 18px; + border-radius: 20px; + background: white; + border: 1px solid #e1e9f3; + box-shadow: 0 16px 48px rgba(15, 23, 42, 0.06); +} + +.api-card span, +.api-card strong, +.api-card small { + display: block; +} + +.api-card strong { + margin: 8px 0; + color: #16a34a; +} + +.api-card.fallback strong { + color: #f59e0b; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + margin-bottom: 18px; +} + +.stat-card, +.panel { + background: #ffffff; + border: 1px solid #e1e9f3; + border-radius: 24px; + box-shadow: 0 16px 48px rgba(15, 23, 42, 0.05); +} + +.stat-card { + padding: 22px; +} + +.stat-card span { + color: #64748b; + font-size: 13px; +} + +.stat-card strong { + display: block; + margin-top: 8px; + font-size: 34px; + color: #0f172a; +} + +.stat-card.warning strong { + color: #f59e0b; +} + +.stat-card p { + margin: 8px 0 0; + color: #94a3b8; + font-size: 13px; +} + +.main-grid { + display: grid; + grid-template-columns: 300px minmax(0, 1fr) 380px; + gap: 18px; +} + +.panel { + padding: 22px; +} + +.panel-title { + margin-bottom: 16px; +} + +.panel-title h2 { + margin: 0; + font-size: 20px; +} + +.panel-title span { + display: block; + margin-top: 6px; + color: #64748b; + font-size: 13px; +} + +.patient-list { + display: grid; + gap: 12px; + align-content: start; +} + +.patient-row { + border: 1px solid #e2e8f0; + background: #f8fafc; + border-radius: 18px; + padding: 14px; + display: flex; + justify-content: space-between; + gap: 12px; + text-align: left; + cursor: pointer; +} + +.patient-row.selected { + background: #eff6ff; + border-color: #bfdbfe; +} + +.patient-row strong, +.patient-row span { + display: block; +} + +.patient-row span { + margin-top: 5px; + color: #64748b; + font-size: 13px; +} + +em { + font-style: normal; + padding: 7px 10px; + border-radius: 999px; + background: #f0fdf4; + color: #15803d; + font-weight: 900; + font-size: 12px; +} + +em.watch { + background: #fffbeb; + color: #b45309; +} + +em.big { + font-size: 14px; + align-self: flex-start; +} + +.patient-hero { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; + margin-bottom: 18px; +} + +.patient-chip { + display: inline-flex; + padding: 7px 10px; + border-radius: 999px; + background: #eff6ff; + color: #1d4ed8; + border: 1px solid #bfdbfe; + font-size: 12px; + font-weight: 900; +} + +.patient-hero h2 { + margin: 12px 0 8px; +} + +.patient-hero p, +.doctor-box p, +.report-panel p, +.twin-panel p, +.finding-row p { + color: #64748b; + line-height: 1.55; +} + +.vital-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.vital-grid div { + padding: 14px; + border-radius: 18px; + background: #f8fafc; + border: 1px solid #e2e8f0; +} + +.vital-grid span, +.vital-grid small { + color: #64748b; + font-size: 12px; +} + +.vital-grid strong { + display: block; + margin: 6px 0; + font-size: 22px; +} + +.doctor-box { + margin-top: 18px; + padding: 18px; + border-radius: 20px; + background: #fffbeb; + border: 1px solid #fde68a; +} + +.doctor-box h3 { + margin: 10px 0; +} + +.doctor-box li, +.report-panel li { + margin-bottom: 8px; + color: #334155; +} + +.right-column { + display: grid; + gap: 18px; +} + +.mini-body { + position: relative; + height: 280px; + border-radius: 22px; + background: linear-gradient(180deg, #f8fbff, #eef6ff); + border: 1px solid #dbeafe; + margin-bottom: 14px; + overflow: hidden; +} + +.body-head, +.body-torso, +.body-leg { + position: absolute; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(180deg, rgba(148, 163, 184, 0.45), rgba(219, 234, 254, 0.75)); + border: 1px solid rgba(37, 99, 235, 0.18); +} + +.body-head { + top: 26px; + width: 44px; + height: 52px; + border-radius: 50%; +} + +.body-torso { + top: 86px; + width: 86px; + height: 112px; + border-radius: 44px 44px 28px 28px; +} + +.body-leg { + top: 190px; + width: 28px; + height: 72px; + border-radius: 999px; +} + +.body-leg.left { + left: 45%; +} + +.body-leg.right { + left: 55%; +} + +.marker { + position: absolute; + width: 20px; + height: 20px; + transform: translate(-50%, -50%); + border-radius: 999px; + border: 3px solid white; + background: #ef4444; + box-shadow: 0 0 0 7px rgba(239, 68, 68, 0.15); +} + +.marker.medium { + background: #f59e0b; + box-shadow: 0 0 0 7px rgba(245, 158, 11, 0.15); +} + +.bottom-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; + margin-top: 18px; +} + +.timeline-row { + display: grid; + grid-template-columns: 70px 1fr; + gap: 14px; + padding: 14px 0; + border-top: 1px solid #e2e8f0; +} + +.timeline-row:first-of-type { + border-top: 0; +} + +.timeline-row strong { + color: #1d4ed8; +} + +.timeline-row span { + color: #64748b; + font-size: 13px; +} + +.timeline-row p { + margin: 5px 0 0; + color: #0f172a; +} + +.finding-row { + display: flex; + justify-content: space-between; + gap: 14px; + padding: 14px 0; + border-top: 1px solid #e2e8f0; +} + +.finding-row:first-of-type { + border-top: 0; +} + +.finding-row strong, +.finding-row span { + display: block; +} + +.finding-row span { + margin-top: 4px; + color: #64748b; + font-size: 13px; +} + +@media (max-width: 1200px) { + .v3-shell, + .main-grid, + .bottom-grid, + .header, + .stats-grid, + .vital-grid { + grid-template-columns: 1fr; + display: grid; + } + + .sidebar { + position: relative; + height: auto; + } +} From 638af24ddf1bec84c47509f0b2e6bb1cd0a59085 Mon Sep 17 00:00:00 2001 From: lee youngjun Date: Sun, 21 Jun 2026 01:52:17 +0900 Subject: [PATCH 4/8] feat: sync v3 patient detail panels by selected patient --- sample_data/clinical_timeline.json | 50 ++++++++++++++++++++ sample_data/digital_twin_findings.json | 63 +++++++++++++++++++------- sample_data/doctor_decisions.json | 36 +++++++++++++++ sample_data/patient_reports.json | 35 ++++++++++++++ src/app/main.jsx | 23 +++++++--- 5 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 sample_data/clinical_timeline.json create mode 100644 sample_data/doctor_decisions.json create mode 100644 sample_data/patient_reports.json diff --git a/sample_data/clinical_timeline.json b/sample_data/clinical_timeline.json new file mode 100644 index 0000000..1d381b7 --- /dev/null +++ b/sample_data/clinical_timeline.json @@ -0,0 +1,50 @@ +[ + { + "time": "08:00", + "patient_id": "P-001", + "type": "Routine Check", + "role": "Nurse", + "title": "정기 바이탈 확인", + "description": "주요 바이탈사인이 안정 범위에 있어 정기 관찰을 유지한다." + }, + { + "time": "08:10", + "patient_id": "P-002", + "type": "Vital Sign Alert", + "role": "Nurse", + "title": "산소포화도 저하 감지", + "description": "SpO₂ 92%, 호흡수 24/min으로 주의 관찰 기준에 진입했다." + }, + { + "time": "08:18", + "patient_id": "P-002", + "type": "Nurse Handoff", + "role": "Nurse", + "title": "간호사 재확인 요청", + "description": "호흡 불편감 문진 및 산소포화도 추세 확인이 필요하다." + }, + { + "time": "08:27", + "patient_id": "P-002", + "type": "Doctor Decision", + "role": "Doctor", + "title": "Clinical review required", + "description": "담당 의료진 확인과 추가 모니터링이 필요하다." + }, + { + "time": "08:35", + "patient_id": "P-002", + "type": "Patient Report", + "role": "Patient", + "title": "환자 설명 리포트 생성", + "description": "환자가 현재 상태를 이해할 수 있도록 쉬운 설명 리포트를 제공한다." + }, + { + "time": "08:45", + "patient_id": "P-003", + "type": "Recovery Check", + "role": "Nurse", + "title": "회복 경과 관찰", + "description": "바이탈 안정 상태를 유지하고 있으며 정기 관찰을 지속한다." + } +] diff --git a/sample_data/digital_twin_findings.json b/sample_data/digital_twin_findings.json index ab065cd..abdc106 100644 --- a/sample_data/digital_twin_findings.json +++ b/sample_data/digital_twin_findings.json @@ -1,42 +1,71 @@ [ + { + "patient_id": "P-001", + "model_type": "prototype-risk-map", + "overall_risk": "Low", + "summary": "현재 신체 위험 위치 시각화에서 급성 출혈 의심 부위는 낮게 표시된다.", + "findings": [ + { + "id": "MON-001", + "region": "Chest", + "label": "정기 관찰", + "severity": "Low", + "description": "현재 급성 위험 소견은 낮으며 정기 관찰을 유지한다.", + "x": 50, + "y": 38 + } + ] + }, { "patient_id": "P-002", - "model_type": "human-trauma-digital-twin", + "model_type": "prototype-risk-map", "overall_risk": "High", - "summary": "혈압 저하, 심박수 증가, 산소포화도 저하가 함께 관찰되어 내부 출혈 가능성을 시각적으로 추적한다.", + "summary": "실제 3D 인체 모델이 아니라, 바이탈사인 기반 위험 위치를 시각화하는 초기 디지털 트윈 모형입니다.", "findings": [ { "id": "BLEED-ABD-001", - "region": "Left Upper Abdomen", - "label": "복부 상부 출혈 의심", + "region": "Abdomen", + "label": "복부 출혈 의심", "severity": "High", - "description": "저혈압과 빈맥이 동반되어 복부 내부 출혈 가능성이 있다.", + "description": "저혈압과 빈맥이 동반되어 내부 출혈 가능성을 표시합니다.", "x": 50, - "y": 44 + "y": 47 }, { "id": "BLEED-PEL-002", "region": "Pelvic Region", "label": "골반 부위 출혈 의심", "severity": "High", - "description": "골반 부위 손상 또는 내부 출혈 가능성을 모니터링한다.", + "description": "골반 손상 가능성을 함께 추적합니다.", "x": 50, - "y": 58 + "y": 59 }, { "id": "INJ-FEM-003", "region": "Left Femur", - "label": "좌측 대퇴부 손상", + "label": "대퇴부 손상 가능성", "severity": "Medium", - "description": "대퇴부 손상과 출혈 가능성을 함께 추적한다.", - "x": 42, - "y": 75 + "description": "대퇴부 손상과 출혈 가능성을 추적합니다.", + "x": 43, + "y": 76 + } + ] + }, + { + "patient_id": "P-003", + "model_type": "prototype-risk-map", + "overall_risk": "Low", + "summary": "현재 위험 위치 시각화에서 이상 징후는 낮으며 회복 경과를 관찰한다.", + "findings": [ + { + "id": "MON-003", + "region": "General", + "label": "회복 관찰", + "severity": "Low", + "description": "현재 주요 위험 위치는 낮게 표시되며 회복 경과를 확인한다.", + "x": 50, + "y": 52 } - ], - "patient_explanation": [ - "현재 몸 안쪽에서 출혈이 의심되는 위치를 모형으로 보여준다.", - "빨간색은 의료진이 빠르게 확인해야 하는 위험 위치를 의미한다.", - "이 정보는 환자가 자신의 상태를 이해하고 의료진에게 질문할 수 있도록 돕는다." ] } ] diff --git a/sample_data/doctor_decisions.json b/sample_data/doctor_decisions.json new file mode 100644 index 0000000..69be51b --- /dev/null +++ b/sample_data/doctor_decisions.json @@ -0,0 +1,36 @@ +[ + { + "patient_id": "P-001", + "risk_level": "Low", + "clinical_summary": "현재 바이탈사인은 안정적이며 급성 악화 신호는 낮다.", + "decision_support": [ + "정기 관찰 유지", + "환자 설명 리포트 제공", + "추가 검사 필요성은 낮음" + ], + "next_action": "Routine monitoring" + }, + { + "patient_id": "P-002", + "risk_level": "Medium-High", + "clinical_summary": "산소포화도 저하와 호흡수 증가가 관찰되어 의료진 확인이 필요하다.", + "decision_support": [ + "간호사 바이탈 재확인", + "산소포화도 추세 확인", + "호흡 불편감 문진", + "필요 시 담당의 알림" + ], + "next_action": "Clinical review required" + }, + { + "patient_id": "P-003", + "risk_level": "Low", + "clinical_summary": "바이탈이 안정 범위에 있으며 정기 관찰을 유지할 수 있다.", + "decision_support": [ + "정기 관찰 유지", + "환자 상태 변화 시 재평가", + "퇴원 또는 회복 계획 검토 가능" + ], + "next_action": "Continue observation" + } +] diff --git a/sample_data/patient_reports.json b/sample_data/patient_reports.json new file mode 100644 index 0000000..100b9c5 --- /dev/null +++ b/sample_data/patient_reports.json @@ -0,0 +1,35 @@ +[ + { + "patient_id": "P-001", + "title": "안정 상태 설명 리포트", + "summary": "현재 주요 바이탈사인은 안정 범위에 있으며 급성 위험 신호는 낮다.", + "explanation": "심박수, 혈압, 체온, 산소포화도가 안정적으로 유지되고 있어 정기 관찰을 유지한다.", + "right_to_know": [ + "현재 바이탈사인이 안정 범위에 있음을 확인할 수 있다.", + "정기 관찰이 필요한 이유를 이해할 수 있다.", + "의료진 상담 전에 현재 상태를 쉽게 파악할 수 있다." + ] + }, + { + "patient_id": "P-002", + "title": "주의 관찰 설명 리포트", + "summary": "산소포화도 저하와 호흡수 증가가 관찰되어 의료진 확인이 필요하다.", + "explanation": "산소포화도와 호흡수가 정상 범위에서 벗어날 가능성이 있어 간호사 또는 담당 의료진의 재확인이 필요하다.", + "right_to_know": [ + "산소포화도가 낮아졌다는 의미를 이해할 수 있다.", + "호흡수가 증가했을 때 어떤 확인이 필요한지 알 수 있다.", + "의료진에게 현재 증상과 불편감을 정확히 설명할 수 있다." + ] + }, + { + "patient_id": "P-003", + "title": "정기 관찰 설명 리포트", + "summary": "현재 바이탈은 안정 범위에 있으며 정기 관찰을 유지할 수 있다.", + "explanation": "특별한 급성 위험 신호는 낮지만, 회복 경과를 보기 위해 정기적인 바이탈 확인을 유지한다.", + "right_to_know": [ + "현재 상태가 안정적이라는 점을 확인할 수 있다.", + "왜 계속 관찰이 필요한지 이해할 수 있다.", + "상태 변화가 있을 때 어떤 점을 말해야 하는지 알 수 있다." + ] + } +] diff --git a/src/app/main.jsx b/src/app/main.jsx index c0d8aeb..40a5994 100644 --- a/src/app/main.jsx +++ b/src/app/main.jsx @@ -154,7 +154,7 @@ function App() { const [vitals, setVitals] = useState(fallbackVitals); const [reports, setReports] = useState(fallbackReports); const [decisions, setDecisions] = useState(fallbackDecisions); - const [timeline, setTimeline] = useState(fallbackTimeline); + const [allTimeline, setAllTimeline] = useState(fallbackTimeline); const [digitalTwin, setDigitalTwin] = useState(fallbackTwin); const [selectedPatientId, setSelectedPatientId] = useState("P-002"); @@ -166,7 +166,7 @@ function App() { getJson("/vital-signs", fallbackVitals), getJson("/patient-reports", fallbackReports), getJson("/doctor-decisions", fallbackDecisions), - getJson("/clinical-timeline/P-002", fallbackTimeline), + getJson("/clinical-timeline", fallbackTimeline), getJson("/digital-twin-findings/P-002", fallbackTwin) ]); @@ -193,6 +193,17 @@ function App() { return decisions.find((item) => item.patient_id === selectedPatientId) || decisions[0]; }, [decisions, selectedPatientId]); + const selectedTimeline = useMemo(() => { + const filtered = allTimeline.filter((item) => item.patient_id === selectedPatientId); + return filtered.length ? filtered : fallbackTimeline.filter((item) => item.patient_id === selectedPatientId); + }, [allTimeline, selectedPatientId]); + + const selectedTwin = useMemo(() => { + if (digitalTwin?.patient_id === selectedPatientId) return digitalTwin; + const fallbackList = Array.isArray(fallbackTwin) ? fallbackTwin : [fallbackTwin]; + return fallbackList.find((item) => item.patient_id === selectedPatientId) || digitalTwin; + }, [digitalTwin, selectedPatientId]); + const watchCount = vitals.filter((item) => item.status === "Watch").length; const reportsReady = reports.length; const doctorReviews = decisions.filter((item) => item.risk_level !== "Low").length; @@ -361,7 +372,7 @@ function App() {
- {digitalTwin.findings.map((finding) => ( + {selectedTwin.findings.map((finding) => ( -

{digitalTwin.summary}

+

{selectedTwin.summary}

@@ -382,7 +393,7 @@ function App() { 바이탈 → 판단 → 설명 리포트 흐름
- {timeline.map((item) => ( + {selectedTimeline.map((item) => (
{item.time}
@@ -399,7 +410,7 @@ function App() { 의료진 확인용 위험 부위 요약
- {digitalTwin.findings.map((finding) => ( + {selectedTwin.findings.map((finding) => (
{finding.label} From 7f0d143734b8dcbb0443ce631b9c7b1e1c2a9e44 Mon Sep 17 00:00:00 2001 From: lee youngjun Date: Sun, 21 Jun 2026 01:55:52 +0900 Subject: [PATCH 5/8] feat: add v3 role-based view modes --- src/app/main.jsx | 36 +++- src/app/main.v3.backup.jsx | 432 +++++++++++++++++++++++++++++++++++++ src/app/style.css | 71 ++++++ 3 files changed, 531 insertions(+), 8 deletions(-) create mode 100644 src/app/main.v3.backup.jsx diff --git a/src/app/main.jsx b/src/app/main.jsx index 40a5994..6c4ab2c 100644 --- a/src/app/main.jsx +++ b/src/app/main.jsx @@ -157,6 +157,7 @@ function App() { const [allTimeline, setAllTimeline] = useState(fallbackTimeline); const [digitalTwin, setDigitalTwin] = useState(fallbackTwin); const [selectedPatientId, setSelectedPatientId] = useState("P-002"); + const [roleMode, setRoleMode] = useState("dashboard"); useEffect(() => { async function load() { @@ -209,7 +210,7 @@ function App() { const doctorReviews = decisions.filter((item) => item.risk_level !== "Low").length; return ( -
+