diff --git a/README.md b/README.md
index 1fc9e48..fe540d8 100644
--- a/README.md
+++ b/README.md
@@ -506,4 +506,19 @@ BioDockLab is a research software prototype.
It is not a medical device, diagnostic tool, clinical decision-making system, or validated biological prediction system.
-All simulation and analysis results are for software demonstration and research workflow exploration only.
\ No newline at end of file
+All simulation and analysis results are for software demonstration and research workflow exploration only.
+---
+
+## Hand-Drawn System Concept
+
+Before implementation, BioDockLab was organized through hand-drawn system sketches to clarify the platform architecture, medical/bio data flow, AI analysis layer, simulation direction, and role-based access model.
+
+### System Overview
+
+
+
+### Medical / Bio AI Map
+
+
+
+More details: [Handdrawn System Concept](docs/architecture/Handdrawn_System_Concept.md)
diff --git a/assets/concept/biodocklab_handdrawn_medical_ai_map.png b/assets/concept/biodocklab_handdrawn_medical_ai_map.png
new file mode 100644
index 0000000..f21f61d
Binary files /dev/null and b/assets/concept/biodocklab_handdrawn_medical_ai_map.png differ
diff --git a/assets/concept/biodocklab_handdrawn_system_overview.png b/assets/concept/biodocklab_handdrawn_system_overview.png
new file mode 100644
index 0000000..04134dc
Binary files /dev/null and b/assets/concept/biodocklab_handdrawn_system_overview.png differ
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/docs/architecture/Handdrawn_System_Concept.md b/docs/architecture/Handdrawn_System_Concept.md
new file mode 100644
index 0000000..97c1438
--- /dev/null
+++ b/docs/architecture/Handdrawn_System_Concept.md
@@ -0,0 +1,92 @@
+# BioDockLab Hand-Drawn System Concept
+
+BioDockLab is a role-based medical and bio AI research platform that connects medical data, biological experiment records, AI analysis, simulation, and explainable reports.
+
+This document contains early hand-drawn system sketches for organizing the platform structure before full implementation.
+
+---
+
+## 1. System Overview
+
+
+
+The first concept sketch shows the overall BioDockLab architecture.
+
+Core structure:
+
+```text
+Role-Based Access
+→ Data Input
+→ AI Analysis
+→ Risk / Priority Scoring
+→ Simulation
+→ Report
+→ Explainable Output
+The purpose is to connect patient-understandable data, medical staff decision support, and reusable research data into one software platform.
+
+2. Medical / Bio AI Concept Map
+
+The second concept sketch focuses on the medical and bio AI direction of BioDockLab.
+
+Key components:
+
+Vital sign data
+Experiment data
+AI risk scoring
+Response prediction
+Digital twin simulation
+Organoid / 3D model expansion
+CFPS expansion
+Role-based access
+Future medical technology tracking
+3. Developer Position
+
+BioDockLab is not designed as a clinical replacement system.
+
+The developer position of this project is to design the software structure that connects:
+
+medical data
+biological research data
+AI analysis
+simulation logic
+report automation
+explainable user interfaces
+
+The project focuses on building a platform architecture where medical and biological data can be structured, analyzed, simulated, and explained.
+
+4. Design Principles
+
+BioDockLab follows four design principles.
+
+Patient-understandable data
+Medical staff decision support
+Research data reusability
+Expandable software architecture
+
+The core idea is:
+
+Data becomes meaningful when it can be understood, connected, and reused.
+
+5. Module Direction
+Vital Sign Module
+→ Patient status summary / nurse handoff / basic monitoring
+
+Experiment Module
+→ experiment condition / success rate / risk level / notes
+
+AI Analysis Module
+→ risk classification / priority scoring / response prediction
+
+Simulation Module
+→ digital twin / organoid / CFPS simulation
+
+Report Module
+→ Markdown report / PDF report / summary output
+
+Security Module
+→ role-based access / audit log / sensitive data control
+6. Status
+
+These sketches are early architecture notes.
+
+They are used to guide the development direction of BioDockLab before implementation is fully connected across frontend, backend, AI analysis, simulation, and reporting layers.
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/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"
+ }
+}
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
new file mode 100644
index 0000000..abdc106
--- /dev/null
+++ b/sample_data/digital_twin_findings.json
@@ -0,0 +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": "prototype-risk-map",
+ "overall_risk": "High",
+ "summary": "실제 3D 인체 모델이 아니라, 바이탈사인 기반 위험 위치를 시각화하는 초기 디지털 트윈 모형입니다.",
+ "findings": [
+ {
+ "id": "BLEED-ABD-001",
+ "region": "Abdomen",
+ "label": "복부 출혈 의심",
+ "severity": "High",
+ "description": "저혈압과 빈맥이 동반되어 내부 출혈 가능성을 표시합니다.",
+ "x": 50,
+ "y": 47
+ },
+ {
+ "id": "BLEED-PEL-002",
+ "region": "Pelvic Region",
+ "label": "골반 부위 출혈 의심",
+ "severity": "High",
+ "description": "골반 손상 가능성을 함께 추적합니다.",
+ "x": 50,
+ "y": 59
+ },
+ {
+ "id": "INJ-FEM-003",
+ "region": "Left Femur",
+ "label": "대퇴부 손상 가능성",
+ "severity": "Medium",
+ "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
+ }
+ ]
+ }
+]
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/hospital_audit_events.json b/sample_data/hospital_audit_events.json
new file mode 100644
index 0000000..7a01492
--- /dev/null
+++ b/sample_data/hospital_audit_events.json
@@ -0,0 +1,68 @@
+[
+ {
+ "id": "AUD-001",
+ "time": "08:02",
+ "actor": "Nurse",
+ "actor_name": "간호사 A",
+ "patient_id": "P-001",
+ "action": "Viewed vital signs",
+ "data_scope": "Vital Signs",
+ "result": "Allowed",
+ "reason": "정기 바이탈 확인"
+ },
+ {
+ "id": "AUD-002",
+ "time": "08:10",
+ "actor": "Nurse",
+ "actor_name": "간호사 B",
+ "patient_id": "P-002",
+ "action": "Viewed vital signs",
+ "data_scope": "Vital Signs",
+ "result": "Allowed",
+ "reason": "산소포화도 저하 확인"
+ },
+ {
+ "id": "AUD-003",
+ "time": "08:27",
+ "actor": "Doctor",
+ "actor_name": "담당의 A",
+ "patient_id": "P-002",
+ "action": "Viewed doctor decision support",
+ "data_scope": "Doctor Decision",
+ "result": "Allowed",
+ "reason": "Clinical review required"
+ },
+ {
+ "id": "AUD-004",
+ "time": "08:35",
+ "actor": "Patient",
+ "actor_name": "Sample Patient B",
+ "patient_id": "P-002",
+ "action": "Viewed patient explanation report",
+ "data_scope": "Patient Report",
+ "result": "Allowed",
+ "reason": "환자 알 권리 리포트 확인"
+ },
+ {
+ "id": "AUD-005",
+ "time": "08:39",
+ "actor": "Researcher",
+ "actor_name": "연구자 계정",
+ "patient_id": "P-002",
+ "action": "Tried to access identifiable patient report",
+ "data_scope": "Patient Report",
+ "result": "Denied",
+ "reason": "연구자 권한은 식별 가능한 환자 리포트 접근 불가"
+ },
+ {
+ "id": "AUD-006",
+ "time": "08:45",
+ "actor": "Security Officer",
+ "actor_name": "보안관리자",
+ "patient_id": "P-002",
+ "action": "Reviewed access log",
+ "data_scope": "Audit Log",
+ "result": "Allowed",
+ "reason": "민감정보 접근 이력 점검"
+ }
+]
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
new file mode 100644
index 0000000..1a1631a
--- /dev/null
+++ b/src/app/main.jsx
@@ -0,0 +1,518 @@
+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 fallbackAuditEvents = [
+ {
+ id: "AUD-001",
+ time: "08:02",
+ actor: "Nurse",
+ actor_name: "간호사 A",
+ patient_id: "P-001",
+ action: "Viewed vital signs",
+ data_scope: "Vital Signs",
+ result: "Allowed",
+ reason: "정기 바이탈 확인"
+ },
+ {
+ id: "AUD-005",
+ time: "08:39",
+ actor: "Researcher",
+ actor_name: "연구자 계정",
+ patient_id: "P-002",
+ action: "Tried to access identifiable patient report",
+ data_scope: "Patient Report",
+ result: "Denied",
+ reason: "연구자 권한은 식별 가능한 환자 리포트 접근 불가"
+ }
+];
+
+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 [allTimeline, setAllTimeline] = useState(fallbackTimeline);
+ const [digitalTwin, setDigitalTwin] = useState(fallbackTwin);
+ const [auditEvents, setAuditEvents] = useState(fallbackAuditEvents);
+ const [selectedPatientId, setSelectedPatientId] = useState("P-002");
+ const [roleMode, setRoleMode] = useState("dashboard");
+
+ useEffect(() => {
+ async function load() {
+ const [health, vitalData, reportData, decisionData, timelineData, twinData, auditData] =
+ await Promise.all([
+ getJson("/health", null),
+ getJson("/vital-signs", fallbackVitals),
+ getJson("/patient-reports", fallbackReports),
+ getJson("/doctor-decisions", fallbackDecisions),
+ getJson("/clinical-timeline", fallbackTimeline),
+ getJson("/digital-twin-findings/P-002", fallbackTwin),
+ getJson("/hospital-audit-events", fallbackAuditEvents)
+ ]);
+
+ 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);
+ setAuditEvents(auditData.length ? auditData : fallbackAuditEvents);
+ }
+
+ 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 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 selectedAuditEvents = useMemo(() => {
+ return auditEvents.filter((item) => item.patient_id === selectedPatientId);
+ }, [auditEvents, selectedPatientId]);
+
+ const deniedAuditCount = auditEvents.filter((item) => item.result === "Denied").length;
+
+ const watchCount = vitals.filter((item) => item.status === "Watch").length;
+ const reportsReady = reports.length;
+ const doctorReviews = decisions.filter((item) => item.risk_level !== "Low").length;
+
+ return (
+
+
+
+
B
+
+ BioDockLab v3
+ Hospital Role-based UI
+
+
+
+
+ setRoleMode("dashboard")}>Dashboard
+ setRoleMode("patient")}>Patient View
+ setRoleMode("nurse")}>Nurse View
+ setRoleMode("doctor")}>Doctor View
+ setRoleMode("security")}>Security View
+
+
+
+ v2 Data Core
+ FastAPI · 샘플 의료 데이터 · 분석 API
+
+
+
+
+
+
+
BioDockLab v3 · Hospital Main Dashboard
+
병원 역할 기반 의료 데이터 플랫폼
+
+ 환자, 간호사, 의사, 보안관리자가 각자 필요한 의료 데이터를 확인하고
+ 환자의 알 권리와 의료진 판단을 연결하는 제품 화면입니다.
+
+
+
+
+ Backend API
+ {apiStatus}
+ v2 data science core
+
+
+
+
+
+
+
Current Role View
+
+ {roleMode === "dashboard" && "Hospital Dashboard"}
+ {roleMode === "patient" && "Patient Right-to-Know View"}
+ {roleMode === "nurse" && "Nurse Vital Monitoring View"}
+ {roleMode === "doctor" && "Doctor Decision Support View"}
+ {roleMode === "security" && "Security Audit View"}
+
+
+ {roleMode === "dashboard" && "전체 병원 운영 현황과 환자 상태를 한 화면에서 확인합니다."}
+ {roleMode === "patient" && "환자가 본인의 상태와 검사 결과를 이해할 수 있도록 쉬운 설명을 우선 표시합니다."}
+ {roleMode === "nurse" && "간호사가 바이탈사인, 주의 환자, 인수인계 흐름을 빠르게 확인합니다."}
+ {roleMode === "doctor" && "의사가 위험도, 판단 보조, 디지털 트윈 위험 위치를 중심으로 확인합니다."}
+ {roleMode === "security" && "보안관리자가 접근 기록과 민감정보 접근 흐름을 확인합니다."}
+
+
+
+
+
+
+
Active Patients
+
{vitals.length}
+
현재 관리 중인 환자
+
+
+
Watch Cases
+
{watchCount}
+
주의 관찰 필요
+
+
+
Doctor Review
+
{doctorReviews}
+
의사 확인 필요
+
+
+
Reports Ready
+
{reportsReady}
+
환자 설명 리포트
+
+
+
Denied Access
+
{deniedAuditCount}
+
권한 차단 이벤트
+
+
+
+
+
+
+
Patient List
+ 오늘 모니터링 대상
+
+
+ {vitals.map((patient) => (
+ setSelectedPatientId(patient.patient_id)}
+ >
+
+ {patient.patient_id}
+ {patient.name} · {patient.age}세
+
+ {patient.status}
+
+ ))}
+
+
+
+
+
+
{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}
+ ))}
+
+
+
+
+
+
+
+
Patient Right-to-Know
+ 환자 설명 리포트
+
+ {selectedReport?.title}
+ {selectedReport?.summary}
+
+ {selectedReport?.right_to_know?.map((item) => (
+ {item}
+ ))}
+
+
+
+
+
+
Digital Twin Risk Map
+ 초기 모형 기반 위험 위치 시각화
+
+
+
+
+
+
+
+
+ {selectedTwin.findings.map((finding) => (
+
+ ))}
+
+
+ {selectedTwin.summary}
+
+
+
+
+
+
+
+
Clinical Timeline
+ 바이탈 → 판단 → 설명 리포트 흐름
+
+
+ {selectedTimeline.map((item) => (
+
+ {item.time}
+
+
{item.type} · {item.role}
+
{item.title}
+
+
+ ))}
+
+
+
+
+
+
Security Audit Log
+ 선택 환자 기준 접근 기록
+
+
+ {selectedAuditEvents.length === 0 && (
+ 선택 환자의 감사 로그가 없습니다.
+ )}
+
+ {selectedAuditEvents.map((event) => (
+
+
+ {event.time}
+ {event.actor} · {event.actor_name}
+
+
+
{event.action}
+
{event.data_scope} · {event.reason}
+
+ {event.result}
+
+ ))}
+
+
+
+
+
Digital Twin Findings
+ 의료진 확인용 위험 부위 요약
+
+
+ {selectedTwin.findings.map((finding) => (
+
+
+
{finding.label}
+
{finding.region}
+
{finding.description}
+
+
+ {finding.severity}
+
+
+ ))}
+
+
+
+
+ );
+}
+
+createRoot(document.getElementById("root")).render( );
diff --git a/src/app/main.v3.backup.jsx b/src/app/main.v3.backup.jsx
new file mode 100644
index 0000000..40a5994
--- /dev/null
+++ b/src/app/main.v3.backup.jsx
@@ -0,0 +1,432 @@
+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 [allTimeline, setAllTimeline] = 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", 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 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;
+
+ return (
+
+
+
+
B
+
+ BioDockLab v3
+ Hospital Role-based UI
+
+
+
+
+ Dashboard
+ Patients
+ Vital Signs
+ Patient Report
+ Doctor Decision
+ Digital Twin
+ Audit Log
+
+
+
+ v2 Data Core
+ FastAPI · 샘플 의료 데이터 · 분석 API
+
+
+
+
+
+
+
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) => (
+ setSelectedPatientId(patient.patient_id)}
+ >
+
+ {patient.patient_id}
+ {patient.name} · {patient.age}세
+
+ {patient.status}
+
+ ))}
+
+
+
+
+
+
{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}
+ ))}
+
+
+
+
+
+
+
+
Patient Right-to-Know
+ 환자 설명 리포트
+
+ {selectedReport?.title}
+ {selectedReport?.summary}
+
+ {selectedReport?.right_to_know?.map((item) => (
+ {item}
+ ))}
+
+
+
+
+
+
Digital Twin Risk Map
+ 초기 모형 기반 위험 위치 시각화
+
+
+
+
+
+
+
+
+ {selectedTwin.findings.map((finding) => (
+
+ ))}
+
+
+ {selectedTwin.summary}
+
+
+
+
+
+
+
+
Clinical Timeline
+ 바이탈 → 판단 → 설명 리포트 흐름
+
+
+ {selectedTimeline.map((item) => (
+
+ {item.time}
+
+
{item.type} · {item.role}
+
{item.title}
+
+
+ ))}
+
+
+
+
+
Digital Twin Findings
+ 의료진 확인용 위험 부위 요약
+
+
+ {selectedTwin.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..ec35d0c
--- /dev/null
+++ b/src/app/style.css
@@ -0,0 +1,641 @@
+: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;
+ }
+}
+
+.role-mode-banner {
+ margin-bottom: 18px;
+ padding: 20px 22px;
+ border-radius: 24px;
+ background: linear-gradient(135deg, #eff6ff, #f0fdfa);
+ border: 1px solid #bfdbfe;
+}
+
+.role-mode-banner strong {
+ display: block;
+ margin-top: 8px;
+ font-size: 24px;
+ color: #0f172a;
+}
+
+.role-mode-banner p {
+ margin: 8px 0 0;
+ color: #475569;
+ line-height: 1.55;
+}
+
+/* Patient View: 환자 설명 리포트와 이해 가능한 상태 중심 */
+.role-patient .doctor-box,
+.role-patient .twin-finding-panel {
+ display: none;
+}
+
+.role-patient .report-panel {
+ border-color: #14b8a6;
+ background: #f0fdfa;
+}
+
+/* Nurse View: 바이탈과 타임라인 중심 */
+.role-nurse .report-panel,
+.role-nurse .twin-finding-panel {
+ display: none;
+}
+
+.role-nurse .patient-detail {
+ border-color: #2563eb;
+}
+
+.role-nurse .timeline-panel {
+ border-color: #14b8a6;
+}
+
+/* Doctor View: 판단보조와 디지털 트윈 중심 */
+.role-doctor .report-panel {
+ display: none;
+}
+
+.role-doctor .doctor-box,
+.role-doctor .twin-panel,
+.role-doctor .twin-finding-panel {
+ border-color: #f59e0b;
+}
+
+/* Security View: 감사 로그 / 타임라인 중심 */
+.role-security .patient-detail,
+.role-security .report-panel,
+.role-security .twin-panel,
+.role-security .twin-finding-panel {
+ display: none;
+}
+
+.role-security .timeline-panel {
+ grid-column: 1 / -1;
+ border-color: #8b5cf6;
+ background: #faf5ff;
+}
+
+.stat-card.danger strong {
+ color: #ef4444;
+}
+
+.empty-text {
+ color: #94a3b8;
+ line-height: 1.6;
+}
+
+.audit-panel {
+ grid-column: span 1;
+}
+
+.audit-row {
+ display: grid;
+ grid-template-columns: 110px 1fr auto;
+ gap: 14px;
+ align-items: start;
+ padding: 14px 0;
+ border-top: 1px solid #e2e8f0;
+}
+
+.audit-row:first-of-type {
+ border-top: 0;
+}
+
+.audit-row strong {
+ display: block;
+ color: #1d4ed8;
+}
+
+.audit-row span {
+ display: block;
+ margin-top: 5px;
+ color: #64748b;
+ font-size: 13px;
+}
+
+.audit-row h3 {
+ margin: 0 0 6px;
+ font-size: 15px;
+ color: #0f172a;
+}
+
+.audit-row p {
+ margin: 0;
+ color: #64748b;
+ line-height: 1.5;
+}
+
+.audit-row.denied {
+ padding-left: 12px;
+ border-left: 4px solid #ef4444;
+ background: #fef2f2;
+ border-radius: 14px;
+}
+
+.role-security .audit-panel {
+ grid-column: 1 / -1;
+ border-color: #8b5cf6;
+ background: #faf5ff;
+}
+
+@media (max-width: 1200px) {
+ .audit-row {
+ grid-template-columns: 1fr;
+ }
+}