From bdf564639b506a7bfa47dbc478a36cf65c3eff39 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Thu, 21 Nov 2024 09:23:05 -0500 Subject: [PATCH 1/6] Add FHIR server, EHR workflows --- .env.development | 2 +- adrenaline/api/patients/data.py | 12 + adrenaline/api/patients/ehr.py | 73 + adrenaline/api/routes/answer.py | 55 +- adrenaline/api/routes/patients.py | 37 + scripts/create_instruction_answers.py | 18 +- scripts/load_mimiciv_fhir.py | 197 +++ services/.env.development | 3 + services/docker-compose.dev-services.yml | 41 + services/hapi.application.yaml | 24 + ui/package-lock.json | 1621 +++++++++++++++++- ui/package.json | 2 + ui/src/app/components/ehr-workflows-card.tsx | 223 +++ ui/src/app/patient/[id]/page.tsx | 3 + 14 files changed, 2214 insertions(+), 97 deletions(-) create mode 100644 scripts/load_mimiciv_fhir.py create mode 100644 services/hapi.application.yaml create mode 100644 ui/src/app/components/ehr-workflows-card.tsx diff --git a/.env.development b/.env.development index e85b7ec..adfdab2 100644 --- a/.env.development +++ b/.env.development @@ -5,7 +5,7 @@ MEDCAT_MODELS_DIR=/Volumes/clinical-data/medcat NER_SERVICE_PORT=8003 EMBEDDING_SERVICE_PORT=8004 -EMBEDDING_SERVICE_HOST=embedding-service-dev +EMBEDDING_SERVICE_HOST=embedding-service-dev-cpu BATCH_SIZE=1 CHROMA_SERVICE_HOST=chromadb-dev diff --git a/adrenaline/api/patients/data.py b/adrenaline/api/patients/data.py index 703ea58..ee168ef 100644 --- a/adrenaline/api/patients/data.py +++ b/adrenaline/api/patients/data.py @@ -6,6 +6,18 @@ from pydantic import BaseModel, Field, field_validator +class MedicationsRequest(BaseModel): + """Request for formatting medications. + + Attributes + ---------- + medications: str + The medications to format. + """ + + medications: str + + class CohortSearchQuery(BaseModel): """Query for cohort search. diff --git a/adrenaline/api/patients/ehr.py b/adrenaline/api/patients/ehr.py index fe67c7e..b4e17a0 100644 --- a/adrenaline/api/patients/ehr.py +++ b/adrenaline/api/patients/ehr.py @@ -186,6 +186,74 @@ def fetch_patient_events_by_type( ) raise + def fetch_latest_medications(self, patient_id: int) -> str: + """Fetch medication events from the latest encounter and format them. + + Parameters + ---------- + patient_id : int + The patient ID. + + Returns + ------- + str + Comma-separated list of medications from the latest encounter. + """ + if self.lazy_df is None: + raise ValueError("LazyFrame not initialized") + + try: + # First get all events for the patient + filtered_df = ( + self.lazy_df.filter(pl.col("patient_id") == patient_id) + .select(list(self._required_columns)) + .collect(streaming=True) + ) + + if filtered_df.height == 0: + logger.info(f"No events found for patient {patient_id}") + return "" + + # Process all events first + processed_events = [ + self._process_event(row) for row in filtered_df.to_dicts() + ] + + # Find the latest encounter + latest_encounter = None + latest_timestamp = None + + for event in processed_events: + if event["event_type"] == "HOSPITAL_ADMISSION" and ( + latest_timestamp is None or event["timestamp"] > latest_timestamp + ): + latest_timestamp = event["timestamp"] + latest_encounter = event["encounter_id"] + + if latest_encounter is None: + logger.info(f"No hospital admissions found for patient {patient_id}") + return "" + + # Filter medications for the latest encounter + medications = { + event["details"] + for event in processed_events + if ( + event["event_type"] == "MEDICATION" + and event["encounter_id"] == latest_encounter + ) + } + + # Return sorted, comma-separated string + return ", ".join(sorted(medications)) + + except Exception as e: + logger.error( + f"Error fetching medications for patient {patient_id}: {str(e)}", + exc_info=True, + ) + raise + def fetch_patient_encounters(patient_id: int) -> List[dict]: """Fetch encounters with admission dates for a patient. @@ -277,3 +345,8 @@ def fetch_patient_events(patient_id: int) -> List[Event]: def fetch_patient_events_by_type(patient_id: int, event_type: str) -> List[Event]: """Fetch events filtered by event_type for a patient.""" return ehr_data_manager.fetch_patient_events_by_type(patient_id, event_type) + + +def fetch_latest_medications(patient_id: int) -> str: + """Fetch medication list from the latest encounter.""" + return ehr_data_manager.fetch_latest_medications(patient_id) diff --git a/adrenaline/api/routes/answer.py b/adrenaline/api/routes/answer.py index 66180fd..16fdbef 100644 --- a/adrenaline/api/routes/answer.py +++ b/adrenaline/api/routes/answer.py @@ -10,7 +10,7 @@ from api.pages.data import Query from api.patients.answer import generate_answer -from api.patients.data import CohortSearchQuery, CohortSearchResult +from api.patients.data import CohortSearchQuery, CohortSearchResult, MedicationsRequest from api.patients.db import get_database from api.patients.rag import ( ChromaManager, @@ -53,6 +53,59 @@ RAG_MANAGER = RAGManager(EMBEDDING_MANAGER, CHROMA_MANAGER, NER_MANAGER) +@router.post("/format_medications") +async def format_medications( + request: MedicationsRequest, + current_user: User = Depends(get_current_active_user), # noqa: B008 +) -> Dict[str, str]: + """Format medications into a markdown table. + + Parameters + ---------- + request : MedicationsRequest + Request containing medications string + current_user : User + The current authenticated user + + Returns + ------- + Dict[str, str] + Formatted markdown table of medications + """ + try: + if not request.medications.strip(): + return {"formatted_medications": "No medications found"} + + # Prepare the prompt for the LLM + prompt = f""" + Convert this comma-separated list of medications into a well-formatted markdown table with columns for Medication Name and Status. + Sort them alphabetically by medication name. Remove any duplicate entries. + + Medications: {request.medications} + + Format the table like this: + | Medication Name | Status | + |----------------|---------| + | Med 1 | Status 1 | + """ + + # Generate the formatted table + formatted_table = await generate_answer( + user_query=prompt, + mode="general", + context="", + ) + + return {"formatted_medications": formatted_table[0]} + + except Exception as e: + logger.error(f"Error formatting medications: {str(e)}") + raise HTTPException( + status_code=500, + detail="An error occurred while formatting medications", + ) from e + + @router.post("/generate_answer") async def generate_answer_endpoint( query: Query = Body(...), # noqa: B008 diff --git a/adrenaline/api/routes/patients.py b/adrenaline/api/routes/patients.py index 97bd64a..2b501b8 100644 --- a/adrenaline/api/routes/patients.py +++ b/adrenaline/api/routes/patients.py @@ -10,6 +10,7 @@ from api.patients.data import ClinicalNote, Event, PatientData, QAPair from api.patients.db import get_database from api.patients.ehr import ( + fetch_latest_medications, fetch_patient_encounters, fetch_patient_events, fetch_patient_events_by_type, @@ -37,6 +38,42 @@ init_lazy_df(MEDS_DATA_DIR) +@router.get("/patient_data/{patient_id}/medications", response_model=str) +async def get_latest_medications( + patient_id: int, + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 + current_user: User = Depends(get_current_active_user), # noqa: B008 +) -> str: + """Retrieve latest medications for a specific patient. + + Parameters + ---------- + patient_id : int + The ID of the patient. + db : AsyncIOMotorDatabase + The database connection. + current_user : User + The current authenticated user. + + Returns + ------- + str + The latest medications for the patient. + """ + try: + medications = fetch_latest_medications(patient_id) + logger.info(f"Retrieved medications for patient ID {patient_id} {medications}") + return medications + except Exception as e: + logger.error( + f"Error retrieving medications for patient ID {patient_id}: {str(e)}" + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An error occurred while retrieving medications", + ) from e + + @router.get( "/patient_data/{patient_id}/events/{event_type}", response_model=List[Event] ) diff --git a/scripts/create_instruction_answers.py b/scripts/create_instruction_answers.py index e1a3747..d66f445 100644 --- a/scripts/create_instruction_answers.py +++ b/scripts/create_instruction_answers.py @@ -1,22 +1,12 @@ """Script to create instruction answers for EHR data.""" import os -from api.patients.ehr import init_lazy_df, fetch_recent_encounter_events +from api.patients.ehr import init_lazy_df, fetch_latest_medications -MEDS_DATA_DIR = os.getenv( - "MEDS_DATA_DIR", "/mnt/data/odyssey/meds/hosp/merge_to_MEDS_cohort/train" -) +MEDS_DATA_DIR = os.getenv("MEDS_DATA_DIR", "/Volumes/clinical-data/train") init_lazy_df(MEDS_DATA_DIR) -events = fetch_recent_encounter_events(10000032) -events_str = "" -for event in events: - event_type = event.code.split("//")[0] - if event_type == "MEDICATION": - medication = event.code.split("//")[1] - events_str += f"{medication} \n" - - -print(events_str) +meds = fetch_latest_medications(10000032) +print(meds) diff --git a/scripts/load_mimiciv_fhir.py b/scripts/load_mimiciv_fhir.py new file mode 100644 index 0000000..3856614 --- /dev/null +++ b/scripts/load_mimiciv_fhir.py @@ -0,0 +1,197 @@ +"""Script to load MIMIC-IV data into a FHIR server.""" + +import gzip +import json +import requests +from pathlib import Path +import logging +from rich.logging import RichHandler +from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn + +logging.basicConfig( + level=logging.INFO, + format="%(message)s", + handlers=[RichHandler(rich_tracebacks=True)], +) +logger = logging.getLogger("MIMIC FHIR Loader") + +# Define MIMIC-IV FHIR resource loading order based on dependencies +RESOURCE_LOAD_ORDER = [ + # Base resources with no dependencies + "MimicOrganization", # Healthcare organization info + "MimicLocation", # Hospital locations + "MimicPatient", # Patient demographics + # Encounter resources + "MimicEncounter", # Hospital admissions + "MimicEncounterED", # Emergency department visits + "MimicEncounterICU", # ICU stays + # Medication-related resources + "MimicMedication", # Medication catalog + "MimicMedicationMix", # Medication mixtures + "MimicMedicationRequest", # Medication orders + "MimicMedicationDispense", # Medication dispensing + "MimicMedicationDispenseED", # ED medication dispensing + "MimicMedicationAdministration", # Medication administration + "MimicMedicationAdministrationICU", # ICU medication administration + "MimicMedicationStatementED", # ED medication statements + # Clinical resources + "MimicSpecimen", # Specimen collection + "MimicSpecimenLab", # Laboratory specimens + "MimicCondition", # Diagnoses and conditions + "MimicConditionED", # ED diagnoses + "MimicProcedure", # Hospital procedures + "MimicProcedureED", # ED procedures + "MimicProcedureICU", # ICU procedures + # Observation resources (largest files last) + "MimicObservationED", # ED observations + "MimicObservationVitalSignsED", # ED vital signs + "MimicObservationMicroOrg", # Microbiology organisms + "MimicObservationMicroTest", # Microbiology tests + "MimicObservationMicroSusc", # Microbiology susceptibilities + "MimicObservationDatetimeevents", # Datetime events + "MimicObservationOutputevents", # Output events + "MimicObservationLabevents", # Laboratory results + "MimicObservationChartevents", # Charted events +] + + +def send_to_fhir(bundle, fhir_base_url): + """Send bundle to FHIR server.""" + headers = {"Content-Type": "application/fhir+json"} + try: + response = requests.post(fhir_base_url, json=bundle, headers=headers) + if response.status_code not in [200, 201]: + logger.error(f"Error response: {response.status_code} - {response.text}") + response.raise_for_status() + return response + except requests.RequestException as e: + logger.error(f"Failed to send bundle to FHIR server: {e}") + return None + + +def create_transaction_bundle(resources): + """Create a FHIR transaction bundle from a list of resources.""" + entries = [] + for res in resources: + # Ensure the resource has an id + if "id" not in res: + logger.warning(f"Resource missing id: {res.get('resourceType', 'Unknown')}") + continue + + entries.append( + { + "resource": res, + "request": { + "method": "PUT", # Use PUT instead of POST to ensure id is preserved + "url": f"{res['resourceType']}/{res['id']}", + }, + } + ) + + return {"resourceType": "Bundle", "type": "transaction", "entry": entries} + + +def load_ndjson_to_fhir(file_path, fhir_base_url, batch_size=50): # Smaller batch size + """Load NDJSON file into FHIR server.""" + logger.info(f"Loading data from {file_path}...") + file_size = file_path.stat().st_size / (1024 * 1024) # Size in MB + logger.info(f"File size: {file_size:.1f} MB") + + with gzip.open(file_path, "rt") as f: + batch = [] + total_loaded = 0 + failed_batches = 0 + + with Progress( + SpinnerColumn(), + *Progress.get_default_columns(), + TimeElapsedColumn(), + transient=True, + ) as progress: + task = progress.add_task(f"Loading {file_path.name}", total=None) + + for line_number, line in enumerate(f, 1): + try: + resource = json.loads(line) + + # Ensure resource has an id + if "id" not in resource: + resource["id"] = f"{line_number}" + + batch.append(resource) + + if len(batch) >= batch_size: + bundle = create_transaction_bundle(batch) + response = send_to_fhir(bundle, fhir_base_url) + + if response and response.status_code in [200, 201]: + total_loaded += len(batch) + progress.update(task, advance=len(batch)) + else: + failed_batches += 1 + logger.error(f"Failed to load batch at line {line_number}") + batch = [] + + except json.JSONDecodeError: + logger.error(f"Invalid JSON at line {line_number}: {line[:100]}...") + except Exception as e: + logger.error(f"Error processing line {line_number}: {str(e)}") + + # Process remaining resources + if batch: + bundle = create_transaction_bundle(batch) + response = send_to_fhir(bundle, fhir_base_url) + + if response and response.status_code in [200, 201]: + total_loaded += len(batch) + progress.update(task, advance=len(batch)) + else: + failed_batches += 1 + logger.error("Failed to load final batch") + + logger.info( + f"✅ Completed loading {total_loaded} resources from {file_path.name}" + f" ({failed_batches} failed batches)" + ) + return total_loaded, failed_batches + + +def main(): + port = "8087" # Change this to match your FHIR server port + fhir_base_url = f"http://localhost:{port}/fhir" + data_dir = Path("/Volumes/clinical-data/physionet.org/files/mimic-iv-fhir/1.0/fhir") + + if not data_dir.exists(): + logger.error(f"Data directory not found: {data_dir}") + return + + # Test server connection + try: + response = requests.get(f"{fhir_base_url}/metadata") + response.raise_for_status() + logger.info("Successfully connected to FHIR server") + except requests.RequestException as e: + logger.error(f"Failed to connect to FHIR server: {e}") + return + + # Process files in the specified order + total_resources = 0 + total_failed = 0 + + for resource_type in RESOURCE_LOAD_ORDER: + resource_file = data_dir / f"{resource_type}.ndjson.gz" + if resource_file.exists(): + logger.info(f"\n=== Loading {resource_type} resources ===") + loaded, failed = load_ndjson_to_fhir(resource_file, fhir_base_url) + total_resources += loaded + total_failed += failed + else: + logger.warning(f"File not found: {resource_file}") + + logger.info("\n=== Loading Complete ===") + logger.info(f"Total resources loaded: {total_resources}") + logger.info(f"Total failed batches: {total_failed}") + + +if __name__ == "__main__": + main() diff --git a/services/.env.development b/services/.env.development index fc17fc4..f527602 100644 --- a/services/.env.development +++ b/services/.env.development @@ -12,3 +12,6 @@ CHROMA_SERVICE_PORT=8000 MONGO_USERNAME=root MONGO_PASSWORD=password + +FHIR_SERVICE_HOST=fhir-dev +FHIR_SERVICE_PORT=8080 diff --git a/services/docker-compose.dev-services.yml b/services/docker-compose.dev-services.yml index 2d31f46..7f261be 100644 --- a/services/docker-compose.dev-services.yml +++ b/services/docker-compose.dev-services.yml @@ -138,9 +138,50 @@ services: start_period: 40s restart: unless-stopped + fhir-dev: + image: "hapiproject/hapi:latest" + ports: + - "8087:8080" + configs: + - source: hapi + target: /app/config/application.yaml + depends_on: + fhir-db-dev: + condition: service_healthy + networks: + - services-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/fhir/metadata"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + fhir-db-dev: + image: postgres:17 + restart: always + environment: + POSTGRES_PASSWORD: admin + POSTGRES_USER: admin + POSTGRES_DB: hapi + volumes: + - hapi_fhir_data:/var/lib/postgresql/data + networks: + - services-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U admin -d hapi"] + interval: 10s + timeout: 5s + retries: 5 + +configs: + hapi: + file: ./hapi.application.yaml + volumes: mongodb_data: chroma_data: + hapi_fhir_data: networks: services-network: diff --git a/services/hapi.application.yaml b/services/hapi.application.yaml new file mode 100644 index 0000000..14e1f0d --- /dev/null +++ b/services/hapi.application.yaml @@ -0,0 +1,24 @@ +spring: + datasource: + url: 'jdbc:postgresql://fhir-db-dev:5432/hapi' + username: admin + password: admin + driverClassName: org.postgresql.Driver + jpa: + properties: + hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect + hibernate.search.enabled: false + +hapi: + fhir: + version: R4 + server_address: 'http://localhost:8080/fhir' + allow_external_references: true + allow_multiple_delete: true + bulk_export_enabled: true + tester: + home: + name: Local Tester + server_address: 'http://localhost:8080/fhir' + refuse_to_fetch_third_party_urls: false + fhir_version: R4 diff --git a/ui/package-lock.json b/ui/package-lock.json index f7a49d6..6063710 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -21,10 +21,12 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18", "react-icons": "^5.3.0", + "react-markdown": "^9.0.1", "react-plotly.js": "^2.6.0", "react-table": "^7.8.0", "react-textarea-autosize": "^8.5.3", "reactflow": "^11.11.4", + "remark-gfm": "^4.0.0", "swr": "^2.2.5", "zod": "^3.23.8", "zod-formik-adapter": "^1.3.0" @@ -2505,11 +2507,44 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -2538,6 +2573,21 @@ "@types/lodash": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.16.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", @@ -2582,6 +2632,12 @@ "@types/react": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -2712,8 +2768,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@zag-js/dom-query": { "version": "0.16.0", @@ -3103,6 +3158,16 @@ "npm": ">=6" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3261,6 +3326,16 @@ "element-size": "^1.1.1" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3284,6 +3359,46 @@ "node": ">=0.8.0" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chart.js": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", @@ -3444,6 +3559,16 @@ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3901,6 +4026,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -3990,6 +4128,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-kerning": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", @@ -4001,6 +4148,19 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4925,6 +5085,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4961,6 +5131,12 @@ "type": "^2.7.2" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/falafel": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", @@ -5874,6 +6050,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -5888,6 +6104,16 @@ "integrity": "sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ==", "peer": true }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5969,6 +6195,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -5991,6 +6223,30 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -6146,6 +6402,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6224,6 +6490,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-iexplorer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-iexplorer/-/is-iexplorer-1.0.0.tgz", @@ -6680,6 +6956,16 @@ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6748,6 +7034,16 @@ "node": ">=6.4.0" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-log2": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", @@ -6757,109 +7053,954 @@ "node": ">=0.10.0" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mouse-change": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", - "integrity": "sha512-vpN0s+zLL2ykyyUDh+fayu9Xkor5v/zRD9jhSqjRS1cJTGS0+oakVZzNm5n19JvvEj0you+MXlYTpNxUDQUjkQ==", - "peer": true, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", "dependencies": { - "mouse-event": "^1.0.0" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mouse-event": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/mouse-event/-/mouse-event-1.0.5.tgz", - "integrity": "sha512-ItUxtL2IkeSKSp9cyaX2JLUuKk2uMoxBg4bbOWVd29+CskYJR9BGsUqtXenNzKbnDshvupjUewDIYVrOB6NmGw==", - "peer": true - }, - "node_modules/mouse-event-offset": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz", - "integrity": "sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w==", - "peer": true - }, - "node_modules/mouse-wheel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mouse-wheel/-/mouse-wheel-1.2.0.tgz", - "integrity": "sha512-+OfYBiUOCTWcTECES49neZwL5AoGkXE+lFjIvzwNCnYRlso+EnfvovcBxGoyQ0yQt806eSPjS675K0EwWknXmw==", - "peer": true, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", "dependencies": { - "right-now": "^1.0.0", - "signum": "^1.0.0", - "to-px": "^1.0.1" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mumath": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/mumath/-/mumath-3.3.4.tgz", - "integrity": "sha512-VAFIOG6rsxoc7q/IaY3jdjmrsuX9f15KlRLYTHmixASBZkZEKC1IFqE2BC5CdhXmK6WLM1Re33z//AGmeRI6FA==", - "deprecated": "Redundant dependency in your project.", - "peer": true, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", "dependencies": { - "almost-equal": "^1.1.0" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/murmurhash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", - "peer": true + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mouse-change": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", + "integrity": "sha512-vpN0s+zLL2ykyyUDh+fayu9Xkor5v/zRD9jhSqjRS1cJTGS0+oakVZzNm5n19JvvEj0you+MXlYTpNxUDQUjkQ==", + "peer": true, + "dependencies": { + "mouse-event": "^1.0.0" + } + }, + "node_modules/mouse-event": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/mouse-event/-/mouse-event-1.0.5.tgz", + "integrity": "sha512-ItUxtL2IkeSKSp9cyaX2JLUuKk2uMoxBg4bbOWVd29+CskYJR9BGsUqtXenNzKbnDshvupjUewDIYVrOB6NmGw==", + "peer": true + }, + "node_modules/mouse-event-offset": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz", + "integrity": "sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w==", + "peer": true + }, + "node_modules/mouse-wheel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mouse-wheel/-/mouse-wheel-1.2.0.tgz", + "integrity": "sha512-+OfYBiUOCTWcTECES49neZwL5AoGkXE+lFjIvzwNCnYRlso+EnfvovcBxGoyQ0yQt806eSPjS675K0EwWknXmw==", + "peer": true, + "dependencies": { + "right-now": "^1.0.0", + "signum": "^1.0.0", + "to-px": "^1.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mumath": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/mumath/-/mumath-3.3.4.tgz", + "integrity": "sha512-VAFIOG6rsxoc7q/IaY3jdjmrsuX9f15KlRLYTHmixASBZkZEKC1IFqE2BC5CdhXmK6WLM1Re33z//AGmeRI6FA==", + "deprecated": "Redundant dependency in your project.", + "peer": true, + "dependencies": { + "almost-equal": "^1.1.0" + } + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "peer": true }, "node_modules/mz": { "version": "2.7.0", @@ -7243,6 +8384,32 @@ "integrity": "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==", "peer": true }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -7690,6 +8857,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -7823,6 +9000,32 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-plotly.js": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.6.0.tgz", @@ -8119,6 +9322,72 @@ "regl-scatter2d": "^3.2.3" } }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -8451,6 +9720,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", @@ -8703,6 +9982,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8755,6 +10048,15 @@ "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==", "peer": true }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -9064,6 +10366,26 @@ "topoquantize": "bin/topoquantize" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -9252,6 +10574,105 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", @@ -9364,6 +10785,34 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -9707,6 +11156,16 @@ "optional": true } } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/ui/package.json b/ui/package.json index 778b972..e781b34 100644 --- a/ui/package.json +++ b/ui/package.json @@ -22,10 +22,12 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18", "react-icons": "^5.3.0", + "react-markdown": "^9.0.1", "react-plotly.js": "^2.6.0", "react-table": "^7.8.0", "react-textarea-autosize": "^8.5.3", "reactflow": "^11.11.4", + "remark-gfm": "^4.0.0", "swr": "^2.2.5", "zod": "^3.23.8", "zod-formik-adapter": "^1.3.0" diff --git a/ui/src/app/components/ehr-workflows-card.tsx b/ui/src/app/components/ehr-workflows-card.tsx new file mode 100644 index 0000000..c037b88 --- /dev/null +++ b/ui/src/app/components/ehr-workflows-card.tsx @@ -0,0 +1,223 @@ +import React, { useState } from 'react' +import { + Card, + CardBody, + Heading, + VStack, + Button, + Text, + useColorModeValue, + useToast, + Box, + Spinner, + Table, + Thead, + Tbody, + Tr, + Th, + Td, +} from '@chakra-ui/react' +import { FaMedkit } from 'react-icons/fa' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +interface EHRWorkflowsCardProps { + patientId: string +} + +const EHRWorkflowsCard: React.FC = ({ patientId }) => { + const [isLoading, setIsLoading] = useState(false) + const [medicationsTable, setMedicationsTable] = useState(null) + const toast = useToast() + + // Color mode values + const cardBgColor = useColorModeValue('white', 'gray.800') + const borderColor = useColorModeValue('gray.200', 'gray.600') + const headerBgColor = useColorModeValue('gray.50', 'gray.700') + const hoverBgColor = useColorModeValue('blue.50', 'blue.900') + const textColor = useColorModeValue('gray.800', 'white') + + const handleRetrieveMedications = async () => { + setIsLoading(true) + try { + const token = localStorage.getItem('token') + if (!token) throw new Error('No token found') + + // Fetch medications + const medsResponse = await fetch(`/api/patient_data/${patientId}/medications`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'text/plain' + }, + }) + + if (!medsResponse.ok) { + const errorText = await medsResponse.text() + throw new Error(`Failed to fetch medications: ${errorText}`) + } + + const medications = await medsResponse.text() + + if (!medications.trim()) { + setMedicationsTable("No medications found for this patient.") + return + } + + // Format medications into table + const formatResponse = await fetch('/api/format_medications', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + medications: medications.replace(/^"|"$/g, '') + }), + }) + + if (!formatResponse.ok) { + const errorText = await formatResponse.text() + throw new Error(`Failed to format medications: ${errorText}`) + } + + const { formatted_medications } = await formatResponse.json() + setMedicationsTable(formatted_medications) + } catch (error) { + console.error('Error:', error) + toast({ + title: "Error", + description: error instanceof Error ? error.message : "An error occurred", + status: "error", + duration: 3000, + isClosable: true, + }) + setMedicationsTable(null) + } finally { + setIsLoading(false) + } + } + + // Custom components for ReactMarkdown + const MarkdownComponents = { + table: ({ children }: { children: React.ReactNode }) => ( + + {children} +
+ ), + thead: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + tbody: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + tr: ({ children }: { children: React.ReactNode }) => ( + + {children} + + ), + th: ({ children }: { children: React.ReactNode }) => ( + + {children} + + ), + td: ({ children }: { children: React.ReactNode }) => ( + + {children} + + ), + } + + return ( + + + + + EHR Workflows + + + + + {isLoading && ( + + + + Processing medications... + + + )} + + {medicationsTable && !isLoading && ( + + + Current Medications + + + {medicationsTable} + + + )} + + + + ) +} + +export default EHRWorkflowsCard diff --git a/ui/src/app/patient/[id]/page.tsx b/ui/src/app/patient/[id]/page.tsx index 9732e9f..731fc85 100644 --- a/ui/src/app/patient/[id]/page.tsx +++ b/ui/src/app/patient/[id]/page.tsx @@ -34,6 +34,7 @@ import PatientDetailsCard from '../../components/patient-details-card' import PatientEncountersTable from '../../components/patient-encounters-table' import SearchBox from '../../components/search-box' import AnswerCard from '../../components/answer-card' +import EHRWorkflowsCard from '../../components/ehr-workflows-card' const MotionBox = motion(Box) @@ -229,6 +230,8 @@ const PatientPage: React.FC = () => { isLoading={isLoadingEncounters} /> + + From 77fef4cec87c4ca9843d11c83401a3a52495f2b2 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Thu, 21 Nov 2024 10:23:02 -0500 Subject: [PATCH 2/6] Improvements and type annotations --- adrenaline/api/patients/answer.py | 36 ++++++---------------------- adrenaline/api/patients/ehr.py | 2 +- adrenaline/api/patients/llm.py | 33 +++++++++++++++++++++++++ adrenaline/api/patients/workflows.py | 3 +++ adrenaline/api/routes/answer.py | 4 ++-- adrenaline/api/routes/pages.py | 12 +++++----- scripts/cot_endpoint.py | 5 +++- services/embedding/api/routes.py | 2 +- 8 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 adrenaline/api/patients/llm.py create mode 100644 adrenaline/api/patients/workflows.py diff --git a/adrenaline/api/patients/answer.py b/adrenaline/api/patients/answer.py index b50b7b1..b958eb6 100644 --- a/adrenaline/api/patients/answer.py +++ b/adrenaline/api/patients/answer.py @@ -2,15 +2,14 @@ import json import logging -import os from typing import Tuple from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import PromptTemplate from langchain_core.runnables import RunnableSequence -from langchain_openai import ChatOpenAI from api.pages.data import Answer +from api.patients.llm import LLM from api.patients.prompts import ( general_answer_template, patient_answer_template, @@ -21,27 +20,6 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -# Set up OpenAI client with custom endpoint -LLM_SERVICE_URL = os.getenv("LLM_SERVICE_URL") -if not LLM_SERVICE_URL: - raise ValueError("LLM_SERVICE_URL is not set") -logger.info(f"LLM_SERVICE_URL is set to: {LLM_SERVICE_URL}") - -os.environ["OPENAI_API_KEY"] = "EMPTY" - -# Initialize LLM with increased timeout -try: - llm = ChatOpenAI( - base_url=LLM_SERVICE_URL, - model_name="Meta-Llama-3.1-70B-Instruct", - temperature=0.3, - max_tokens=4096, - request_timeout=60, - ) - logger.info("ChatOpenAI initialized successfully") -except Exception as e: - logger.error(f"Error initializing ChatOpenAI: {str(e)}") - raise answer_parser = PydanticOutputParser(pydantic_object=Answer) patient_answer_prompt = PromptTemplate( @@ -54,8 +32,8 @@ ) # Initialize the LLMChains -patient_answer_chain = RunnableSequence(patient_answer_prompt | llm) -general_answer_chain = RunnableSequence(general_answer_prompt | llm) +patient_answer_chain = RunnableSequence(patient_answer_prompt | LLM) +general_answer_chain = RunnableSequence(general_answer_prompt | LLM) def parse_llm_output_answer(output: str) -> Tuple[str, str]: @@ -139,7 +117,7 @@ async def generate_answer( raise -async def test_llm_connection(): +async def test_llm_connection() -> bool: """Test the connection to the LLM. Returns @@ -158,12 +136,12 @@ async def test_llm_connection(): return False -async def initialize_llm(): +async def initialize_llm() -> bool: """Initialize the LLM. Returns ------- bool - True if the connection is successful, False otherwise. + True if the LLM is initialized successfully, False otherwise. """ - await test_llm_connection() + return await test_llm_connection() diff --git a/adrenaline/api/patients/ehr.py b/adrenaline/api/patients/ehr.py index b4e17a0..43bf345 100644 --- a/adrenaline/api/patients/ehr.py +++ b/adrenaline/api/patients/ehr.py @@ -240,7 +240,7 @@ def fetch_latest_medications(self, patient_id: int) -> str: for event in processed_events if ( event["event_type"] == "MEDICATION" - and event["encounter_id"] == latest_encounter + and event["timestamp"] > latest_timestamp ) } diff --git a/adrenaline/api/patients/llm.py b/adrenaline/api/patients/llm.py new file mode 100644 index 0000000..6dfceb1 --- /dev/null +++ b/adrenaline/api/patients/llm.py @@ -0,0 +1,33 @@ +"""LLM module for patients API.""" + +import logging +import os + +from langchain_openai import ChatOpenAI + + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Set up OpenAI client with custom endpoint +LLM_SERVICE_URL = os.getenv("LLM_SERVICE_URL") +if not LLM_SERVICE_URL: + raise ValueError("LLM_SERVICE_URL is not set") +logger.info(f"LLM_SERVICE_URL is set to: {LLM_SERVICE_URL}") + +os.environ["OPENAI_API_KEY"] = "EMPTY" + +# Initialize LLM with increased timeout +try: + LLM = ChatOpenAI( + base_url=LLM_SERVICE_URL, + model_name="Meta-Llama-3.1-70B-Instruct", + temperature=0.3, + max_tokens=4096, + request_timeout=60, + ) + logger.info("ChatOpenAI initialized successfully") +except Exception as e: + logger.error(f"Error initializing ChatOpenAI: {str(e)}") + raise \ No newline at end of file diff --git a/adrenaline/api/patients/workflows.py b/adrenaline/api/patients/workflows.py new file mode 100644 index 0000000..0cebd81 --- /dev/null +++ b/adrenaline/api/patients/workflows.py @@ -0,0 +1,3 @@ +"""EHR workflow functions.""" + + diff --git a/adrenaline/api/routes/answer.py b/adrenaline/api/routes/answer.py index 16fdbef..7e456aa 100644 --- a/adrenaline/api/routes/answer.py +++ b/adrenaline/api/routes/answer.py @@ -3,7 +3,7 @@ import logging import os from datetime import datetime -from typing import Dict, List +from typing import Any, Dict, List from fastapi import APIRouter, Body, Depends, HTTPException from motor.motor_asyncio import AsyncIOMotorDatabase @@ -109,7 +109,7 @@ async def format_medications( @router.post("/generate_answer") async def generate_answer_endpoint( query: Query = Body(...), # noqa: B008 - db: AsyncIOMotorDatabase = Depends(get_database), # noqa: B008 + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 current_user: User = Depends(get_current_active_user), # noqa: B008 ) -> Dict[str, str]: """Generate an answer using RAG.""" diff --git a/adrenaline/api/routes/pages.py b/adrenaline/api/routes/pages.py index 9fbe5e2..842e56d 100644 --- a/adrenaline/api/routes/pages.py +++ b/adrenaline/api/routes/pages.py @@ -2,7 +2,7 @@ import logging from datetime import UTC, datetime -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from bson import ObjectId from fastapi import APIRouter, Body, Depends, HTTPException @@ -32,7 +32,7 @@ class CreatePageRequest(BaseModel): @router.post("/pages/create") async def create_page( request: CreatePageRequest, - db: AsyncIOMotorDatabase = Depends(get_database), # noqa: B008 + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 current_user: User = Depends(get_current_active_user), # noqa: B008 ) -> Dict[str, str]: """Create a new page for a user.""" @@ -63,9 +63,9 @@ async def append_to_page( page_id: str, question: str = Body(...), answer: str = Body(...), - db: AsyncIOMotorDatabase = Depends(get_database), # noqa: B008 + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 current_user: User = Depends(get_current_active_user), # noqa: B008 -) -> dict: +) -> Dict[str, str]: """Append a follow-up question and answer to an existing page.""" existing_page = await db.pages.find_one( {"_id": ObjectId(page_id), "user_id": str(current_user.id)} @@ -89,7 +89,7 @@ async def append_to_page( @router.get("/pages/history", response_model=List[Page]) async def get_user_page_history( - db: AsyncIOMotorDatabase = Depends(get_database), # noqa: B008 + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 current_user: User = Depends(get_current_active_user), # noqa: B008 ) -> List[Page]: """Retrieve all pages for the current user. @@ -114,7 +114,7 @@ async def get_user_page_history( @router.get("/pages/{page_id}", response_model=Page) async def get_page( page_id: str, - db: AsyncIOMotorDatabase = Depends(get_database), # noqa: B008 + db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008 current_user: User = Depends(get_current_active_user), # noqa: B008 ) -> Page: """Retrieve a specific page. diff --git a/scripts/cot_endpoint.py b/scripts/cot_endpoint.py index ff73407..f475042 100644 --- a/scripts/cot_endpoint.py +++ b/scripts/cot_endpoint.py @@ -1,4 +1,7 @@ +"""Test a chain of thought endpoint.""" + import os +from typing import Dict from langchain_openai import OpenAI from langchain.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser @@ -47,7 +50,7 @@ class Query(BaseModel): # Define endpoint @app.post("/cot") -async def chain_of_thought(query: Query): +async def chain_of_thought(query: Query) -> Dict[str, str]: try: result = chain.invoke({"query": query.text}) diff --git a/services/embedding/api/routes.py b/services/embedding/api/routes.py index d5711e5..fcf2789 100644 --- a/services/embedding/api/routes.py +++ b/services/embedding/api/routes.py @@ -96,7 +96,7 @@ async def create_embeddings(request: EmbeddingRequest) -> Dict[str, List[List[fl raise HTTPException(status_code=500, detail=str(e)) from e -def initialize_model(): +def initialize_model() -> None: """Initialize the model.""" global model model = load_model() From 1dad75a1036eaf90280847dda43105c4fa132186 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:23:39 +0000 Subject: [PATCH 3/6] [pre-commit.ci] Add auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- adrenaline/api/patients/llm.py | 2 +- adrenaline/api/patients/workflows.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/adrenaline/api/patients/llm.py b/adrenaline/api/patients/llm.py index 6dfceb1..d00874e 100644 --- a/adrenaline/api/patients/llm.py +++ b/adrenaline/api/patients/llm.py @@ -30,4 +30,4 @@ logger.info("ChatOpenAI initialized successfully") except Exception as e: logger.error(f"Error initializing ChatOpenAI: {str(e)}") - raise \ No newline at end of file + raise diff --git a/adrenaline/api/patients/workflows.py b/adrenaline/api/patients/workflows.py index 0cebd81..73603b8 100644 --- a/adrenaline/api/patients/workflows.py +++ b/adrenaline/api/patients/workflows.py @@ -1,3 +1 @@ """EHR workflow functions.""" - - From 2119c62467624e92820a7a6fed1f192695a87e60 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Thu, 21 Nov 2024 13:55:26 -0500 Subject: [PATCH 4/6] Improve date formatting --- adrenaline/api/patients/llm.py | 2 +- adrenaline/api/patients/workflows.py | 2 - ui/src/app/components/ehr-workflows-card.tsx | 12 +- .../components/patient-encounters-table.tsx | 112 +++++++++++++----- ui/src/app/patient/[id]/page.tsx | 43 ++++++- ui/src/app/types/patient.ts | 10 ++ 6 files changed, 141 insertions(+), 40 deletions(-) diff --git a/adrenaline/api/patients/llm.py b/adrenaline/api/patients/llm.py index 6dfceb1..d00874e 100644 --- a/adrenaline/api/patients/llm.py +++ b/adrenaline/api/patients/llm.py @@ -30,4 +30,4 @@ logger.info("ChatOpenAI initialized successfully") except Exception as e: logger.error(f"Error initializing ChatOpenAI: {str(e)}") - raise \ No newline at end of file + raise diff --git a/adrenaline/api/patients/workflows.py b/adrenaline/api/patients/workflows.py index 0cebd81..73603b8 100644 --- a/adrenaline/api/patients/workflows.py +++ b/adrenaline/api/patients/workflows.py @@ -1,3 +1 @@ """EHR workflow functions.""" - - diff --git a/ui/src/app/components/ehr-workflows-card.tsx b/ui/src/app/components/ehr-workflows-card.tsx index c037b88..45c8c69 100644 --- a/ui/src/app/components/ehr-workflows-card.tsx +++ b/ui/src/app/components/ehr-workflows-card.tsx @@ -43,12 +43,17 @@ const EHRWorkflowsCard: React.FC = ({ patientId }) => { const token = localStorage.getItem('token') if (!token) throw new Error('No token found') - // Fetch medications + // Create AbortController with longer timeout + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 300000) // 5 minute timeout + + // Fetch medications with timeout const medsResponse = await fetch(`/api/patient_data/${patientId}/medications`, { headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'text/plain' }, + signal: controller.signal }) if (!medsResponse.ok) { @@ -63,18 +68,21 @@ const EHRWorkflowsCard: React.FC = ({ patientId }) => { return } - // Format medications into table + // Format medications with timeout const formatResponse = await fetch('/api/format_medications', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, + signal: controller.signal, body: JSON.stringify({ medications: medications.replace(/^"|"$/g, '') }), }) + clearTimeout(timeoutId) + if (!formatResponse.ok) { const errorText = await formatResponse.text() throw new Error(`Failed to format medications: ${errorText}`) diff --git a/ui/src/app/components/patient-encounters-table.tsx b/ui/src/app/components/patient-encounters-table.tsx index cc55189..bcb6118 100644 --- a/ui/src/app/components/patient-encounters-table.tsx +++ b/ui/src/app/components/patient-encounters-table.tsx @@ -12,17 +12,20 @@ import { Td, Skeleton, Text, - useColorModeValue + useColorModeValue, + Box } from '@chakra-ui/react' +import { Encounter, PatientEncountersTableProps } from '../types/patient' -interface Encounter { - encounter_id: string; - admission_date: string; -} -interface PatientEncountersTableProps { - encounters: Encounter[]; - isLoading: boolean; +const formatDate = (dateString: string): string => { + const date = new Date(dateString) + return new Intl.DateTimeFormat('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }).format(date) } const PatientEncountersTable: React.FC = ({ @@ -31,53 +34,104 @@ const PatientEncountersTable: React.FC = ({ }) => { const cardBgColor = useColorModeValue('white', 'gray.800') const borderColor = useColorModeValue('gray.200', 'gray.600') - const headerBgColor = useColorModeValue('gray.50', 'gray.700') + const headerBgColor = useColorModeValue('blue.50', 'blue.900') + const rowHoverBg = useColorModeValue('gray.50', 'gray.700') + const textColor = useColorModeValue('gray.800', 'gray.100') + const headerTextColor = useColorModeValue('blue.700', 'blue.100') return ( - - - Patient Encounters - + + + + Patient Encounters + + + {isLoading ? ( - + + + ) : encounters.length > 0 ? ( - +
- - - + + + {encounters.map((encounter, index) => ( - - + + ))}
Encounter IDAdmission Date
+ Encounter ID + + Admission Date +
{encounter.encounter_id}{new Date(encounter.admission_date).toLocaleDateString()} + {encounter.encounter_id} + + {formatDate(encounter.admission_date)} +
) : ( - No encounters found + + + No encounters found + + )}
diff --git a/ui/src/app/patient/[id]/page.tsx b/ui/src/app/patient/[id]/page.tsx index 731fc85..14e07b7 100644 --- a/ui/src/app/patient/[id]/page.tsx +++ b/ui/src/app/patient/[id]/page.tsx @@ -200,7 +200,7 @@ const PatientPage: React.FC = () => { return ( - + @@ -225,12 +225,43 @@ const PatientPage: React.FC = () => { )} - + + {isLoading ? ( + + ) : patientData ? ( + + ) : ( + + + No patient data found + + + )} + - + + {isLoading ? ( + + ) : patientData ? ( + + ) : ( + + + No patient data found + + + )} + diff --git a/ui/src/app/types/patient.ts b/ui/src/app/types/patient.ts index 4b7571f..2abc681 100644 --- a/ui/src/app/types/patient.ts +++ b/ui/src/app/types/patient.ts @@ -1,3 +1,13 @@ +export interface Encounter { + encounter_id: string; + admission_date: string; +} + +export interface PatientEncountersTableProps { + encounters: Encounter[]; + isLoading: boolean; +} + export interface ClinicalNote { note_id: string; encounter_id: string; From 90e29e880c48d3a4c3eb103c20973820bd2d21c9 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Thu, 21 Nov 2024 13:58:23 -0500 Subject: [PATCH 5/6] Remove unused imports --- ui/src/app/patient/[id]/page.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ui/src/app/patient/[id]/page.tsx b/ui/src/app/patient/[id]/page.tsx index 14e07b7..affe4ac 100644 --- a/ui/src/app/patient/[id]/page.tsx +++ b/ui/src/app/patient/[id]/page.tsx @@ -17,13 +17,6 @@ import { GridItem, Progress, Heading, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableContainer } from '@chakra-ui/react' import { motion, AnimatePresence } from 'framer-motion' import Sidebar from '../../components/sidebar' From 16f6d466d65d06fc23daa1838289792374a822f3 Mon Sep 17 00:00:00 2001 From: Amrit Krishnan Date: Thu, 21 Nov 2024 14:01:26 -0500 Subject: [PATCH 6/6] Fix formatting of encounter ID --- ui/src/app/components/clinical-notes-table.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/src/app/components/clinical-notes-table.tsx b/ui/src/app/components/clinical-notes-table.tsx index b336021..2582157 100644 --- a/ui/src/app/components/clinical-notes-table.tsx +++ b/ui/src/app/components/clinical-notes-table.tsx @@ -22,6 +22,11 @@ const ClinicalNotesTable: React.FC = ({ notes, patientI router.push(`/note/${patientId}/${noteId}`); }, [router, patientId]); + const formatEncounterId = (encounterId: string | number): string => { + const numericId = parseInt(encounterId.toString(), 10); + return numericId === -1 ? 'N/A' : numericId.toString(); + }; + return ( @@ -49,7 +54,7 @@ const ClinicalNotesTable: React.FC = ({ notes, patientI
- {note.encounter_id === '-1' ? 'N/A' : note.encounter_id} + {formatEncounterId(note.encounter_id)}