From 3e8f1c93e690d9b9af35fafaeae152891430f241 Mon Sep 17 00:00:00 2001 From: Reed Salus Date: Wed, 4 Mar 2026 21:03:44 -0800 Subject: [PATCH 01/10] feat: add PreCheckCriterion type and demo data for policy pre-check Foundation for the policy intelligence demo extension. Adds: - PreCheckCriterion interface for pre-sign gap analysis - DEMO_PRECHECK_CRITERIA with 3 met / 2 indeterminate items - DEMO_PA_RESULT_SOURCES for post-sign evidence trail display Co-Authored-By: Claude Opus 4.6 --- .../src/lib/__tests__/demoData.test.ts | 40 ++++++++++ apps/dashboard/src/lib/demoData.ts | 75 +++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/apps/dashboard/src/lib/__tests__/demoData.test.ts b/apps/dashboard/src/lib/__tests__/demoData.test.ts index ace469c..092b965 100644 --- a/apps/dashboard/src/lib/__tests__/demoData.test.ts +++ b/apps/dashboard/src/lib/__tests__/demoData.test.ts @@ -8,6 +8,8 @@ import { DEMO_ORDERS, DEMO_ENCOUNTER_META, DEMO_PA_RESULT, + DEMO_PRECHECK_CRITERIA, + DEMO_PA_RESULT_SOURCES, LCD_L34220_POLICY, } from '../demoData'; @@ -62,6 +64,44 @@ describe('demoData', () => { expect(DEMO_PA_RESULT.status).toBe('ready'); }); + it('DEMO_PRECHECK_CRITERIA_HasFiveItems', () => { + expect(DEMO_PRECHECK_CRITERIA).toHaveLength(5); + }); + + it('DEMO_PRECHECK_CRITERIA_ThreeMetWithEvidenceAndSource', () => { + const met = DEMO_PRECHECK_CRITERIA.filter((c) => c.status === 'met'); + expect(met).toHaveLength(3); + met.forEach((c) => { + expect(c.evidence).toBeTruthy(); + expect(c.source).toBeTruthy(); + }); + }); + + it('DEMO_PRECHECK_CRITERIA_TwoIndeterminateWithGap', () => { + const indeterminate = DEMO_PRECHECK_CRITERIA.filter((c) => c.status === 'indeterminate'); + expect(indeterminate).toHaveLength(2); + indeterminate.forEach((c) => { + expect(c.gap).toBeTruthy(); + }); + }); + + it('DEMO_PRECHECK_CRITERIA_LabelsMatchLCDPolicy', () => { + const policyLabels = LCD_L34220_POLICY.criteria.map((c) => c.label); + DEMO_PRECHECK_CRITERIA.forEach((c) => { + // Each pre-check label should correspond to a policy criterion + expect(policyLabels.some((pl) => pl.includes(c.label.substring(0, 10)))).toBe(true); + }); + }); + + it('DEMO_PA_RESULT_SOURCES_HasEntryForEachCriterion', () => { + DEMO_PA_RESULT.criteria.forEach((c) => { + const source = DEMO_PA_RESULT_SOURCES[c.label]; + expect(source).toBeDefined(); + expect(source.evidence).toBeTruthy(); + expect(source.source).toBeTruthy(); + }); + }); + it('LCD_L34220_POLICY_HasFiveCriteriaWithRequirements', () => { expect(LCD_L34220_POLICY.policyId).toBe('LCD L34220'); expect(LCD_L34220_POLICY.procedureCode).toBe('72148'); diff --git a/apps/dashboard/src/lib/demoData.ts b/apps/dashboard/src/lib/demoData.ts index 13c632f..66ad940 100644 --- a/apps/dashboard/src/lib/demoData.ts +++ b/apps/dashboard/src/lib/demoData.ts @@ -62,6 +62,81 @@ export const DEMO_ORDERS = [ }, ]; +/** + * Pre-check criterion — used by the PAReadinessWidget before encounter signing. + */ +export interface PreCheckCriterion { + label: string; + status: 'met' | 'not-met' | 'indeterminate'; + evidence?: string; + gap?: string; + source?: string; +} + +/** + * Pre-check criteria for LCD L34220 evaluated against the patient chart + * BEFORE the encounter is signed. 3/5 met from existing chart data, + * 2/5 indeterminate until encounter note is complete. + */ +export const DEMO_PRECHECK_CRITERIA: PreCheckCriterion[] = [ + { + label: 'Valid ICD-10 for lumbar pathology', + status: 'met', + evidence: 'M54.5 (low back pain) on active problem list', + source: 'Problem List', + }, + { + label: 'Red flag symptoms or progressive neuro deficit', + status: 'indeterminate', + gap: 'Requires encounter documentation — sign encounter to evaluate', + }, + { + label: '4+ weeks conservative management', + status: 'met', + evidence: 'PT referral 01/10/2026, Naproxen 500mg prescribed 01/15/2026', + source: 'Orders / Medications', + }, + { + label: 'Clinical rationale documented', + status: 'indeterminate', + gap: 'Requires encounter documentation — sign encounter to evaluate', + }, + { + label: 'No recent duplicative imaging', + status: 'met', + evidence: 'No prior CT or MRI of lumbar spine in record', + source: 'Imaging History', + }, +]; + +/** + * Source/evidence mapping for the post-sign PA result criteria. + * Keyed by criterion label, provides extracted evidence and chart source + * for the evidence trail display in PAResultsPanel. + */ +export const DEMO_PA_RESULT_SOURCES: Record = { + 'Valid ICD-10 for lumbar pathology': { + evidence: 'M54.5, M54.51 — low back pain, lumbar radiculopathy left', + source: 'Assessment', + }, + 'Cauda equina, tumor, infection, major neuro deficit': { + evidence: 'Progressive numbness in left foot over past 3 weeks', + source: 'HPI', + }, + '4+ weeks conservative management documented': { + evidence: '8 weeks PT (2x/week), naproxen 500mg BID x 6 weeks', + source: 'HPI', + }, + 'Supporting clinical rationale documented': { + evidence: 'Persistent radiculopathy with progressive neuro symptoms despite conservative therapy', + source: 'Assessment / Plan', + }, + 'No recent duplicative CT/MRI': { + evidence: 'No prior lumbar CT or MRI in patient record', + source: 'Imaging History', + }, +}; + /** * LCD L34220 policy requirements for MRI Lumbar Spine (CPT 72148). * Shown in the pre-sign policy criteria modal so providers can review From ae711823547b326eabd0ec5e984462189767dbac Mon Sep 17 00:00:00 2001 From: Reed Salus Date: Wed, 4 Mar 2026 21:10:30 -0800 Subject: [PATCH 02/10] feat: add PAReadinessWidget, EvidenceTag, and evidence trail - PAReadinessWidget: amber-accented pre-check gap analysis component with staggered cascade animation and criteria status display - EvidenceTag: monospace provenance marker for sourced evidence - PAResultsPanel: enhanced with evidence trail (text + source tags) - useEhrDemoFlow: extended with flagged state and preCheckCriteria - 53/53 EHR component tests passing Co-Authored-By: Claude Opus 4.6 --- .../src/components/ehr/EvidenceTag.tsx | 12 ++ .../src/components/ehr/PAReadinessWidget.tsx | 164 +++++++++++++++ .../src/components/ehr/PAResultsPanel.tsx | 43 ++-- .../ehr/__tests__/EvidenceTag.test.tsx | 22 ++ .../ehr/__tests__/PAReadinessWidget.test.tsx | 197 ++++++++++++++++++ .../ehr/__tests__/PAResultsPanel.test.tsx | 20 ++ .../ehr/__tests__/useEhrDemoFlow.test.ts | 128 ++++++++++++ apps/dashboard/src/components/ehr/index.ts | 1 + .../src/components/ehr/useEhrDemoFlow.ts | 18 +- 9 files changed, 587 insertions(+), 18 deletions(-) create mode 100644 apps/dashboard/src/components/ehr/EvidenceTag.tsx create mode 100644 apps/dashboard/src/components/ehr/PAReadinessWidget.tsx create mode 100644 apps/dashboard/src/components/ehr/__tests__/EvidenceTag.test.tsx create mode 100644 apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx diff --git a/apps/dashboard/src/components/ehr/EvidenceTag.tsx b/apps/dashboard/src/components/ehr/EvidenceTag.tsx new file mode 100644 index 0000000..9587a6d --- /dev/null +++ b/apps/dashboard/src/components/ehr/EvidenceTag.tsx @@ -0,0 +1,12 @@ +interface EvidenceTagProps { + source: string; +} + +export function EvidenceTag({ source }: EvidenceTagProps) { + return ( + + Sourced from: + {source} + + ); +} diff --git a/apps/dashboard/src/components/ehr/PAReadinessWidget.tsx b/apps/dashboard/src/components/ehr/PAReadinessWidget.tsx new file mode 100644 index 0000000..150c841 --- /dev/null +++ b/apps/dashboard/src/components/ehr/PAReadinessWidget.tsx @@ -0,0 +1,164 @@ +import { + Check, + X, + HelpCircle, + ChevronRight, + ShieldAlert, + Loader2, +} from 'lucide-react'; +import type { PreCheckCriterion } from '@/lib/demoData'; + +export interface PAReadinessWidgetProps { + state: 'checking' | 'ready'; + criteria: PreCheckCriterion[]; + order: { code: string; name: string }; + payer: string; + policyId: string; + onCriterionClick?: (criterion: PreCheckCriterion) => void; +} + +function StatusIcon({ status }: { status: PreCheckCriterion['status'] }) { + if (status === 'met') { + return ; + } + if (status === 'indeterminate') { + return ; + } + return ; +} + +function ProgressDots({ met, total }: { met: number; total: number }) { + return ( + + {Array.from({ length: total }, (_, i) => ( + + ))} + + ); +} + +export function PAReadinessWidget({ + state, + criteria, + order, + payer, + policyId, + onCriterionClick, +}: PAReadinessWidgetProps) { + const metCount = criteria.filter((c) => c.status === 'met').length; + const total = criteria.length; + + return ( +
+ {/* Header */} +
+ + + AuthScript — Policy Pre-Check + + + {policyId} + +
+ + {/* Body */} +
+ {state === 'checking' && ( +
+ + + Analyzing patient chart against payer policy... + +
+ )} + + {state === 'ready' && ( +
+ {/* Summary row */} +
+ + {order.name} + + | + {payer} + | + + + + {metCount}/{total} criteria documented + + +
+ + {/* Criteria list */} +
    + {criteria.map((criterion, index) => { + const isClickable = !!onCriterionClick; + const Row = isClickable ? 'button' : 'div'; + + return ( +
  • + onCriterionClick(criterion) + : undefined + } + > + + + + + + {criterion.label} + + {criterion.status === 'met' && criterion.evidence && ( + + + {criterion.evidence} + + {criterion.source && ( + + {criterion.source} + + )} + + )} + {criterion.status === 'indeterminate' && criterion.gap && ( + + {criterion.gap} + + )} + {criterion.status === 'not-met' && criterion.gap && ( + + {criterion.gap} + + )} + + {isClickable && ( + + )} + +
  • + ); + })} +
+
+ )} +
+
+ ); +} diff --git a/apps/dashboard/src/components/ehr/PAResultsPanel.tsx b/apps/dashboard/src/components/ehr/PAResultsPanel.tsx index 8f44f5a..baa6f99 100644 --- a/apps/dashboard/src/components/ehr/PAResultsPanel.tsx +++ b/apps/dashboard/src/components/ehr/PAResultsPanel.tsx @@ -14,6 +14,8 @@ import { } from 'lucide-react'; import type { EhrDemoState } from './useEhrDemoFlow'; import type { PARequest, Criterion } from '@/api/graphqlService'; +import { EvidenceTag } from './EvidenceTag'; +import { DEMO_PA_RESULT_SOURCES } from '@/lib/demoData'; export interface PAResultsPanelProps { state: EhrDemoState; @@ -146,21 +148,32 @@ function ReviewingView({ {/* Criteria list */}
    - {paRequest.criteria.map((criterion) => ( -
  • - -
  • - ))} + {paRequest.criteria.map((criterion) => { + const sourceData = DEMO_PA_RESULT_SOURCES[criterion.label]; + return ( +
  • + +
  • + ); + })}
{/* Clinical summary */} diff --git a/apps/dashboard/src/components/ehr/__tests__/EvidenceTag.test.tsx b/apps/dashboard/src/components/ehr/__tests__/EvidenceTag.test.tsx new file mode 100644 index 0000000..d6ddd72 --- /dev/null +++ b/apps/dashboard/src/components/ehr/__tests__/EvidenceTag.test.tsx @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { EvidenceTag } from '../EvidenceTag'; + +describe('EvidenceTag', () => { + it('EvidenceTag_Renders_SourceText', () => { + render(); + expect(screen.getByText('HPI')).toBeInTheDocument(); + }); + + it('EvidenceTag_HasMonospaceStyle', () => { + const { container } = render(); + const tag = container.firstElementChild; + expect(tag?.className).toContain('font-mono'); + }); + + it('EvidenceTag_IsAccessible', () => { + render(); + // Screen reader text + expect(screen.getByText('Sourced from:')).toBeInTheDocument(); + }); +}); diff --git a/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx b/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx new file mode 100644 index 0000000..3c4ca0e --- /dev/null +++ b/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx @@ -0,0 +1,197 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { PAReadinessWidget } from '../PAReadinessWidget'; +import type { PreCheckCriterion } from '@/lib/demoData'; + +function buildCriteria(): PreCheckCriterion[] { + return [ + { + label: 'Valid ICD-10 for lumbar pathology', + status: 'met', + evidence: 'M54.5 on active problem list since 2025-09-12', + source: 'Problem List', + }, + { + label: 'Red flag symptoms or progressive neurological deficit', + status: 'indeterminate', + gap: 'No neurological findings in chart — document in encounter note', + }, + { + label: '4+ weeks conservative management', + status: 'met', + evidence: 'PT 2x/week x 8 weeks documented by referring provider', + source: 'Referral', + }, + { + label: 'Clinical rationale documented', + status: 'indeterminate', + gap: 'Rationale not yet documented — include in assessment/plan', + }, + { + label: 'No recent duplicative imaging', + status: 'met', + evidence: 'No lumbar CT or MRI in past 12 months', + source: 'Imaging Hx', + }, + ]; +} + +const defaultOrder = { code: '72148', name: 'MRI Lumbar Spine w/o Contrast' }; +const defaultPayer = 'Blue Cross Blue Shield'; +const defaultPolicyId = 'LCD L34220'; + +describe('PAReadinessWidget', () => { + it('PAReadinessWidget_Checking_ShowsAnalyzingState', () => { + render( + , + ); + + expect( + screen.getByText(/analyzing patient chart against payer policy/i), + ).toBeInTheDocument(); + + // Loading indicator (Loader2 renders as an svg) + const spinner = document.querySelector('.animate-spin'); + expect(spinner).toBeInTheDocument(); + }); + + it('PAReadinessWidget_Ready_ShowsSummaryBar', () => { + const criteria = buildCriteria(); + + render( + , + ); + + // Order name + expect(screen.getByText(/MRI Lumbar Spine/)).toBeInTheDocument(); + + // Payer + expect(screen.getByText(/Blue Cross Blue Shield/)).toBeInTheDocument(); + + // Policy ID in monospace/uppercase + const policyEl = screen.getByText('LCD L34220'); + expect(policyEl).toBeInTheDocument(); + expect(policyEl.className).toMatch(/font-mono/); + expect(policyEl.className).toMatch(/uppercase/); + + // Criteria count: 3 met out of 5 + expect(screen.getByText(/3\/5 criteria documented/)).toBeInTheDocument(); + }); + + it('PAReadinessWidget_Ready_ShowsAllCriteria', () => { + const criteria = buildCriteria(); + + render( + , + ); + + expect(screen.getByText('Valid ICD-10 for lumbar pathology')).toBeInTheDocument(); + expect( + screen.getByText('Red flag symptoms or progressive neurological deficit'), + ).toBeInTheDocument(); + expect(screen.getByText('4+ weeks conservative management')).toBeInTheDocument(); + expect(screen.getByText('Clinical rationale documented')).toBeInTheDocument(); + expect(screen.getByText('No recent duplicative imaging')).toBeInTheDocument(); + }); + + it('PAReadinessWidget_Met_ShowsEvidenceAndSource', () => { + const criteria = buildCriteria(); + + render( + , + ); + + // Evidence text in italic + const evidenceEl = screen.getByText( + 'M54.5 on active problem list since 2025-09-12', + ); + expect(evidenceEl).toBeInTheDocument(); + expect(evidenceEl.className).toMatch(/italic/); + + // Source tag + const sourceEl = screen.getByText('Problem List'); + expect(sourceEl).toBeInTheDocument(); + expect(sourceEl.className).toMatch(/font-mono/); + }); + + it('PAReadinessWidget_Indeterminate_ShowsGap', () => { + const criteria = buildCriteria(); + + render( + , + ); + + const gapEl = screen.getByText( + /No neurological findings in chart/, + ); + expect(gapEl).toBeInTheDocument(); + expect(gapEl.className).toMatch(/text-amber-600/); + }); + + it('PAReadinessWidget_CriterionClick_CallsHandler', () => { + const criteria = buildCriteria(); + const onCriterionClick = vi.fn(); + + render( + , + ); + + fireEvent.click(screen.getByText('Valid ICD-10 for lumbar pathology')); + + expect(onCriterionClick).toHaveBeenCalledWith(criteria[0]); + }); + + it('PAReadinessWidget_Ready_HasAmberAccent', () => { + const criteria = buildCriteria(); + + const { container } = render( + , + ); + + const widget = container.firstElementChild; + expect(widget?.className).toMatch(/border-amber-500/); + }); +}); diff --git a/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx b/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx index d08aadd..3acaeb2 100644 --- a/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx +++ b/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { PAResultsPanel } from '../PAResultsPanel'; import type { PARequest } from '@/api/graphqlService'; +import { DEMO_PA_RESULT, DEMO_PA_RESULT_SOURCES } from '@/lib/demoData'; function buildMockPARequest(overrides: Partial = {}): PARequest { return { @@ -139,6 +140,25 @@ describe('PAResultsPanel', () => { ).not.toBeInTheDocument(); }); + it('PAResultsPanel_Reviewing_ShowsEvidenceText', () => { + render( + , + ); + // Evidence text should appear for criteria that have source data + // The evidence text comes from DEMO_PA_RESULT_SOURCES lookup + expect(screen.getByText(/M54\.5.*low back pain/)).toBeInTheDocument(); + }); + + it('PAResultsPanel_Reviewing_ShowsEvidenceTrailForDemoData', () => { + render( + , + ); + // Evidence text from DEMO_PA_RESULT_SOURCES should be visible + expect(screen.getByText(/8 weeks PT/)).toBeInTheDocument(); + // Source tags should be visible (HPI appears for multiple criteria) + expect(screen.getAllByText('HPI').length).toBeGreaterThanOrEqual(1); + }); + it('PAResultsPanel_Error_ShowsErrorMessage', () => { // Arrange render( diff --git a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts index fd8341a..77dc66a 100644 --- a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts +++ b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts @@ -127,6 +127,134 @@ describe('useEhrDemoFlow', () => { expect(result.current.error).toBeNull(); }); + it('useEhrDemoFlow_Flag_TransitionsToFlagged', async () => { + const useEhrDemoFlow = await importHook(); + const { result } = renderHook(() => useEhrDemoFlow()); + + expect(result.current.state).toBe('idle'); + + let flagPromise: Promise; + act(() => { + flagPromise = result.current.flag(); + }); + + // Still idle during delay + expect(result.current.state).toBe('idle'); + + // After 1500ms → flagged + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + await act(async () => { + await flagPromise!; + }); + + expect(result.current.state).toBe('flagged'); + expect(result.current.preCheckCriteria).not.toBeNull(); + }); + + it('useEhrDemoFlow_Flagged_HasPreCheckCriteria', async () => { + const useEhrDemoFlow = await importHook(); + const { result } = renderHook(() => useEhrDemoFlow()); + + let flagPromise: Promise; + act(() => { + flagPromise = result.current.flag(); + }); + + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + await act(async () => { + await flagPromise!; + }); + + expect(result.current.state).toBe('flagged'); + const criteria = result.current.preCheckCriteria!; + expect(criteria).toHaveLength(5); + expect(criteria.filter((c) => c.status === 'met')).toHaveLength(3); + expect(criteria.filter((c) => c.status === 'indeterminate')).toHaveLength(2); + }); + + it('useEhrDemoFlow_Flagged_Sign_TransitionsToSigning', async () => { + const useEhrDemoFlow = await importHook(); + const { result } = renderHook(() => useEhrDemoFlow()); + + // Get to flagged state + let flagPromise: Promise; + act(() => { + flagPromise = result.current.flag(); + }); + + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + await act(async () => { + await flagPromise!; + }); + + expect(result.current.state).toBe('flagged'); + + // Now sign from flagged state + let signPromise: Promise; + act(() => { + signPromise = result.current.sign(); + }); + + expect(result.current.state).toBe('signing'); + + // 800ms → processing + await act(async () => { + vi.advanceTimersByTime(800); + }); + expect(result.current.state).toBe('processing'); + + // 5000ms → reviewing + await act(async () => { + vi.advanceTimersByTime(5000); + }); + + await act(async () => { + await signPromise!; + }); + + expect(result.current.state).toBe('reviewing'); + expect(result.current.paRequest).not.toBeNull(); + }); + + it('useEhrDemoFlow_Reset_ClearsPreCheckCriteria', async () => { + const useEhrDemoFlow = await importHook(); + const { result } = renderHook(() => useEhrDemoFlow()); + + // Get to flagged state + let flagPromise: Promise; + act(() => { + flagPromise = result.current.flag(); + }); + + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + await act(async () => { + await flagPromise!; + }); + + expect(result.current.state).toBe('flagged'); + expect(result.current.preCheckCriteria).not.toBeNull(); + + // Reset + act(() => { + result.current.reset(); + }); + + expect(result.current.state).toBe('idle'); + expect(result.current.preCheckCriteria).toBeNull(); + }); + it('useEhrDemoFlow_DemoResult_HasAllFiveCriteria', async () => { const useEhrDemoFlow = await importHook(); const { result } = renderHook(() => useEhrDemoFlow()); diff --git a/apps/dashboard/src/components/ehr/index.ts b/apps/dashboard/src/components/ehr/index.ts index 0dcf16a..abab3dc 100644 --- a/apps/dashboard/src/components/ehr/index.ts +++ b/apps/dashboard/src/components/ehr/index.ts @@ -2,4 +2,5 @@ export { EhrHeader } from './EhrHeader'; export { EncounterNote } from './EncounterNote'; export { EncounterSidebar } from './EncounterSidebar'; export { SignEncounterButton } from './SignEncounterButton'; +export { PAReadinessWidget } from './PAReadinessWidget'; export { PAResultsPanel } from './PAResultsPanel'; diff --git a/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts b/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts index 1b1e328..0a0718a 100644 --- a/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts +++ b/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts @@ -1,13 +1,16 @@ import { useState, useCallback } from 'react'; import type { PARequest } from '@/api/graphqlService'; -import { DEMO_PA_RESULT } from '@/lib/demoData'; +import { DEMO_PA_RESULT, DEMO_PRECHECK_CRITERIA } from '@/lib/demoData'; +import type { PreCheckCriterion } from '@/lib/demoData'; -export type EhrDemoState = 'idle' | 'signing' | 'processing' | 'reviewing' | 'submitting' | 'complete' | 'error'; +export type EhrDemoState = 'idle' | 'flagged' | 'signing' | 'processing' | 'reviewing' | 'submitting' | 'complete' | 'error'; export interface EhrDemoFlow { state: EhrDemoState; paRequest: PARequest | null; + preCheckCriteria: PreCheckCriterion[] | null; error: string | null; + flag: () => Promise; sign: () => Promise; submit: () => Promise; reset: () => void; @@ -26,8 +29,16 @@ function delay(ms: number): Promise { export function useEhrDemoFlow(): EhrDemoFlow { const [state, setState] = useState('idle'); const [paRequest, setPaRequest] = useState(null); + const [preCheckCriteria, setPreCheckCriteria] = useState(null); const [error, setError] = useState(null); + const flag = useCallback(async () => { + if (state !== 'idle') return; + await delay(1500); + setState('flagged'); + setPreCheckCriteria(DEMO_PRECHECK_CRITERIA); + }, [state]); + const sign = useCallback(async () => { try { setState('signing'); @@ -78,8 +89,9 @@ export function useEhrDemoFlow(): EhrDemoFlow { const reset = useCallback(() => { setState('idle'); setPaRequest(null); + setPreCheckCriteria(null); setError(null); }, []); - return { state, paRequest, error, sign, submit, reset }; + return { state, paRequest, preCheckCriteria, error, flag, sign, submit, reset }; } From aac5737081106d9f92f9222e740d70dcb4292145 Mon Sep 17 00:00:00 2001 From: Reed Salus Date: Wed, 4 Mar 2026 22:57:41 -0800 Subject: [PATCH 03/10] feat: integrate policy pre-check into EHR demo flow - EncounterSidebar: amber "Policy Check" indicator in flagged state - ehr-demo: auto-triggers flag() on mount, renders PAReadinessWidget in flagged state, transitions to PAResultsPanel on sign - Removed View Policy Criteria button (superseded by live widget) - Amber-to-blue visual transition when signing encounter - 298/298 dashboard tests passing Co-Authored-By: Claude Opus 4.6 --- .../src/components/ehr/EncounterSidebar.tsx | 20 +++- .../ehr/__tests__/EncounterSidebar.test.tsx | 13 ++ .../src/routes/__tests__/ehr-demo.test.tsx | 112 ++++++++++++++---- apps/dashboard/src/routes/ehr-demo.tsx | 58 +++++---- 4 files changed, 156 insertions(+), 47 deletions(-) diff --git a/apps/dashboard/src/components/ehr/EncounterSidebar.tsx b/apps/dashboard/src/components/ehr/EncounterSidebar.tsx index e60e2d3..294d023 100644 --- a/apps/dashboard/src/components/ehr/EncounterSidebar.tsx +++ b/apps/dashboard/src/components/ehr/EncounterSidebar.tsx @@ -10,6 +10,7 @@ interface EncounterSidebarProps { activeStage?: StageName; signed?: boolean; flowState?: EhrDemoState; + preCheckCount?: { met: number; total: number }; } type StageState = 'completed' | 'active' | 'pending'; @@ -116,10 +117,12 @@ export function EncounterSidebar({ activeStage = 'A&P', signed = false, flowState = 'idle', + preCheckCount, }: EncounterSidebarProps) { // When signed, all encounter stages are completed (activeIndex past last stage) const activeIndex = signed ? ENCOUNTER_STAGES.length : ENCOUNTER_STAGES.indexOf(activeStage); - const showPA = flowState !== 'idle' && flowState !== 'error'; + const isFlagged = flowState === 'flagged'; + const showPAStages = flowState !== 'idle' && flowState !== 'error' && flowState !== 'flagged'; const paActiveIndex = getPAActiveIndex(flowState); return ( @@ -134,7 +137,20 @@ export function EncounterSidebar({ /> - {showPA && ( + {isFlagged && preCheckCount && ( + <> +
+

+ Policy Check +

+
+ + {preCheckCount.met}/{preCheckCount.total} documented +
+ + )} + + {showPAStages && ( <>

diff --git a/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx b/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx index 0a2725d..5bf1408 100644 --- a/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx +++ b/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx @@ -57,6 +57,19 @@ describe('EncounterSidebar', () => { expect(analyzingContainer).toHaveAttribute('data-completed', 'true'); }); + it('EncounterSidebar_Flagged_ShowsPolicyCheckIndicator', () => { + render(); + expect(screen.getByText('Policy Check')).toBeInTheDocument(); + expect(screen.getByText(/3\/5/)).toBeInTheDocument(); + }); + + it('EncounterSidebar_Flagged_NoPAStages', () => { + render(); + expect(screen.queryByText('Analyzing')).not.toBeInTheDocument(); + expect(screen.queryByText('Submit')).not.toBeInTheDocument(); + expect(screen.queryByText('Complete')).not.toBeInTheDocument(); + }); + it('EncounterSidebar_Complete_AllPAStagesCompleted', () => { render(); const completeContainer = screen.getByText('Complete').closest('[data-stage]'); diff --git a/apps/dashboard/src/routes/__tests__/ehr-demo.test.tsx b/apps/dashboard/src/routes/__tests__/ehr-demo.test.tsx index 9057977..784b4fa 100644 --- a/apps/dashboard/src/routes/__tests__/ehr-demo.test.tsx +++ b/apps/dashboard/src/routes/__tests__/ehr-demo.test.tsx @@ -24,12 +24,14 @@ vi.mock('@/components/PdfViewerModal', () => ({ : null, })); -// Mock PolicyCriteriaModal to avoid portal issues -vi.mock('@/components/ehr/PolicyCriteriaModal', () => ({ - PolicyCriteriaModal: ({ isOpen }: { isOpen: boolean }) => - isOpen - ? createElement('div', { 'data-testid': 'policy-criteria-modal' }, 'Policy Criteria') - : null, +// Mock PAReadinessWidget for isolation +vi.mock('@/components/ehr/PAReadinessWidget', () => ({ + PAReadinessWidget: ({ state, criteria }: { state: string; criteria: unknown[] }) => + createElement( + 'div', + { 'data-testid': 'pa-readiness-widget' }, + `AuthScript — Policy Pre-Check (${state}, ${criteria?.length ?? 0} criteria)`, + ), })); async function renderEhrDemoPage() { @@ -75,17 +77,9 @@ describe('EhrDemoPage', () => { // Pre-sign action buttons visible expect(screen.getByRole('button', { name: /preview pa form/i })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /view policy criteria/i })).toBeInTheDocument(); - // NO iframe in DOM - expect(screen.queryByTitle('AuthScript Dashboard')).not.toBeInTheDocument(); - expect(document.querySelector('iframe')).toBeNull(); - - // No PAResultsPanel visible initially + // No PAResultsPanel visible initially (before flag delay) expect(screen.queryByText('AuthScript — Prior Authorization')).not.toBeInTheDocument(); - - // No PA sidebar stages initially - expect(screen.queryByText('Prior Auth')).not.toBeInTheDocument(); }); it('EhrDemoPage_PreviewPAForm_OpensBlankForm', async () => { @@ -98,14 +92,6 @@ describe('EhrDemoPage', () => { expect(screen.getByText(/NOFR001/)).toBeInTheDocument(); }); - it('EhrDemoPage_ViewPolicyCriteria_OpensModal', async () => { - await renderEhrDemoPage(); - - fireEvent.click(screen.getByRole('button', { name: /view policy criteria/i })); - - expect(screen.getByTestId('policy-criteria-modal')).toBeInTheDocument(); - }); - it('EhrDemoPage_Sign_ShowsProcessingPanel', async () => { await renderEhrDemoPage(); @@ -201,6 +187,86 @@ describe('EhrDemoPage', () => { expect(apContainer).toHaveAttribute('data-completed', 'true'); }); + it('EhrDemoPage_Load_ShowsPAReadinessAfterDelay', async () => { + await renderEhrDemoPage(); + + // Initially no readiness widget + expect(screen.queryByTestId('pa-readiness-widget')).not.toBeInTheDocument(); + + // After 1500ms flag delay, widget appears + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + expect(screen.getByTestId('pa-readiness-widget')).toBeInTheDocument(); + // Sidebar shows Policy Check + expect(screen.getByText('Policy Check')).toBeInTheDocument(); + }); + + it('EhrDemoPage_Flagged_ShowsPreCheckWidget', async () => { + await renderEhrDemoPage(); + + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + // Widget rendered with criteria (mocked, so check testid + text content) + const widget = screen.getByTestId('pa-readiness-widget'); + expect(widget).toBeInTheDocument(); + expect(widget.textContent).toContain('5 criteria'); + }); + + it('EhrDemoPage_Flagged_SignButton_StillVisible', async () => { + await renderEhrDemoPage(); + + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + expect(screen.getByRole('button', { name: 'Sign Encounter' })).toBeEnabled(); + }); + + it('EhrDemoPage_FullFlow_FlaggedToComplete', async () => { + await renderEhrDemoPage(); + + // 1. Flag + await act(async () => { + vi.advanceTimersByTime(1500); + }); + expect(screen.getByTestId('pa-readiness-widget')).toBeInTheDocument(); + + // 2. Sign from flagged + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Sign Encounter' })); + }); + + // Pre-check widget should be gone, PA panel appears + expect(screen.queryByTestId('pa-readiness-widget')).not.toBeInTheDocument(); + expect(screen.getByText('AuthScript — Prior Authorization')).toBeInTheDocument(); + + // 3. Advance through processing + await act(async () => { + vi.advanceTimersByTime(800); + }); + await act(async () => { + vi.advanceTimersByTime(5000); + }); + + // 4. Reviewing state — confidence and evidence trail + expect(screen.getByText('88%')).toBeInTheDocument(); + expect(screen.getByText('Valid ICD-10 for lumbar pathology')).toBeInTheDocument(); + + // 5. Submit + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /submit to blue cross/i })); + }); + await act(async () => { + vi.advanceTimersByTime(1500); + }); + + expect(screen.getByText('PA Submitted')).toBeInTheDocument(); + }); + it('EhrDemoPage_ViewPdf_OpensPdfViewer', async () => { await renderEhrDemoPage(); diff --git a/apps/dashboard/src/routes/ehr-demo.tsx b/apps/dashboard/src/routes/ehr-demo.tsx index 73f4cb4..ae9d248 100644 --- a/apps/dashboard/src/routes/ehr-demo.tsx +++ b/apps/dashboard/src/routes/ehr-demo.tsx @@ -1,17 +1,17 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { FileText, ClipboardList } from 'lucide-react'; +import { FileText } from 'lucide-react'; import { EhrHeader, EncounterNote, EncounterSidebar, SignEncounterButton, + PAReadinessWidget, PAResultsPanel, } from '@/components/ehr'; import { useEhrDemoFlow } from '@/components/ehr/useEhrDemoFlow'; import { CriteriaReasonDialog } from './analysis.$transactionId'; import { PdfViewerModal } from '@/components/PdfViewerModal'; -import { PolicyCriteriaModal } from '@/components/ehr/PolicyCriteriaModal'; import { DEMO_EHR_PATIENT, DEMO_ENCOUNTER, @@ -25,7 +25,7 @@ import type { Order } from '@/components/ehr/EncounterNote'; const BLANK_PA_FORM_URL = '/pdf-templates/tx-standard-pa-form.pdf'; function deriveOrderStatus(flowState: string, baseStatus: Order['status']): Order['status'] { - if (flowState === 'idle') return baseStatus; + if (flowState === 'idle' || flowState === 'flagged') return baseStatus; if (flowState === 'signing' || flowState === 'processing') return 'pending'; return 'completed'; } @@ -35,24 +35,50 @@ export function EhrDemoPage() { const [selectedCriterion, setSelectedCriterion] = useState(null); const [pdfOpen, setPdfOpen] = useState(false); const [blankFormOpen, setBlankFormOpen] = useState(false); - const [policyCriteriaOpen, setPolicyCriteriaOpen] = useState(false); + + // Auto-trigger PA detection on mount + useEffect(() => { + flow.flag(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const dynamicOrders = DEMO_ORDERS.map(order => ({ ...order, status: deriveOrderStatus(flow.state, order.status), })); + const preCheckCount = flow.preCheckCriteria + ? { met: flow.preCheckCriteria.filter(c => c.status === 'met').length, total: flow.preCheckCriteria.length } + : undefined; + + const isSigned = flow.state !== 'idle' && flow.state !== 'flagged'; + const showPostSignPanel = isSigned && flow.state !== 'error'; + return (
- +
+ + {/* Pre-check readiness widget (visible in flagged state) */} + {flow.state === 'flagged' && flow.preCheckCriteria && ( +
+ +
+ )} +
- - flow.sign()} signed={flow.state !== 'idle'} /> + flow.sign()} signed={isSigned} />
- {flow.state !== 'idle' && ( + + {/* Post-sign PA results panel */} + {showPostSignPanel && (
- - {/* Policy criteria modal */} - setPolicyCriteriaOpen(false)} - />
); } From 567059e5de7be3dcce54b98adac3845c56c366ab Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 14:42:02 -0800 Subject: [PATCH 04/10] fix: address CI failure and CodeRabbit review feedback - Remove accidental lightningcss-darwin-arm64 direct dependency from root package.json that caused EBADPLATFORM on Linux CI runners - Regenerate package-lock.json for cross-platform compatibility - Fix race condition in useEhrDemoFlow: flag() now checks a cancellation ref after the delay so reset() prevents stale state transitions - Align DEMO_PRECHECK_CRITERIA labels with LCD_L34220_POLICY labels (neurological vs neuro) and use exact Set-based matching in test Co-Authored-By: Claude Opus 4.6 --- .../src/components/ehr/useEhrDemoFlow.ts | 6 +- .../src/lib/__tests__/demoData.test.ts | 5 +- apps/dashboard/src/lib/demoData.ts | 2 +- package-lock.json | 1466 +++++++++-------- package.json | 1 - 5 files changed, 796 insertions(+), 684 deletions(-) diff --git a/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts b/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts index 0a0718a..ddbb5a7 100644 --- a/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts +++ b/apps/dashboard/src/components/ehr/useEhrDemoFlow.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useRef } from 'react'; import type { PARequest } from '@/api/graphqlService'; import { DEMO_PA_RESULT, DEMO_PRECHECK_CRITERIA } from '@/lib/demoData'; import type { PreCheckCriterion } from '@/lib/demoData'; @@ -31,10 +31,13 @@ export function useEhrDemoFlow(): EhrDemoFlow { const [paRequest, setPaRequest] = useState(null); const [preCheckCriteria, setPreCheckCriteria] = useState(null); const [error, setError] = useState(null); + const cancelledRef = useRef(false); const flag = useCallback(async () => { if (state !== 'idle') return; + cancelledRef.current = false; await delay(1500); + if (cancelledRef.current) return; setState('flagged'); setPreCheckCriteria(DEMO_PRECHECK_CRITERIA); }, [state]); @@ -87,6 +90,7 @@ export function useEhrDemoFlow(): EhrDemoFlow { }, [paRequest]); const reset = useCallback(() => { + cancelledRef.current = true; setState('idle'); setPaRequest(null); setPreCheckCriteria(null); diff --git a/apps/dashboard/src/lib/__tests__/demoData.test.ts b/apps/dashboard/src/lib/__tests__/demoData.test.ts index 092b965..6ab6b4e 100644 --- a/apps/dashboard/src/lib/__tests__/demoData.test.ts +++ b/apps/dashboard/src/lib/__tests__/demoData.test.ts @@ -86,10 +86,9 @@ describe('demoData', () => { }); it('DEMO_PRECHECK_CRITERIA_LabelsMatchLCDPolicy', () => { - const policyLabels = LCD_L34220_POLICY.criteria.map((c) => c.label); + const policyLabels = new Set(LCD_L34220_POLICY.criteria.map((c) => c.label)); DEMO_PRECHECK_CRITERIA.forEach((c) => { - // Each pre-check label should correspond to a policy criterion - expect(policyLabels.some((pl) => pl.includes(c.label.substring(0, 10)))).toBe(true); + expect(policyLabels.has(c.label)).toBe(true); }); }); diff --git a/apps/dashboard/src/lib/demoData.ts b/apps/dashboard/src/lib/demoData.ts index 66ad940..f53506c 100644 --- a/apps/dashboard/src/lib/demoData.ts +++ b/apps/dashboard/src/lib/demoData.ts @@ -86,7 +86,7 @@ export const DEMO_PRECHECK_CRITERIA: PreCheckCriterion[] = [ source: 'Problem List', }, { - label: 'Red flag symptoms or progressive neuro deficit', + label: 'Red flag symptoms or progressive neurological deficit', status: 'indeterminate', gap: 'Requires encounter documentation — sign encounter to evaluate', }, diff --git a/package-lock.json b/package-lock.json index 76d3e73..fcc543c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "shared/validation" ], "dependencies": { - "lightningcss-darwin-arm64": "^1.31.1", "seroval": "^1.5.0" }, "devDependencies": { @@ -82,14 +81,19 @@ }, "apps/dashboard/node_modules/@types/node": { "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "apps/dashboard/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@acemir/cssom": { "version": "0.9.31", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", @@ -712,9 +716,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", - "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -756,9 +760,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", - "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "funding": [ { @@ -772,8 +776,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.1", - "@csstools/css-calc": "^3.0.0" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { "node": ">=20.19.0" @@ -807,9 +811,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", - "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.0.tgz", + "integrity": "sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==", "dev": true, "funding": [ { @@ -1328,15 +1332,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1354,9 +1358,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1393,20 +1397,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -1452,9 +1456,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1465,9 +1469,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -1502,9 +1506,9 @@ } }, "node_modules/@exodus/bytes": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", - "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", "dev": true, "license": "MIT", "engines": { @@ -1527,31 +1531,31 @@ "license": "MIT" }, "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", - "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.5" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -1559,22 +1563,22 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.22.0.tgz", - "integrity": "sha512-jMpciqEVUBKE1QwU64S4saNMzpsSza6diNCk4MWAeCxO2+LFi2FIFmL2S0VDLzEJCxuvCbU783xi8Hp/gkM5CQ==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", + "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.22.0", - "@shikijs/langs": "^3.22.0", - "@shikijs/themes": "^3.22.0", - "@shikijs/types": "^3.22.0", + "@shikijs/engine-oniguruma": "^3.23.0", + "@shikijs/langs": "^3.23.0", + "@shikijs/themes": "^3.23.0", + "@shikijs/types": "^3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -1704,14 +1708,52 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1720,6 +1762,24 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2738,9 +2798,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz", - "integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -2752,9 +2812,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz", - "integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -2766,9 +2826,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz", - "integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -2780,9 +2840,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz", - "integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -2794,9 +2854,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz", - "integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -2808,9 +2868,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz", - "integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -2822,9 +2882,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz", - "integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -2836,9 +2896,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz", - "integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -2850,9 +2910,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz", - "integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -2864,9 +2924,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz", - "integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -2878,9 +2938,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz", - "integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -2892,9 +2952,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz", - "integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -2906,9 +2966,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz", - "integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -2920,9 +2980,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz", - "integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -2934,9 +2994,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz", - "integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -2948,9 +3008,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz", - "integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -2962,9 +3022,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz", - "integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -2976,9 +3036,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz", - "integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -2990,9 +3050,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz", - "integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -3004,9 +3064,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz", - "integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -3018,9 +3078,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz", - "integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -3032,9 +3092,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz", - "integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -3046,9 +3106,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz", - "integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -3060,9 +3120,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz", - "integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -3074,9 +3134,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz", - "integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -3088,40 +3148,40 @@ ] }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", - "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0", + "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", - "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0" + "@shikijs/types": "3.23.0" } }, "node_modules/@shikijs/themes": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", - "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0" + "@shikijs/types": "3.23.0" } }, "node_modules/@shikijs/types": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", - "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3475,9 +3535,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz", - "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", "dev": true, "license": "MIT", "dependencies": { @@ -3487,37 +3547,37 @@ "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.0" + "tailwindcss": "4.2.1" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz", - "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.0", - "@tailwindcss/oxide-darwin-arm64": "4.2.0", - "@tailwindcss/oxide-darwin-x64": "4.2.0", - "@tailwindcss/oxide-freebsd-x64": "4.2.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", - "@tailwindcss/oxide-linux-x64-musl": "4.2.0", - "@tailwindcss/oxide-wasm32-wasi": "4.2.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz", - "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", "cpu": [ "arm64" ], @@ -3532,9 +3592,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz", - "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", "cpu": [ "arm64" ], @@ -3549,9 +3609,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz", - "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", "cpu": [ "x64" ], @@ -3566,9 +3626,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz", - "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", "cpu": [ "x64" ], @@ -3583,9 +3643,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz", - "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", "cpu": [ "arm" ], @@ -3600,9 +3660,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz", - "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", "cpu": [ "arm64" ], @@ -3617,9 +3677,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz", - "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", "cpu": [ "arm64" ], @@ -3634,9 +3694,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz", - "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", "cpu": [ "x64" ], @@ -3651,9 +3711,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz", - "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", "cpu": [ "x64" ], @@ -3668,9 +3728,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz", - "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3698,9 +3758,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz", - "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", "cpu": [ "arm64" ], @@ -3715,9 +3775,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz", - "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", "cpu": [ "x64" ], @@ -3732,29 +3792,29 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.0.tgz", - "integrity": "sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", + "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.2.0", - "@tailwindcss/oxide": "4.2.0", + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", - "tailwindcss": "4.2.0" + "tailwindcss": "4.2.1" } }, "node_modules/@tailwindcss/vite": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.0.tgz", - "integrity": "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.2.0", - "@tailwindcss/oxide": "4.2.0", - "tailwindcss": "4.2.0" + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" @@ -3964,18 +4024,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/@tanstack/router-plugin/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@tanstack/router-plugin/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4191,7 +4239,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/hast": { @@ -4212,22 +4260,15 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", - "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "version": "25.3.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", + "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" } }, - "node_modules/@types/node/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/pako": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", @@ -4245,7 +4286,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -4255,7 +4296,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -4283,17 +4324,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", - "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.0", - "@typescript-eslint/type-utils": "8.56.0", - "@typescript-eslint/utils": "8.56.0", - "@typescript-eslint/visitor-keys": "8.56.0", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -4306,7 +4347,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.56.0", + "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -4322,16 +4363,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", - "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.56.0", - "@typescript-eslint/types": "8.56.0", - "@typescript-eslint/typescript-estree": "8.56.0", - "@typescript-eslint/visitor-keys": "8.56.0", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "engines": { @@ -4347,14 +4388,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", - "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.0", - "@typescript-eslint/types": "^8.56.0", + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "engines": { @@ -4369,14 +4410,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", - "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.0", - "@typescript-eslint/visitor-keys": "8.56.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4387,9 +4428,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", - "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "license": "MIT", "engines": { @@ -4404,15 +4445,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", - "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.0", - "@typescript-eslint/typescript-estree": "8.56.0", - "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -4429,9 +4470,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", - "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true, "license": "MIT", "engines": { @@ -4443,18 +4484,18 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", - "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.0", - "@typescript-eslint/tsconfig-utils": "8.56.0", - "@typescript-eslint/types": "8.56.0", - "@typescript-eslint/visitor-keys": "8.56.0", + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", - "minimatch": "^9.0.5", + "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" @@ -4470,17 +4511,40 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4500,16 +4564,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", - "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.0", - "@typescript-eslint/types": "8.56.0", - "@typescript-eslint/typescript-estree": "8.56.0" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4524,13 +4588,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", - "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4988,9 +5052,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", - "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", "dev": true, "license": "MIT", "dependencies": { @@ -5241,9 +5305,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "funding": [ { "type": "opencollective", @@ -5367,46 +5431,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5509,14 +5533,14 @@ } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -5549,7 +5573,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -5773,11 +5797,14 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optional": true, + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -5805,22 +5832,22 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "license": "ISC" }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6035,7 +6062,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -6096,25 +6123,25 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -6133,7 +6160,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -6243,6 +6270,19 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -6251,9 +6291,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6418,19 +6458,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6497,6 +6524,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -6560,9 +6605,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", "dev": true, "license": "ISC" }, @@ -6613,9 +6658,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -6825,26 +6870,25 @@ } }, "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10.13.0" + "node": ">= 6" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6925,9 +6969,9 @@ "license": "ISC" }, "node_modules/graphql": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", - "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.1.tgz", + "integrity": "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -7992,6 +8036,27 @@ "lightningcss-win32-x64-msvc": "1.31.1" } }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-darwin-arm64": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", @@ -7999,7 +8064,9 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", + "optional": true, "os": [ "darwin" ], @@ -8011,6 +8078,195 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -8250,9 +8506,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, @@ -8315,9 +8571,9 @@ } }, "node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.3.tgz", + "integrity": "sha512-5rvZbDy5y2k40rre/0OBbYnl03en25XPU3gOVO7532beGMjAipq88VdS9OeLOZNrD+Tb0lDhBJHZ7Gcd8qKlPg==", "dev": true, "license": "ISC", "dependencies": { @@ -8350,7 +8606,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -8462,9 +8718,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, "node_modules/normalize-path": { @@ -8654,14 +8910,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/openapi3-ts": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz", @@ -8963,10 +9211,10 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "devOptional": true, + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -9377,10 +9625,10 @@ } }, "node_modules/rollup": { - "version": "4.58.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", - "integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==", - "devOptional": true, + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -9393,31 +9641,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.58.0", - "@rollup/rollup-android-arm64": "4.58.0", - "@rollup/rollup-darwin-arm64": "4.58.0", - "@rollup/rollup-darwin-x64": "4.58.0", - "@rollup/rollup-freebsd-arm64": "4.58.0", - "@rollup/rollup-freebsd-x64": "4.58.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.58.0", - "@rollup/rollup-linux-arm-musleabihf": "4.58.0", - "@rollup/rollup-linux-arm64-gnu": "4.58.0", - "@rollup/rollup-linux-arm64-musl": "4.58.0", - "@rollup/rollup-linux-loong64-gnu": "4.58.0", - "@rollup/rollup-linux-loong64-musl": "4.58.0", - "@rollup/rollup-linux-ppc64-gnu": "4.58.0", - "@rollup/rollup-linux-ppc64-musl": "4.58.0", - "@rollup/rollup-linux-riscv64-gnu": "4.58.0", - "@rollup/rollup-linux-riscv64-musl": "4.58.0", - "@rollup/rollup-linux-s390x-gnu": "4.58.0", - "@rollup/rollup-linux-x64-gnu": "4.58.0", - "@rollup/rollup-linux-x64-musl": "4.58.0", - "@rollup/rollup-openbsd-x64": "4.58.0", - "@rollup/rollup-openharmony-arm64": "4.58.0", - "@rollup/rollup-win32-arm64-msvc": "4.58.0", - "@rollup/rollup-win32-ia32-msvc": "4.58.0", - "@rollup/rollup-win32-x64-gnu": "4.58.0", - "@rollup/rollup-win32-x64-msvc": "4.58.0", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -9824,7 +10072,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9879,21 +10127,18 @@ } }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/string-width-cjs": { @@ -9912,42 +10157,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -10169,9 +10378,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", - "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", "dev": true, "license": "MIT" }, @@ -10190,31 +10399,54 @@ } }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", - "minimatch": "^9.0.4" + "minimatch": "^10.2.2" }, "engines": { "node": ">=18" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10259,7 +10491,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -10272,29 +10504,11 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -10334,22 +10548,22 @@ } }, "node_modules/tldts": { - "version": "7.0.23", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", - "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.23" + "tldts-core": "^7.0.25" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.23", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", - "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", "dev": true, "license": "MIT" }, @@ -11049,13 +11263,13 @@ } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -11079,16 +11293,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", - "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.56.0", - "@typescript-eslint/parser": "8.56.0", - "@typescript-eslint/typescript-estree": "8.56.0", - "@typescript-eslint/utils": "8.56.0" + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -11129,9 +11343,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -11304,7 +11518,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -11398,29 +11612,11 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11701,18 +11897,18 @@ } }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -11737,70 +11933,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", @@ -11901,28 +12033,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 563fbaf..e89ed93 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@tanstack/router-core": "1.131.35" }, "dependencies": { - "lightningcss-darwin-arm64": "^1.31.1", "seroval": "^1.5.0" } } From 83ec27421ffa917cdb5e81a3277c13a9de7a1d3d Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 14:44:43 -0800 Subject: [PATCH 05/10] fix: resolve lint errors and sync schema drift - Remove unused DEMO_PA_RESULT_SOURCES import from PAResultsPanel test - Remove unused 'within' import from analysis-criteria-dialog test - Run sync:schemas to resolve schema drift in generated files Co-Authored-By: Claude Opus 4.6 --- .../src/api/generated/analysis/analysis.ts | 35 ++++++++----- .../src/api/generated/intelligence.schemas.ts | 23 +++++++++ .../ehr/__tests__/PAResultsPanel.test.tsx | 2 +- .../analysis-criteria-dialog.test.tsx | 2 +- .../Models/Generated/IntelligenceTypes.cs | 48 ++++++++++++++++++ apps/intelligence/openapi.json | 50 +++++++++++++++++-- shared/schemas/intelligence.openapi.json | 50 +++++++++++++++++-- shared/types/src/generated/intelligence.ts | 43 ++++++++++++++-- 8 files changed, 228 insertions(+), 25 deletions(-) diff --git a/apps/dashboard/src/api/generated/analysis/analysis.ts b/apps/dashboard/src/api/generated/analysis/analysis.ts index 4ee815d..a528de8 100644 --- a/apps/dashboard/src/api/generated/analysis/analysis.ts +++ b/apps/dashboard/src/api/generated/analysis/analysis.ts @@ -21,6 +21,7 @@ import type { } from '@tanstack/react-query'; import type { + AnalyzeAnalyzePostParams, AnalyzeRequest, AnalyzeWithDocumentsAnalyzeWithDocumentsPostParams, BodyAnalyzeWithDocumentsAnalyzeWithDocumentsPost, @@ -36,6 +37,8 @@ import type { * Analyze clinical data and generate PA form response. Uses LLM to extract evidence from clinical data and generate PA form. +Resolves policy from registry; unknown CPT codes fall back to generic policy. +When demo=True and procedure_code is 72148, returns a canned demo response. * @summary Analyze */ export type analyzeAnalyzePostResponse200 = { @@ -57,17 +60,25 @@ export type analyzeAnalyzePostResponseError = (analyzeAnalyzePostResponse422) & export type analyzeAnalyzePostResponse = (analyzeAnalyzePostResponseSuccess | analyzeAnalyzePostResponseError) -export const getAnalyzeAnalyzePostUrl = () => { +export const getAnalyzeAnalyzePostUrl = (params?: AnalyzeAnalyzePostParams,) => { + const normalizedParams = new URLSearchParams(); + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); - + const stringifiedParams = normalizedParams.toString(); - return `http://localhost:8000/analyze` + return stringifiedParams.length > 0 ? `http://localhost:8000/analyze?${stringifiedParams}` : `http://localhost:8000/analyze` } -export const analyzeAnalyzePost = async (analyzeRequest: AnalyzeRequest, options?: RequestInit): Promise => { +export const analyzeAnalyzePost = async (analyzeRequest: AnalyzeRequest, + params?: AnalyzeAnalyzePostParams, options?: RequestInit): Promise => { - const res = await fetch(getAnalyzeAnalyzePostUrl(), + const res = await fetch(getAnalyzeAnalyzePostUrl(params), { ...options, method: 'POST', @@ -87,8 +98,8 @@ export const analyzeAnalyzePost = async (analyzeRequest: AnalyzeRequest, options export const getAnalyzeAnalyzePostMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: AnalyzeRequest}, TContext>, fetch?: RequestInit} -): UseMutationOptions>, TError,{data: AnalyzeRequest}, TContext> => { + TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: AnalyzeRequest;params?: AnalyzeAnalyzePostParams}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{data: AnalyzeRequest;params?: AnalyzeAnalyzePostParams}, TContext> => { const mutationKey = ['analyzeAnalyzePost']; const {mutation: mutationOptions, fetch: fetchOptions} = options ? @@ -100,10 +111,10 @@ const {mutation: mutationOptions, fetch: fetchOptions} = options ? - const mutationFn: MutationFunction>, {data: AnalyzeRequest}> = (props) => { - const {data} = props ?? {}; + const mutationFn: MutationFunction>, {data: AnalyzeRequest;params?: AnalyzeAnalyzePostParams}> = (props) => { + const {data,params} = props ?? {}; - return analyzeAnalyzePost(data,fetchOptions) + return analyzeAnalyzePost(data,params,fetchOptions) } @@ -119,11 +130,11 @@ const {mutation: mutationOptions, fetch: fetchOptions} = options ? * @summary Analyze */ export const useAnalyzeAnalyzePost = (options?: { mutation?:UseMutationOptions>, TError,{data: AnalyzeRequest}, TContext>, fetch?: RequestInit} + TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: AnalyzeRequest;params?: AnalyzeAnalyzePostParams}, TContext>, fetch?: RequestInit} ): UseMutationResult< Awaited>, TError, - {data: AnalyzeRequest}, + {data: AnalyzeRequest;params?: AnalyzeAnalyzePostParams}, TContext > => { diff --git a/apps/dashboard/src/api/generated/intelligence.schemas.ts b/apps/dashboard/src/api/generated/intelligence.schemas.ts index cd7f46f..e84b4de 100644 --- a/apps/dashboard/src/api/generated/intelligence.schemas.ts +++ b/apps/dashboard/src/api/generated/intelligence.schemas.ts @@ -45,6 +45,8 @@ export const EvidenceItemStatus = { export interface EvidenceItem { /** ID of the policy criterion */ criterion_id: string; + /** Human-readable criterion label */ + criterion_label?: string; /** Criterion status */ status: EvidenceItemStatus; /** Extracted evidence text */ @@ -81,6 +83,16 @@ export const PAFormResponseRecommendation = { */ export type PAFormResponseFieldMappings = {[key: string]: string}; +/** + * Policy identifier + */ +export type PAFormResponsePolicyId = string | null; + +/** + * LCD article reference + */ +export type PAFormResponseLcdReference = string | null; + /** * Complete PA form response from analysis. */ @@ -109,6 +121,10 @@ export interface PAFormResponse { confidence_score: number; /** PDF field name to value mappings */ field_mappings: PAFormResponseFieldMappings; + /** Policy identifier */ + policy_id?: PAFormResponsePolicyId; + /** LCD article reference */ + lcd_reference?: PAFormResponseLcdReference; } export type ValidationErrorLocItem = string | number; @@ -119,6 +135,13 @@ export interface ValidationError { type: string; } +export type AnalyzeAnalyzePostParams = { +/** + * Return canned demo response for supported procedures + */ +demo?: boolean; +}; + export type AnalyzeWithDocumentsAnalyzeWithDocumentsPostParams = { patient_id: string; procedure_code: string; diff --git a/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx b/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx index 3acaeb2..5209903 100644 --- a/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx +++ b/apps/dashboard/src/components/ehr/__tests__/PAResultsPanel.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { PAResultsPanel } from '../PAResultsPanel'; import type { PARequest } from '@/api/graphqlService'; -import { DEMO_PA_RESULT, DEMO_PA_RESULT_SOURCES } from '@/lib/demoData'; +import { DEMO_PA_RESULT } from '@/lib/demoData'; function buildMockPARequest(overrides: Partial = {}): PARequest { return { diff --git a/apps/dashboard/src/routes/__tests__/analysis-criteria-dialog.test.tsx b/apps/dashboard/src/routes/__tests__/analysis-criteria-dialog.test.tsx index 98d1e60..439b24b 100644 --- a/apps/dashboard/src/routes/__tests__/analysis-criteria-dialog.test.tsx +++ b/apps/dashboard/src/routes/__tests__/analysis-criteria-dialog.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { render, screen, within } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { CriteriaReasonDialog } from '../analysis.$transactionId'; // Mock createPortal so the dialog renders inline instead of into document.body diff --git a/apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs b/apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs index 0436da4..742e301 100644 --- a/apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs +++ b/apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs @@ -84,6 +84,12 @@ public partial class EvidenceItem [Newtonsoft.Json.JsonProperty("criterion_id", Required = Newtonsoft.Json.Required.Always)] public string Criterion_id { get; set; } + /// + /// Human-readable criterion label + /// + [Newtonsoft.Json.JsonProperty("criterion_label", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Criterion_label { get; set; } = ""; + /// /// Criterion status /// @@ -206,6 +212,18 @@ public partial class PAFormResponse [Newtonsoft.Json.JsonProperty("field_mappings", Required = Newtonsoft.Json.Required.Always)] public System.Collections.Generic.IDictionary Field_mappings { get; set; } = new System.Collections.Generic.Dictionary(); + /// + /// Policy identifier + /// + [Newtonsoft.Json.JsonProperty("policy_id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Policy_id Policy_id { get; set; } + + /// + /// LCD article reference + /// + [Newtonsoft.Json.JsonProperty("lcd_reference", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Lcd_reference Lcd_reference { get; set; } + private System.Collections.Generic.IDictionary _additionalProperties; [Newtonsoft.Json.JsonExtensionData] @@ -271,6 +289,36 @@ public enum PAFormResponseRecommendation } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Policy_id + { + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Lcd_reference + { + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class Loc { diff --git a/apps/intelligence/openapi.json b/apps/intelligence/openapi.json index 3188a04..876d614 100644 --- a/apps/intelligence/openapi.json +++ b/apps/intelligence/openapi.json @@ -12,17 +12,31 @@ "Analysis" ], "summary": "Analyze", - "description": "Analyze clinical data and generate PA form response.\n\nUses LLM to extract evidence from clinical data and generate PA form.", + "description": "Analyze clinical data and generate PA form response.\n\nUses LLM to extract evidence from clinical data and generate PA form.\nResolves policy from registry; unknown CPT codes fall back to generic policy.\nWhen demo=True and procedure_code is 72148, returns a canned demo response.", "operationId": "analyze_analyze_post", + "parameters": [ + { + "name": "demo", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "Return canned demo response for supported procedures", + "default": false, + "title": "Demo" + }, + "description": "Return canned demo response for supported procedures" + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnalyzeRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -220,6 +234,12 @@ "title": "Criterion Id", "description": "ID of the policy criterion" }, + "criterion_label": { + "type": "string", + "title": "Criterion Label", + "description": "Human-readable criterion label", + "default": "" + }, "status": { "type": "string", "enum": [ @@ -339,6 +359,30 @@ "type": "object", "title": "Field Mappings", "description": "PDF field name to value mappings" + }, + "policy_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Policy Id", + "description": "Policy identifier" + }, + "lcd_reference": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Lcd Reference", + "description": "LCD article reference" } }, "type": "object", diff --git a/shared/schemas/intelligence.openapi.json b/shared/schemas/intelligence.openapi.json index 3188a04..876d614 100644 --- a/shared/schemas/intelligence.openapi.json +++ b/shared/schemas/intelligence.openapi.json @@ -12,17 +12,31 @@ "Analysis" ], "summary": "Analyze", - "description": "Analyze clinical data and generate PA form response.\n\nUses LLM to extract evidence from clinical data and generate PA form.", + "description": "Analyze clinical data and generate PA form response.\n\nUses LLM to extract evidence from clinical data and generate PA form.\nResolves policy from registry; unknown CPT codes fall back to generic policy.\nWhen demo=True and procedure_code is 72148, returns a canned demo response.", "operationId": "analyze_analyze_post", + "parameters": [ + { + "name": "demo", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "Return canned demo response for supported procedures", + "default": false, + "title": "Demo" + }, + "description": "Return canned demo response for supported procedures" + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnalyzeRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -220,6 +234,12 @@ "title": "Criterion Id", "description": "ID of the policy criterion" }, + "criterion_label": { + "type": "string", + "title": "Criterion Label", + "description": "Human-readable criterion label", + "default": "" + }, "status": { "type": "string", "enum": [ @@ -339,6 +359,30 @@ "type": "object", "title": "Field Mappings", "description": "PDF field name to value mappings" + }, + "policy_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Policy Id", + "description": "Policy identifier" + }, + "lcd_reference": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Lcd Reference", + "description": "LCD article reference" } }, "type": "object", diff --git a/shared/types/src/generated/intelligence.ts b/shared/types/src/generated/intelligence.ts index ab90ba4..dcf0a52 100644 --- a/shared/types/src/generated/intelligence.ts +++ b/shared/types/src/generated/intelligence.ts @@ -45,6 +45,8 @@ export const EvidenceItemStatus = { export interface EvidenceItem { /** ID of the policy criterion */ criterion_id: string; + /** Human-readable criterion label */ + criterion_label?: string; /** Criterion status */ status: EvidenceItemStatus; /** Extracted evidence text */ @@ -81,6 +83,16 @@ export const PAFormResponseRecommendation = { */ export type PAFormResponseFieldMappings = {[key: string]: string}; +/** + * Policy identifier + */ +export type PAFormResponsePolicyId = string | null; + +/** + * LCD article reference + */ +export type PAFormResponseLcdReference = string | null; + /** * Complete PA form response from analysis. */ @@ -109,6 +121,10 @@ export interface PAFormResponse { confidence_score: number; /** PDF field name to value mappings */ field_mappings: PAFormResponseFieldMappings; + /** Policy identifier */ + policy_id?: PAFormResponsePolicyId; + /** LCD article reference */ + lcd_reference?: PAFormResponseLcdReference; } export type ValidationErrorLocItem = string | number; @@ -119,6 +135,13 @@ export interface ValidationError { type: string; } +export type AnalyzeAnalyzePostParams = { +/** + * Return canned demo response for supported procedures + */ +demo?: boolean; +}; + export type AnalyzeWithDocumentsAnalyzeWithDocumentsPostParams = { patient_id: string; procedure_code: string; @@ -133,6 +156,8 @@ export type RootGet200 = {[key: string]: string}; * Analyze clinical data and generate PA form response. Uses LLM to extract evidence from clinical data and generate PA form. +Resolves policy from registry; unknown CPT codes fall back to generic policy. +When demo=True and procedure_code is 72148, returns a canned demo response. * @summary Analyze */ export type analyzeAnalyzePostResponse200 = { @@ -154,17 +179,25 @@ export type analyzeAnalyzePostResponseError = (analyzeAnalyzePostResponse422) & export type analyzeAnalyzePostResponse = (analyzeAnalyzePostResponseSuccess | analyzeAnalyzePostResponseError) -export const getAnalyzeAnalyzePostUrl = () => { +export const getAnalyzeAnalyzePostUrl = (params?: AnalyzeAnalyzePostParams,) => { + const normalizedParams = new URLSearchParams(); + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); - + const stringifiedParams = normalizedParams.toString(); - return `/analyze` + return stringifiedParams.length > 0 ? `/analyze?${stringifiedParams}` : `/analyze` } -export const analyzeAnalyzePost = async (analyzeRequest: AnalyzeRequest, options?: RequestInit): Promise => { +export const analyzeAnalyzePost = async (analyzeRequest: AnalyzeRequest, + params?: AnalyzeAnalyzePostParams, options?: RequestInit): Promise => { - const res = await fetch(getAnalyzeAnalyzePostUrl(), + const res = await fetch(getAnalyzeAnalyzePostUrl(params), { ...options, method: 'POST', From 4bc459b29a11c5f54033aedb052d5ad3fb132900 Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 14:47:00 -0800 Subject: [PATCH 06/10] style(intelligence): fix all ruff lint errors - Fix E501 line-too-long violations across policy seeds, tests, and reasoning modules by wrapping long strings and function signatures - Fix E402 import ordering in test_evidence_extractor.py - Fix F841 unused variable in test_form_generator.py - Fix import sorting (I001) via ruff --fix Pre-existing issues surfaced by schema sync triggering Intelligence CI. Co-Authored-By: Claude Opus 4.6 --- apps/intelligence/src/api/analyze.py | 5 +- .../src/policies/seed/__init__.py | 8 +- .../src/policies/seed/echocardiogram.py | 25 ++++++- .../src/policies/seed/epidural_steroid.py | 5 +- .../src/policies/seed/mri_lumbar.py | 5 +- .../src/reasoning/confidence_scorer.py | 1 - .../src/reasoning/evidence_extractor.py | 4 +- apps/intelligence/src/tests/test_analyze.py | 12 ++- .../src/tests/test_confidence_scorer.py | 75 +++++++++++++++---- .../src/tests/test_echocardiogram_policy.py | 1 + .../src/tests/test_evidence_extractor.py | 3 +- .../src/tests/test_form_generator.py | 7 +- .../src/tests/test_generic_policy.py | 1 + .../src/tests/test_pa_form_model.py | 1 - .../src/tests/test_policy_model.py | 1 - .../src/tests/test_policy_registry.py | 5 +- .../src/tests/test_seed_policies.py | 10 +-- 17 files changed, 127 insertions(+), 42 deletions(-) diff --git a/apps/intelligence/src/api/analyze.py b/apps/intelligence/src/api/analyze.py index 7e7ecd5..20ec4f8 100644 --- a/apps/intelligence/src/api/analyze.py +++ b/apps/intelligence/src/api/analyze.py @@ -44,7 +44,10 @@ class AnalyzeRequest(BaseModel): @router.post("", response_model=PAFormResponse) async def analyze( request: AnalyzeRequest, - demo: bool = Query(default=False, description="Return canned demo response for supported procedures"), + demo: bool = Query( + default=False, + description="Return canned demo response for supported procedures", + ), ) -> PAFormResponse: """ Analyze clinical data and generate PA form response. diff --git a/apps/intelligence/src/policies/seed/__init__.py b/apps/intelligence/src/policies/seed/__init__.py index 9cb0bb3..bd49d75 100644 --- a/apps/intelligence/src/policies/seed/__init__.py +++ b/apps/intelligence/src/policies/seed/__init__.py @@ -1,10 +1,10 @@ """Seed policy loader.""" -from src.policies.seed.mri_lumbar import POLICY as MRI_LUMBAR +from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM +from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID from src.policies.seed.mri_brain import POLICY as MRI_BRAIN -from src.policies.seed.tka import POLICY as TKA +from src.policies.seed.mri_lumbar import POLICY as MRI_LUMBAR from src.policies.seed.physical_therapy import POLICY as PHYSICAL_THERAPY -from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID -from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM +from src.policies.seed.tka import POLICY as TKA ALL_SEED_POLICIES = [MRI_LUMBAR, MRI_BRAIN, TKA, PHYSICAL_THERAPY, EPIDURAL_STEROID, ECHOCARDIOGRAM] diff --git a/apps/intelligence/src/policies/seed/echocardiogram.py b/apps/intelligence/src/policies/seed/echocardiogram.py index a742a9f..c181b1f 100644 --- a/apps/intelligence/src/policies/seed/echocardiogram.py +++ b/apps/intelligence/src/policies/seed/echocardiogram.py @@ -20,28 +20,45 @@ criteria=[ PolicyCriterion( id="diagnosis_present", - description="Valid ICD-10 for cardiac pathology (heart failure, valvular disease, arrhythmia, cardiomyopathy)", + description=( + "Valid ICD-10 for cardiac pathology" + " (heart failure, valvular disease," + " arrhythmia, cardiomyopathy)" + ), weight=0.20, required=True, lcd_section="ACC/AHA AUC — Covered Cardiac Diagnoses", ), PolicyCriterion( id="clinical_indication", - description="Documented clinical indication for echocardiographic assessment (evaluate EF, assess valvular function, monitor known condition, new symptoms)", + description=( + "Documented clinical indication for" + " echocardiographic assessment (evaluate EF," + " assess valvular function, monitor known" + " condition, new symptoms)" + ), weight=0.35, required=True, lcd_section="ACC/AHA AUC — Clinical Indications for TTE", ), PolicyCriterion( id="symptom_or_change", - description="New or worsening symptoms, or clinical change warranting imaging (dyspnea, edema, chest pain, syncope, new murmur)", + description=( + "New or worsening symptoms, or clinical change" + " warranting imaging (dyspnea, edema," + " chest pain, syncope, new murmur)" + ), weight=0.25, required=False, lcd_section="ACC/AHA AUC — Symptom-Based Indications", ), PolicyCriterion( id="no_recent_duplicate", - description="No echocardiogram within prior 12 months for same indication, unless clinical change documented", + description=( + "No echocardiogram within prior 12 months" + " for same indication, unless clinical" + " change documented" + ), weight=0.20, required=False, lcd_section="ACC/AHA AUC — Repeat Study Appropriateness", diff --git a/apps/intelligence/src/policies/seed/epidural_steroid.py b/apps/intelligence/src/policies/seed/epidural_steroid.py index 64599c9..ed8f832 100644 --- a/apps/intelligence/src/policies/seed/epidural_steroid.py +++ b/apps/intelligence/src/policies/seed/epidural_steroid.py @@ -21,7 +21,10 @@ ), PolicyCriterion( id="severity_documented", - description="Pain severe enough to impact QoL/function, documented with standardized scale", + description=( + "Pain severe enough to impact QoL/function," + " documented with standardized scale" + ), weight=0.20, required=True, lcd_section="L39240 — Requirement 2", diff --git a/apps/intelligence/src/policies/seed/mri_lumbar.py b/apps/intelligence/src/policies/seed/mri_lumbar.py index d57836b..2a0fc01 100644 --- a/apps/intelligence/src/policies/seed/mri_lumbar.py +++ b/apps/intelligence/src/policies/seed/mri_lumbar.py @@ -36,7 +36,10 @@ ), PolicyCriterion( id="clinical_rationale", - description="Imaging abnormalities alone insufficient; supporting clinical rationale documented", + description=( + "Imaging abnormalities alone insufficient;" + " supporting clinical rationale documented" + ), weight=0.20, required=True, lcd_section="L34220 — Coverage Principle", diff --git a/apps/intelligence/src/reasoning/confidence_scorer.py b/apps/intelligence/src/reasoning/confidence_scorer.py index 22f5c53..6067ad7 100644 --- a/apps/intelligence/src/reasoning/confidence_scorer.py +++ b/apps/intelligence/src/reasoning/confidence_scorer.py @@ -6,7 +6,6 @@ from src.models.pa_form import EvidenceItem from src.models.policy import PolicyDefinition - STATUS_SCORES = {"MET": 1.0, "UNCLEAR": 0.5, "NOT_MET": 0.0} SCORE_FLOOR = 0.05 diff --git a/apps/intelligence/src/reasoning/evidence_extractor.py b/apps/intelligence/src/reasoning/evidence_extractor.py index 8f343cf..379c4e2 100644 --- a/apps/intelligence/src/reasoning/evidence_extractor.py +++ b/apps/intelligence/src/reasoning/evidence_extractor.py @@ -209,7 +209,9 @@ async def extract_evidence( crit.id if isinstance(crit, PolicyCriterion) else crit.get("id", "unknown") ) criterion_label = ( - crit.description if isinstance(crit, PolicyCriterion) else crit.get("description", "") + crit.description + if isinstance(crit, PolicyCriterion) + else crit.get("description", "") ) logger.error("Criterion %s evaluation failed: %s", criterion_id, result) evidence_items.append( diff --git a/apps/intelligence/src/tests/test_analyze.py b/apps/intelligence/src/tests/test_analyze.py index a2f60c2..6120b2a 100644 --- a/apps/intelligence/src/tests/test_analyze.py +++ b/apps/intelligence/src/tests/test_analyze.py @@ -30,7 +30,9 @@ def valid_request() -> AnalyzeRequest: @pytest.mark.asyncio async def test_analyze_returns_approve(valid_request: AnalyzeRequest) -> None: """Should return APPROVE recommendation with high confidence.""" - mock_llm = AsyncMock(return_value="The criterion is MET based on the evidence. HIGH CONFIDENCE.") + mock_llm = AsyncMock( + return_value="The criterion is MET based on the evidence. HIGH CONFIDENCE." + ) with ( patch("src.reasoning.evidence_extractor.chat_completion", mock_llm), patch("src.reasoning.form_generator.chat_completion", mock_llm), @@ -94,7 +96,13 @@ async def test_analyze_unknown_cpt_returns_200_with_generic() -> None: request = AnalyzeRequest( patient_id="test", procedure_code="99999", - clinical_data={"patient": {"name": "Test", "birth_date": "1980-01-01", "member_id": "M001"}}, + clinical_data={ + "patient": { + "name": "Test", + "birth_date": "1980-01-01", + "member_id": "M001", + }, + }, ) mock_llm = AsyncMock(return_value="MET. Evidence found.") with ( diff --git a/apps/intelligence/src/tests/test_confidence_scorer.py b/apps/intelligence/src/tests/test_confidence_scorer.py index 303ad53..fbbd515 100644 --- a/apps/intelligence/src/tests/test_confidence_scorer.py +++ b/apps/intelligence/src/tests/test_confidence_scorer.py @@ -1,25 +1,59 @@ """Tests for weighted LCD compliance confidence scorer.""" import pytest + from src.models.pa_form import EvidenceItem from src.models.policy import PolicyCriterion, PolicyDefinition -from src.reasoning.confidence_scorer import ScoreResult, calculate_confidence - - -def _make_criterion(id: str, weight: float, required: bool = False, bypasses: list[str] | None = None) -> PolicyCriterion: - return PolicyCriterion(id=id, description=f"Test {id}", weight=weight, required=required, bypasses=bypasses or []) - -def _make_evidence(criterion_id: str, status: str, confidence: float = 0.9) -> EvidenceItem: - return EvidenceItem(criterion_id=criterion_id, status=status, evidence="test", source="test", confidence=confidence) - -def _make_policy(criteria: list[PolicyCriterion]) -> PolicyDefinition: - return PolicyDefinition(policy_id="test", policy_name="Test", payer="Test", procedure_codes=["72148"], criteria=criteria) +from src.reasoning.confidence_scorer import calculate_confidence + + +def _make_criterion( + id: str, + weight: float, + required: bool = False, + bypasses: list[str] | None = None, +) -> PolicyCriterion: + return PolicyCriterion( + id=id, + description=f"Test {id}", + weight=weight, + required=required, + bypasses=bypasses or [], + ) + +def _make_evidence( + criterion_id: str, + status: str, + confidence: float = 0.9, +) -> EvidenceItem: + return EvidenceItem( + criterion_id=criterion_id, + status=status, + evidence="test", + source="test", + confidence=confidence, + ) + +def _make_policy( + criteria: list[PolicyCriterion], +) -> PolicyDefinition: + return PolicyDefinition( + policy_id="test", + policy_name="Test", + payer="Test", + procedure_codes=["72148"], + criteria=criteria, + ) def test_all_met_high_confidence(): """All criteria MET with high confidence -> score >= 0.85, APPROVE.""" criteria = [_make_criterion("c1", 0.3), _make_criterion("c2", 0.3), _make_criterion("c3", 0.4)] policy = _make_policy(criteria) - evidence = [_make_evidence("c1", "MET", 0.9), _make_evidence("c2", "MET", 0.9), _make_evidence("c3", "MET", 0.9)] + evidence = [ + _make_evidence("c1", "MET", 0.9), + _make_evidence("c2", "MET", 0.9), + _make_evidence("c3", "MET", 0.9), + ] result = calculate_confidence(evidence, policy) assert result.score >= 0.85 assert result.recommendation == "APPROVE" @@ -27,7 +61,10 @@ def test_all_met_high_confidence(): def test_all_not_met_hits_floor(): """All NOT_MET -> score = 0.05 (floor).""" - criteria = [_make_criterion("c1", 0.5, required=True), _make_criterion("c2", 0.5, required=True)] + criteria = [ + _make_criterion("c1", 0.5, required=True), + _make_criterion("c2", 0.5, required=True), + ] policy = _make_policy(criteria) evidence = [_make_evidence("c1", "NOT_MET", 0.9), _make_evidence("c2", "NOT_MET", 0.9)] result = calculate_confidence(evidence, policy) @@ -76,8 +113,16 @@ def test_multiple_required_not_met_stacks_penalty(): _make_criterion("c3", 0.4), ] policy = _make_policy(criteria) - evidence_one = [_make_evidence("c1", "MET"), _make_evidence("c2", "NOT_MET"), _make_evidence("c3", "MET")] - evidence_two = [_make_evidence("c1", "NOT_MET"), _make_evidence("c2", "NOT_MET"), _make_evidence("c3", "MET")] + evidence_one = [ + _make_evidence("c1", "MET"), + _make_evidence("c2", "NOT_MET"), + _make_evidence("c3", "MET"), + ] + evidence_two = [ + _make_evidence("c1", "NOT_MET"), + _make_evidence("c2", "NOT_MET"), + _make_evidence("c3", "MET"), + ] result_one = calculate_confidence(evidence_one, policy) result_two = calculate_confidence(evidence_two, policy) assert result_two.score < result_one.score diff --git a/apps/intelligence/src/tests/test_echocardiogram_policy.py b/apps/intelligence/src/tests/test_echocardiogram_policy.py index ac11403..63e334e 100644 --- a/apps/intelligence/src/tests/test_echocardiogram_policy.py +++ b/apps/intelligence/src/tests/test_echocardiogram_policy.py @@ -1,5 +1,6 @@ """Tests for echocardiogram seed policy.""" import pytest + from src.policies.registry import registry from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM diff --git a/apps/intelligence/src/tests/test_evidence_extractor.py b/apps/intelligence/src/tests/test_evidence_extractor.py index 867e2f5..f8559ad 100644 --- a/apps/intelligence/src/tests/test_evidence_extractor.py +++ b/apps/intelligence/src/tests/test_evidence_extractor.py @@ -7,6 +7,7 @@ from src.models.clinical_bundle import ClinicalBundle, Condition, PatientInfo from src.models.pa_form import EvidenceItem +from src.models.policy import PolicyCriterion, PolicyDefinition from src.reasoning.evidence_extractor import evaluate_criterion, extract_evidence @@ -234,8 +235,6 @@ def test_get_llm_semaphore_returns_singleton(): # --- T006: Evidence extractor enhancement tests --- -from src.models.policy import PolicyCriterion, PolicyDefinition - def _make_policy_def() -> PolicyDefinition: return PolicyDefinition( diff --git a/apps/intelligence/src/tests/test_form_generator.py b/apps/intelligence/src/tests/test_form_generator.py index c57226f..ca42a85 100644 --- a/apps/intelligence/src/tests/test_form_generator.py +++ b/apps/intelligence/src/tests/test_form_generator.py @@ -189,10 +189,13 @@ async def test_generate_form_data_delegates_to_scorer( mock_scorer = ScoreResult(score=0.72, recommendation="MANUAL_REVIEW") mock_llm = AsyncMock(return_value="Summary.") with ( - patch("src.reasoning.form_generator.calculate_confidence", return_value=mock_scorer) as mock_calc, + patch( + "src.reasoning.form_generator.calculate_confidence", + return_value=mock_scorer, + ) as mock_calc, patch("src.reasoning.form_generator.chat_completion", mock_llm), ): - result = await generate_form_data(sample_bundle, sample_evidence, sample_policy) + await generate_form_data(sample_bundle, sample_evidence, sample_policy) mock_calc.assert_called_once() diff --git a/apps/intelligence/src/tests/test_generic_policy.py b/apps/intelligence/src/tests/test_generic_policy.py index edaffda..5aae820 100644 --- a/apps/intelligence/src/tests/test_generic_policy.py +++ b/apps/intelligence/src/tests/test_generic_policy.py @@ -1,5 +1,6 @@ """Tests for generic fallback policy builder.""" import pytest + from src.models.policy import PolicyDefinition from src.policies.generic_policy import build_generic_policy diff --git a/apps/intelligence/src/tests/test_pa_form_model.py b/apps/intelligence/src/tests/test_pa_form_model.py index 9658b43..9d649f9 100644 --- a/apps/intelligence/src/tests/test_pa_form_model.py +++ b/apps/intelligence/src/tests/test_pa_form_model.py @@ -1,5 +1,4 @@ """Tests for PAFormResponse model update.""" -import pytest from src.models.pa_form import PAFormResponse diff --git a/apps/intelligence/src/tests/test_policy_model.py b/apps/intelligence/src/tests/test_policy_model.py index c2fdb93..62f4ac2 100644 --- a/apps/intelligence/src/tests/test_policy_model.py +++ b/apps/intelligence/src/tests/test_policy_model.py @@ -1,5 +1,4 @@ """Tests for policy data models.""" -import pytest from src.models.policy import PolicyCriterion, PolicyDefinition diff --git a/apps/intelligence/src/tests/test_policy_registry.py b/apps/intelligence/src/tests/test_policy_registry.py index 36037dc..9924346 100644 --- a/apps/intelligence/src/tests/test_policy_registry.py +++ b/apps/intelligence/src/tests/test_policy_registry.py @@ -1,5 +1,6 @@ """Tests for policy registry.""" import pytest + from src.models.policy import PolicyDefinition from src.policies.registry import PolicyRegistry, registry @@ -71,4 +72,6 @@ def test_seed_policy_weights_sum_approximately_one(): for cpt in seed_cpts: policy = registry.resolve(cpt) total = sum(c.weight for c in policy.criteria) - assert total == pytest.approx(1.0, abs=0.01), f"Policy {policy.policy_id}: weights sum to {total}" + assert total == pytest.approx(1.0, abs=0.01), ( + f"Policy {policy.policy_id}: weights sum to {total}" + ) diff --git a/apps/intelligence/src/tests/test_seed_policies.py b/apps/intelligence/src/tests/test_seed_policies.py index 4640403..985f04a 100644 --- a/apps/intelligence/src/tests/test_seed_policies.py +++ b/apps/intelligence/src/tests/test_seed_policies.py @@ -1,12 +1,12 @@ """Tests for LCD-backed seed policies.""" import pytest -from src.policies.seed.mri_lumbar import POLICY as MRI_LUMBAR + +from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM +from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID from src.policies.seed.mri_brain import POLICY as MRI_BRAIN -from src.policies.seed.tka import POLICY as TKA +from src.policies.seed.mri_lumbar import POLICY as MRI_LUMBAR from src.policies.seed.physical_therapy import POLICY as PHYSICAL_THERAPY -from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID -from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM - +from src.policies.seed.tka import POLICY as TKA ALL_POLICIES = [MRI_LUMBAR, MRI_BRAIN, TKA, PHYSICAL_THERAPY, EPIDURAL_STEROID, ECHOCARDIOGRAM] From 7d4656dbc2a291706ba753b65156a7ee343927d6 Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 14:50:24 -0800 Subject: [PATCH 07/10] fix: resolve mypy and TypeScript type check errors - intelligence: use lru_cache instead of manual _cached attribute pattern - intelligence: add Literal type annotation for recommendation variable - intelligence: add PolicyRegistry type annotation for register_all_seeds - intelligence: fix EvidenceItem status type in test helper - dashboard: fix NewPAModal test mock to match Procedure interface Co-Authored-By: Claude Opus 4.6 --- .../src/components/__tests__/NewPAModal.test.tsx | 6 ++++-- apps/intelligence/src/api/analyze.py | 14 ++++++++------ apps/intelligence/src/policies/seed/__init__.py | 3 ++- .../src/reasoning/confidence_scorer.py | 1 + .../src/tests/test_confidence_scorer.py | 6 +++++- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/components/__tests__/NewPAModal.test.tsx b/apps/dashboard/src/components/__tests__/NewPAModal.test.tsx index ff35143..7d94ff6 100644 --- a/apps/dashboard/src/components/__tests__/NewPAModal.test.tsx +++ b/apps/dashboard/src/components/__tests__/NewPAModal.test.tsx @@ -3,6 +3,7 @@ import { describe, it, expect, vi } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { NewPAModal } from '../NewPAModal'; import { type Patient } from '../../lib/patients'; +import { type Procedure } from '@/api/graphqlService'; // Mock TanStack Router vi.mock('@tanstack/react-router', () => ({ @@ -48,10 +49,11 @@ const mockPatient: Patient = { phone: '(253) 555-0654', }; -const mockService = { +const mockService: Procedure = { code: '72148', name: 'MRI Lumbar Spine', - type: 'procedure' as const, + category: 'Imaging', + requiresPA: true, }; describe('NewPAModal', () => { diff --git a/apps/intelligence/src/api/analyze.py b/apps/intelligence/src/api/analyze.py index 20ec4f8..aab2fee 100644 --- a/apps/intelligence/src/api/analyze.py +++ b/apps/intelligence/src/api/analyze.py @@ -5,6 +5,7 @@ import asyncio import json +from functools import lru_cache from pathlib import Path from typing import Any @@ -23,14 +24,15 @@ _DEMO_PROCEDURE_CODE = "72148" +@lru_cache(maxsize=1) def _load_demo_response() -> PAFormResponse: """Load and cache the canned demo response for MRI Lumbar Spine.""" - if not hasattr(_load_demo_response, "_cached"): - fixture_path = Path(__file__).parent.parent / "fixtures" / "demo_mri_lumbar.json" - with open(fixture_path) as f: - data = json.load(f) - _load_demo_response._cached = PAFormResponse(**data) - return _load_demo_response._cached + fixture_path = ( + Path(__file__).parent.parent / "fixtures" / "demo_mri_lumbar.json" + ) + with open(fixture_path) as f: + data = json.load(f) + return PAFormResponse(**data) class AnalyzeRequest(BaseModel): diff --git a/apps/intelligence/src/policies/seed/__init__.py b/apps/intelligence/src/policies/seed/__init__.py index bd49d75..a63a6f7 100644 --- a/apps/intelligence/src/policies/seed/__init__.py +++ b/apps/intelligence/src/policies/seed/__init__.py @@ -1,4 +1,5 @@ """Seed policy loader.""" +from src.policies.registry import PolicyRegistry from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID from src.policies.seed.mri_brain import POLICY as MRI_BRAIN @@ -9,6 +10,6 @@ ALL_SEED_POLICIES = [MRI_LUMBAR, MRI_BRAIN, TKA, PHYSICAL_THERAPY, EPIDURAL_STEROID, ECHOCARDIOGRAM] -def register_all_seeds(registry) -> None: +def register_all_seeds(registry: PolicyRegistry) -> None: for policy in ALL_SEED_POLICIES: registry.register(policy) diff --git a/apps/intelligence/src/reasoning/confidence_scorer.py b/apps/intelligence/src/reasoning/confidence_scorer.py index 6067ad7..db8f82c 100644 --- a/apps/intelligence/src/reasoning/confidence_scorer.py +++ b/apps/intelligence/src/reasoning/confidence_scorer.py @@ -79,6 +79,7 @@ def calculate_confidence( final_score = max(SCORE_FLOOR, min(1.0, raw_score)) # Recommendation from score + recommendation: Literal["APPROVE", "MANUAL_REVIEW", "NEED_INFO"] if final_score >= 0.80: recommendation = "APPROVE" elif final_score >= 0.50: diff --git a/apps/intelligence/src/tests/test_confidence_scorer.py b/apps/intelligence/src/tests/test_confidence_scorer.py index fbbd515..98ec160 100644 --- a/apps/intelligence/src/tests/test_confidence_scorer.py +++ b/apps/intelligence/src/tests/test_confidence_scorer.py @@ -1,10 +1,14 @@ """Tests for weighted LCD compliance confidence scorer.""" +from typing import Literal + import pytest from src.models.pa_form import EvidenceItem from src.models.policy import PolicyCriterion, PolicyDefinition from src.reasoning.confidence_scorer import calculate_confidence +EvidenceStatus = Literal["MET", "NOT_MET", "UNCLEAR"] + def _make_criterion( id: str, @@ -22,7 +26,7 @@ def _make_criterion( def _make_evidence( criterion_id: str, - status: str, + status: EvidenceStatus, confidence: float = 0.9, ) -> EvidenceItem: return EvidenceItem( From 155478424926ad0701af5050116504cf1ffb257c Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 15:28:21 -0800 Subject: [PATCH 08/10] fix: unify criterion labels and derive confidence from algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align all demo data surfaces to use LCD_L34220_POLICY labels as the single source of truth for criterion names: - Pre-check, result criteria, and evidence sources now share identical labels (previously 4 different label sets) - Confidence changed from hardcoded 88% to 93%, computed by running the real scoring algorithm against realistic per-criterion evidence confidence values (0.88–0.98) - Fix circular import in intelligence seed/__init__.py (TYPE_CHECKING) - Add test: DEMO_PA_RESULT criteria labels must match LCD policy labels Co-Authored-By: Claude Opus 4.6 --- .../ehr/__tests__/useEhrDemoFlow.test.ts | 6 +++--- .../src/lib/__tests__/demoData.test.ts | 11 ++++++++-- apps/dashboard/src/lib/demoData.ts | 20 +++++++++---------- .../src/routes/__tests__/ehr-demo.test.tsx | 10 +++++----- .../src/policies/seed/__init__.py | 8 +++++++- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts index 77dc66a..bdefd87 100644 --- a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts +++ b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts @@ -76,7 +76,7 @@ describe('useEhrDemoFlow', () => { expect(result.current.state).toBe('reviewing'); expect(result.current.paRequest).not.toBeNull(); - expect(result.current.paRequest!.confidence).toBe(88); + expect(result.current.paRequest!.confidence).toBe(93); expect(result.current.paRequest!.criteria).toHaveLength(5); expect(result.current.paRequest!.provider).toBe('Dr. Kelli Smith'); }); @@ -268,7 +268,7 @@ describe('useEhrDemoFlow', () => { // Verify LCD L34220 criterion labels const labels = criteria.map((c) => c.label); expect(labels).toContain('Valid ICD-10 for lumbar pathology'); - expect(labels).toContain('4+ weeks conservative management documented'); - expect(labels).toContain('No recent duplicative CT/MRI'); + expect(labels).toContain('4+ weeks conservative management'); + expect(labels).toContain('No recent duplicative imaging'); }); }); diff --git a/apps/dashboard/src/lib/__tests__/demoData.test.ts b/apps/dashboard/src/lib/__tests__/demoData.test.ts index 6ab6b4e..13a5fc5 100644 --- a/apps/dashboard/src/lib/__tests__/demoData.test.ts +++ b/apps/dashboard/src/lib/__tests__/demoData.test.ts @@ -57,8 +57,8 @@ describe('demoData', () => { expect(DEMO_PA_RESULT.criteria.every((c) => c.met === true)).toBe(true); }); - it('DEMO_PA_RESULT_Has88PercentConfidence', () => { - expect(DEMO_PA_RESULT.confidence).toBe(88); + it('DEMO_PA_RESULT_Has93PercentConfidence', () => { + expect(DEMO_PA_RESULT.confidence).toBe(93); expect(DEMO_PA_RESULT.provider).toBe('Dr. Kelli Smith'); expect(DEMO_PA_RESULT.procedureCode).toBe('72148'); expect(DEMO_PA_RESULT.status).toBe('ready'); @@ -92,6 +92,13 @@ describe('demoData', () => { }); }); + it('DEMO_PA_RESULT_CriteriaLabelsMatchLCDPolicy', () => { + const policyLabels = new Set(LCD_L34220_POLICY.criteria.map((c) => c.label)); + DEMO_PA_RESULT.criteria.forEach((c) => { + expect(policyLabels.has(c.label)).toBe(true); + }); + }); + it('DEMO_PA_RESULT_SOURCES_HasEntryForEachCriterion', () => { DEMO_PA_RESULT.criteria.forEach((c) => { const source = DEMO_PA_RESULT_SOURCES[c.label]; diff --git a/apps/dashboard/src/lib/demoData.ts b/apps/dashboard/src/lib/demoData.ts index f53506c..1009138 100644 --- a/apps/dashboard/src/lib/demoData.ts +++ b/apps/dashboard/src/lib/demoData.ts @@ -119,19 +119,19 @@ export const DEMO_PA_RESULT_SOURCES: Record { vi.advanceTimersByTime(5000); }); - // Confidence score from DEMO_PA_RESULT - expect(screen.getByText('88%')).toBeInTheDocument(); + // Confidence score from DEMO_PA_RESULT (93% per scoring algorithm) + expect(screen.getByText('93%')).toBeInTheDocument(); // LCD L34220 criteria visible expect(screen.getByText('Valid ICD-10 for lumbar pathology')).toBeInTheDocument(); - expect(screen.getByText('4+ weeks conservative management documented')).toBeInTheDocument(); - expect(screen.getByText('No recent duplicative CT/MRI')).toBeInTheDocument(); + expect(screen.getByText('4+ weeks conservative management')).toBeInTheDocument(); + expect(screen.getByText('No recent duplicative imaging')).toBeInTheDocument(); // Submit button visible expect(screen.getByRole('button', { name: /submit to blue cross/i })).toBeInTheDocument(); @@ -253,7 +253,7 @@ describe('EhrDemoPage', () => { }); // 4. Reviewing state — confidence and evidence trail - expect(screen.getByText('88%')).toBeInTheDocument(); + expect(screen.getByText('93%')).toBeInTheDocument(); expect(screen.getByText('Valid ICD-10 for lumbar pathology')).toBeInTheDocument(); // 5. Submit diff --git a/apps/intelligence/src/policies/seed/__init__.py b/apps/intelligence/src/policies/seed/__init__.py index a63a6f7..58795bf 100644 --- a/apps/intelligence/src/policies/seed/__init__.py +++ b/apps/intelligence/src/policies/seed/__init__.py @@ -1,5 +1,8 @@ """Seed policy loader.""" -from src.policies.registry import PolicyRegistry +from __future__ import annotations + +from typing import TYPE_CHECKING + from src.policies.seed.echocardiogram import POLICY as ECHOCARDIOGRAM from src.policies.seed.epidural_steroid import POLICY as EPIDURAL_STEROID from src.policies.seed.mri_brain import POLICY as MRI_BRAIN @@ -7,6 +10,9 @@ from src.policies.seed.physical_therapy import POLICY as PHYSICAL_THERAPY from src.policies.seed.tka import POLICY as TKA +if TYPE_CHECKING: + from src.policies.registry import PolicyRegistry + ALL_SEED_POLICIES = [MRI_LUMBAR, MRI_BRAIN, TKA, PHYSICAL_THERAPY, EPIDURAL_STEROID, ECHOCARDIOGRAM] From 8d2352e365dc16f68ea11484e85924d1547e7059 Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 16:00:09 -0800 Subject: [PATCH 09/10] feat: derive pre-check criteria from chart data, all 5 met MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded DEMO_PRECHECK_CRITERIA with buildPreCheckCriteria() that scans DEMO_ENCOUNTER fields to extract evidence: - Red flag: regex extracts "progressive numbness" snippet from HPI - Conservative mgmt: regex extracts therapy duration from HPI - Clinical rationale: matches "warrant" keyword in assessment - Imaging: checks absence of prior studies All 5 criteria now met from existing chart data, matching the post-sign result (5/5 at 93%). Pre-check becomes the coaching moment: "we already checked — you're covered." Co-Authored-By: Claude Opus 4.6 --- .../ehr/__tests__/EncounterSidebar.test.tsx | 6 +- .../ehr/__tests__/PAReadinessWidget.test.tsx | 26 +-- .../ehr/__tests__/useEhrDemoFlow.test.ts | 3 +- .../src/lib/__tests__/demoData.test.ts | 22 ++- apps/dashboard/src/lib/demoData.ts | 178 +++++++++++------- 5 files changed, 144 insertions(+), 91 deletions(-) diff --git a/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx b/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx index 5bf1408..5c29a1e 100644 --- a/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx +++ b/apps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsx @@ -58,13 +58,13 @@ describe('EncounterSidebar', () => { }); it('EncounterSidebar_Flagged_ShowsPolicyCheckIndicator', () => { - render(); + render(); expect(screen.getByText('Policy Check')).toBeInTheDocument(); - expect(screen.getByText(/3\/5/)).toBeInTheDocument(); + expect(screen.getByText(/5\/5/)).toBeInTheDocument(); }); it('EncounterSidebar_Flagged_NoPAStages', () => { - render(); + render(); expect(screen.queryByText('Analyzing')).not.toBeInTheDocument(); expect(screen.queryByText('Submit')).not.toBeInTheDocument(); expect(screen.queryByText('Complete')).not.toBeInTheDocument(); diff --git a/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx b/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx index 3c4ca0e..80c55df 100644 --- a/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx +++ b/apps/dashboard/src/components/ehr/__tests__/PAReadinessWidget.test.tsx @@ -13,19 +13,21 @@ function buildCriteria(): PreCheckCriterion[] { }, { label: 'Red flag symptoms or progressive neurological deficit', - status: 'indeterminate', - gap: 'No neurological findings in chart — document in encounter note', + status: 'met', + evidence: 'Progressive numbness in left foot over past 3 weeks.', + source: 'CC / HPI', }, { label: '4+ weeks conservative management', status: 'met', - evidence: 'PT 2x/week x 8 weeks documented by referring provider', - source: 'Referral', + evidence: 'Failed 8 weeks of physical therapy and 6 weeks of NSAIDs.', + source: 'HPI / Orders', }, { label: 'Clinical rationale documented', - status: 'indeterminate', - gap: 'Rationale not yet documented — include in assessment/plan', + status: 'met', + evidence: 'Progressive neurological symptoms warrant advanced imaging.', + source: 'Assessment', }, { label: 'No recent duplicative imaging', @@ -86,8 +88,8 @@ describe('PAReadinessWidget', () => { expect(policyEl.className).toMatch(/font-mono/); expect(policyEl.className).toMatch(/uppercase/); - // Criteria count: 3 met out of 5 - expect(screen.getByText(/3\/5 criteria documented/)).toBeInTheDocument(); + // Criteria count: 5 met out of 5 + expect(screen.getByText(/5\/5 criteria documented/)).toBeInTheDocument(); }); it('PAReadinessWidget_Ready_ShowsAllCriteria', () => { @@ -139,7 +141,9 @@ describe('PAReadinessWidget', () => { }); it('PAReadinessWidget_Indeterminate_ShowsGap', () => { - const criteria = buildCriteria(); + const criteria: PreCheckCriterion[] = [ + { label: 'Test criterion', status: 'indeterminate', gap: 'Missing documentation' }, + ]; render( { />, ); - const gapEl = screen.getByText( - /No neurological findings in chart/, - ); + const gapEl = screen.getByText(/Missing documentation/); expect(gapEl).toBeInTheDocument(); expect(gapEl.className).toMatch(/text-amber-600/); }); diff --git a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts index bdefd87..79556bf 100644 --- a/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts +++ b/apps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.ts @@ -174,8 +174,7 @@ describe('useEhrDemoFlow', () => { expect(result.current.state).toBe('flagged'); const criteria = result.current.preCheckCriteria!; expect(criteria).toHaveLength(5); - expect(criteria.filter((c) => c.status === 'met')).toHaveLength(3); - expect(criteria.filter((c) => c.status === 'indeterminate')).toHaveLength(2); + expect(criteria.filter((c) => c.status === 'met')).toHaveLength(5); }); it('useEhrDemoFlow_Flagged_Sign_TransitionsToSigning', async () => { diff --git a/apps/dashboard/src/lib/__tests__/demoData.test.ts b/apps/dashboard/src/lib/__tests__/demoData.test.ts index 13a5fc5..95ce86a 100644 --- a/apps/dashboard/src/lib/__tests__/demoData.test.ts +++ b/apps/dashboard/src/lib/__tests__/demoData.test.ts @@ -68,21 +68,27 @@ describe('demoData', () => { expect(DEMO_PRECHECK_CRITERIA).toHaveLength(5); }); - it('DEMO_PRECHECK_CRITERIA_ThreeMetWithEvidenceAndSource', () => { + it('DEMO_PRECHECK_CRITERIA_AllMetWithEvidenceAndSource', () => { const met = DEMO_PRECHECK_CRITERIA.filter((c) => c.status === 'met'); - expect(met).toHaveLength(3); + expect(met).toHaveLength(5); met.forEach((c) => { expect(c.evidence).toBeTruthy(); expect(c.source).toBeTruthy(); }); }); - it('DEMO_PRECHECK_CRITERIA_TwoIndeterminateWithGap', () => { - const indeterminate = DEMO_PRECHECK_CRITERIA.filter((c) => c.status === 'indeterminate'); - expect(indeterminate).toHaveLength(2); - indeterminate.forEach((c) => { - expect(c.gap).toBeTruthy(); - }); + it('DEMO_PRECHECK_CRITERIA_DerivedFromChartData', () => { + // Red flag criterion sources evidence from DEMO_ENCOUNTER + const redFlag = DEMO_PRECHECK_CRITERIA.find((c) => c.label.includes('Red flag')); + expect(redFlag?.evidence).toContain('Progressive numbness'); + + // Conservative management sources from DEMO_ENCOUNTER HPI + const conservative = DEMO_PRECHECK_CRITERIA.find((c) => c.label.includes('conservative')); + expect(conservative?.evidence).toBeTruthy(); + + // Clinical rationale sources from DEMO_ENCOUNTER assessment + const rationale = DEMO_PRECHECK_CRITERIA.find((c) => c.label.includes('Clinical rationale')); + expect(rationale?.evidence).toContain('warrant'); }); it('DEMO_PRECHECK_CRITERIA_LabelsMatchLCDPolicy', () => { diff --git a/apps/dashboard/src/lib/demoData.ts b/apps/dashboard/src/lib/demoData.ts index 1009138..775ce76 100644 --- a/apps/dashboard/src/lib/demoData.ts +++ b/apps/dashboard/src/lib/demoData.ts @@ -73,74 +73,9 @@ export interface PreCheckCriterion { source?: string; } -/** - * Pre-check criteria for LCD L34220 evaluated against the patient chart - * BEFORE the encounter is signed. 3/5 met from existing chart data, - * 2/5 indeterminate until encounter note is complete. - */ -export const DEMO_PRECHECK_CRITERIA: PreCheckCriterion[] = [ - { - label: 'Valid ICD-10 for lumbar pathology', - status: 'met', - evidence: 'M54.5 (low back pain) on active problem list', - source: 'Problem List', - }, - { - label: 'Red flag symptoms or progressive neurological deficit', - status: 'indeterminate', - gap: 'Requires encounter documentation — sign encounter to evaluate', - }, - { - label: '4+ weeks conservative management', - status: 'met', - evidence: 'PT referral 01/10/2026, Naproxen 500mg prescribed 01/15/2026', - source: 'Orders / Medications', - }, - { - label: 'Clinical rationale documented', - status: 'indeterminate', - gap: 'Requires encounter documentation — sign encounter to evaluate', - }, - { - label: 'No recent duplicative imaging', - status: 'met', - evidence: 'No prior CT or MRI of lumbar spine in record', - source: 'Imaging History', - }, -]; - -/** - * Source/evidence mapping for the post-sign PA result criteria. - * Keyed by criterion label, provides extracted evidence and chart source - * for the evidence trail display in PAResultsPanel. - */ -export const DEMO_PA_RESULT_SOURCES: Record = { - 'Valid ICD-10 for lumbar pathology': { - evidence: 'M54.5, M54.51 — low back pain, lumbar radiculopathy left', - source: 'Assessment', - }, - 'Red flag symptoms or progressive neurological deficit': { - evidence: 'Progressive numbness in left foot over past 3 weeks', - source: 'HPI', - }, - '4+ weeks conservative management': { - evidence: '8 weeks PT (2x/week), naproxen 500mg BID x 6 weeks', - source: 'HPI', - }, - 'Clinical rationale documented': { - evidence: 'Persistent radiculopathy with progressive neuro symptoms despite conservative therapy', - source: 'Assessment / Plan', - }, - 'No recent duplicative imaging': { - evidence: 'No prior lumbar CT or MRI in patient record', - source: 'Imaging History', - }, -}; - /** * LCD L34220 policy requirements for MRI Lumbar Spine (CPT 72148). - * Shown in the pre-sign policy criteria modal so providers can review - * what documentation is needed before signing. + * Canonical label source — all demo surfaces reference these labels. */ export const LCD_L34220_POLICY = { policyId: 'LCD L34220', @@ -177,6 +112,117 @@ export const LCD_L34220_POLICY = { ], }; +/** + * Build pre-check criteria by scanning actual chart data. + * Evaluates LCD L34220 criteria against DEMO_ENCOUNTER, DEMO_ORDERS, + * and known patient chart state (problem list, imaging history). + */ +function buildPreCheckCriteria(): PreCheckCriterion[] { + const criteria: PreCheckCriterion[] = []; + + // 1. Valid ICD-10 — check known problem list + // In production this queries FHIR Condition resources + const diagnosisCode = 'M54.5'; + criteria.push({ + label: LCD_L34220_POLICY.criteria[0].label, + status: 'met', + evidence: `${diagnosisCode} (low back pain) on active problem list`, + source: 'Problem List', + }); + + // 2. Red flag / progressive neuro deficit — scan CC and HPI + const ccMentionsProgression = DEMO_ENCOUNTER.cc.toLowerCase().includes('worsening'); + const hpiMentionsDeficit = DEMO_ENCOUNTER.hpi.toLowerCase().includes('progressive numbness'); + const hasRedFlag = ccMentionsProgression || hpiMentionsDeficit; + const rawSnippet = hpiMentionsDeficit + ? DEMO_ENCOUNTER.hpi.match(/progressive numbness[^.]*\./i)?.[0] + : DEMO_ENCOUNTER.cc.match(/worsening[^,]*/i)?.[0]; + const redFlagSnippet = rawSnippet + ? rawSnippet.charAt(0).toUpperCase() + rawSnippet.slice(1) + : undefined; + criteria.push({ + label: LCD_L34220_POLICY.criteria[1].label, + status: hasRedFlag ? 'met' : 'indeterminate', + evidence: hasRedFlag ? redFlagSnippet?.trim() : undefined, + source: hasRedFlag ? 'CC / HPI' : undefined, + gap: hasRedFlag ? undefined : 'No red flag symptoms identified in chart', + }); + + // 3. Conservative management — scan HPI for therapy duration + orders + const hpiMentionsPT = /\d+\s*weeks?\s*(of\s+)?physical therapy/i.test(DEMO_ENCOUNTER.hpi); + const hpiMentionsNSAIDs = /\d+\s*weeks?\s*(of\s+)?NSAIDs/i.test(DEMO_ENCOUNTER.hpi); + const hasConservative = hpiMentionsPT || hpiMentionsNSAIDs; + const conservativeSnippet = DEMO_ENCOUNTER.hpi + .match(/failed\s+\d+\s+weeks?[^.]*\./i)?.[0]; + const orderEvidence = DEMO_ORDERS.length > 0 + ? `Active order: ${DEMO_ORDERS[0].name}` + : undefined; + criteria.push({ + label: LCD_L34220_POLICY.criteria[2].label, + status: hasConservative ? 'met' : 'indeterminate', + evidence: hasConservative + ? (conservativeSnippet?.trim() ?? orderEvidence) + : undefined, + source: hasConservative ? 'HPI / Orders' : undefined, + gap: hasConservative ? undefined : 'Conservative therapy documentation not found', + }); + + // 4. Clinical rationale — scan assessment for rationale language + const assessmentHasRationale = + DEMO_ENCOUNTER.assessment.toLowerCase().includes('warrant') || + DEMO_ENCOUNTER.assessment.toLowerCase().includes('medically necessary'); + criteria.push({ + label: LCD_L34220_POLICY.criteria[3].label, + status: assessmentHasRationale ? 'met' : 'indeterminate', + evidence: assessmentHasRationale ? DEMO_ENCOUNTER.assessment : undefined, + source: assessmentHasRationale ? 'Assessment' : undefined, + gap: assessmentHasRationale ? undefined : 'Clinical rationale not yet documented', + }); + + // 5. No duplicative imaging — check imaging history (empty = met) + // In production this queries FHIR ImagingStudy resources + const hasPriorImaging = false; // No prior lumbar imaging in demo chart + criteria.push({ + label: LCD_L34220_POLICY.criteria[4].label, + status: hasPriorImaging ? 'not-met' : 'met', + evidence: hasPriorImaging ? undefined : 'No prior CT or MRI of lumbar spine in record', + source: 'Imaging History', + gap: hasPriorImaging ? 'Prior lumbar imaging found in record' : undefined, + }); + + return criteria; +} + +export const DEMO_PRECHECK_CRITERIA: PreCheckCriterion[] = buildPreCheckCriteria(); + +/** + * Source/evidence mapping for the post-sign PA result criteria. + * Keyed by criterion label, provides extracted evidence and chart source + * for the evidence trail display in PAResultsPanel. + */ +export const DEMO_PA_RESULT_SOURCES: Record = { + 'Valid ICD-10 for lumbar pathology': { + evidence: 'M54.5, M54.51 — low back pain, lumbar radiculopathy left', + source: 'Assessment', + }, + 'Red flag symptoms or progressive neurological deficit': { + evidence: 'Progressive numbness in left foot over past 3 weeks', + source: 'HPI', + }, + '4+ weeks conservative management': { + evidence: '8 weeks PT (2x/week), naproxen 500mg BID x 6 weeks', + source: 'HPI', + }, + 'Clinical rationale documented': { + evidence: 'Persistent radiculopathy with progressive neuro symptoms despite conservative therapy', + source: 'Assessment / Plan', + }, + 'No recent duplicative imaging': { + evidence: 'No prior lumbar CT or MRI in patient record', + source: 'Imaging History', + }, +}; + /** * Pre-built PA result for the EHR demo flow. * Matches the Intelligence fixture (demo_mri_lumbar.json) with all 5 LCD L34220 From 8d312076c56dddb2f2378fb03c457d9b58317e87 Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 7 Mar 2026 17:44:23 -0800 Subject: [PATCH 10/10] feat: add documentation sub-flow and submission confirmation to EHR demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a "Document" action on indeterminate pre-check gaps that opens an AI suggestion box, lets the presenter edit and insert text into the encounter note, then save to chart — upgrading criteria from 4/5 to 5/5. Add a submission confirmation receipt dialog with PA details. Enrich the patient header with age, sex, insurance, member ID, and allergies. Co-Authored-By: Claude Opus 4.6 --- .../src/components/ehr/EhrHeader.tsx | 28 +++ .../src/components/ehr/EncounterNote.tsx | 185 +++++++++++++++++- .../src/components/ehr/PAReadinessWidget.tsx | 50 +++-- .../src/components/ehr/PAResultsPanel.tsx | 39 ++-- .../ehr/__tests__/PAReadinessWidget.test.tsx | 47 ++++- .../ehr/__tests__/useEhrDemoFlow.test.ts | 73 ++++++- .../src/components/ehr/useEhrDemoFlow.ts | 85 +++++++- .../src/lib/__tests__/demoData.test.ts | 46 +++-- apps/dashboard/src/lib/demoData.ts | 75 ++++--- apps/dashboard/src/routes/ehr-demo.tsx | 171 +++++++++++++++- package-lock.json | 110 +++-------- 11 files changed, 750 insertions(+), 159 deletions(-) diff --git a/apps/dashboard/src/components/ehr/EhrHeader.tsx b/apps/dashboard/src/components/ehr/EhrHeader.tsx index aeaf9b6..ec237f5 100644 --- a/apps/dashboard/src/components/ehr/EhrHeader.tsx +++ b/apps/dashboard/src/components/ehr/EhrHeader.tsx @@ -2,6 +2,11 @@ interface EhrHeaderPatient { name: string; dob: string; mrn: string; + age?: number; + sex?: 'M' | 'F'; + insurance?: string; + memberId?: string; + allergies?: string[]; } interface EncounterMeta { @@ -41,12 +46,35 @@ export function EhrHeader({ patient, encounterMeta }: EhrHeaderProps) {
{patient.name}
+ {patient.age != null && patient.sex && ( + {patient.age}{patient.sex} + )} DOB: {patient.dob} MRN: {patient.mrn} + {patient.insurance && ( + <> + | + + Ins: {patient.insurance} + + {patient.memberId && ( + ({patient.memberId}) + )} + + )} + {patient.allergies && patient.allergies.length > 0 && ( + <> + | + + Allergies:{' '} + {patient.allergies.join(', ')} + + + )}
diff --git a/apps/dashboard/src/components/ehr/EncounterNote.tsx b/apps/dashboard/src/components/ehr/EncounterNote.tsx index fd516e7..21b6766 100644 --- a/apps/dashboard/src/components/ehr/EncounterNote.tsx +++ b/apps/dashboard/src/components/ehr/EncounterNote.tsx @@ -1,3 +1,7 @@ +import { useRef, useEffect, useState } from 'react'; +import { Sparkles, Check, Loader2, CloudUpload } from 'lucide-react'; +import type { DocState } from './useEhrDemoFlow'; + export interface Encounter { cc: string; hpi: string; @@ -22,6 +26,16 @@ interface EncounterNoteProps { encounter: Encounter; vitals?: Vitals; orders?: Order[]; + hpiHighlight?: string; + assessmentHighlight?: string; + /** Documentation sub-flow state */ + docState?: DocState; + /** The suggested text to show in the AI suggestion box */ + suggestionText?: string; + /** Called when user clicks "Add to Note" — receives the (possibly edited) text */ + onInsertSuggestion?: (editedText: string) => void; + /** Called when user clicks "Save to Chart" after inserting */ + onSaveToChart?: () => void; } function SectionLabel({ children }: { children: React.ReactNode }) { @@ -32,7 +46,32 @@ function SectionLabel({ children }: { children: React.ReactNode }) { ); } -function EncounterSection({ label, text }: { label: string; text: string }) { +function EncounterSection({ + label, + text, + highlight, +}: { + label: string; + text: string; + highlight?: string; +}) { + if (highlight && text.includes(highlight)) { + const idx = text.indexOf(highlight); + const before = text.slice(0, idx); + const after = text.slice(idx + highlight.length); + return ( +
+ {label} +

+ {before} + + {highlight} + + {after} +

+
+ ); + } return (
{label} @@ -100,12 +139,135 @@ function OrdersCard({ orders }: { orders: Order[] }) { ); } +/** AI Suggestion box shown inside the HPI section during the documentation flow */ +function AISuggestionBox({ + text, + onInsert, +}: { + text: string; + onInsert: (editedText: string) => void; +}) { + const [value, setValue] = useState(text); + const textareaRef = useRef(null); + + // Auto-focus and select all text on mount so it feels interactive + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.focus(); + textareaRef.current.select(); + } + }, []); + + return ( +
+
+ + + AuthScript Suggestion + + + Edit before adding + +
+