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/__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/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 + +
+