| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936 |
- /* Generated from patient-service-desk-v3.js for direct file:// opening. */
- (() => {
- // docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
- function createInitialState({
- activePatientId = "patient-zhang-san",
- demoNow = "2026-07-03T09:00:00+08:00"
- } = {}) {
- return {
- activeTab: "home",
- activePatientId,
- demoNow,
- appliedProjectionFixtureIds: [],
- activeAgentTaskProjection: null,
- patientWorkItems: [],
- journeyProjections: [],
- reportSourceProjections: {},
- reportFeedback: [],
- reportFilters: { family: "ALL", status: "ALL" },
- resultSnapshotProjections: {},
- commandProjections: {},
- messages: [],
- consents: {},
- consentScopeBindings: {},
- overlays: {
- commandId: null,
- consentId: null,
- consentScope: null,
- reportId: null,
- reportView: null,
- reportUnavailableReason: null,
- reportExpanded: false,
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null,
- appointmentId: null,
- journeyView: null,
- scenarioPanelOpen: false
- }
- };
- }
- var deepFreeze = (value) => {
- if (value && typeof value === "object" && !Object.isFrozen(value)) {
- Object.freeze(value);
- Object.values(value).forEach(deepFreeze);
- }
- return value;
- };
- var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
- var reportIdPattern = /^[^\s\u0000-\u001F\u007F]{1,128}$/;
- var reportHashPattern = /^[^\s\u0000-\u001F\u007F]{1,256}$/;
- var reportSourceStatuses = /* @__PURE__ */ new Set(["FINAL", "PRELIMINARY"]);
- var reportFamilies = /* @__PURE__ */ new Set(["LAB", "EXAM"]);
- var reportTypes = /* @__PURE__ */ new Set(["BLOOD_ROUTINE", "IMAGING"]);
- var reportVerificationStatuses = /* @__PURE__ */ new Set(["VERIFIED", "NEED_REVIEW"]);
- var bloodRoutineReportFixture = deepFreeze({
- reportId: "report-blood-20260702",
- family: "LAB",
- reportType: "BLOOD_ROUTINE",
- title: "\u8840\u5E38\u89C4",
- hospitalName: "\u7A7A\u6D77\u533B\u9662",
- departmentName: "\u68C0\u9A8C\u79D1",
- issuedAt: "2026-07-03 09:30",
- sourceStatus: "FINAL",
- interpretationStatus: "NOT_REQUESTED",
- verificationStatus: "VERIFIED",
- sourceVersion: 1,
- sourceHash: "sha256:blood-v1",
- readStatus: "UNREAD",
- metrics: [
- {
- code: "WBC",
- name: "\u767D\u7EC6\u80DE\u8BA1\u6570",
- value: 11.2,
- unit: "10^9/L",
- referenceRange: "3.5\u20139.5",
- sourceFlag: "HIGH"
- }
- ]
- });
- var registrationDemoText = "\u6211\u60F3\u6302\u660E\u5929\u795E\u7ECF\u5185\u79D1\u674E\u660E\u4E3B\u4EFB\u7684\u53F7";
- var registrationResultRef = "result-slot-001";
- var registrationResultVersion = 1;
- var registrationItems = deepFreeze([
- {
- candidateId: "candidate-slot-0930",
- visitDate: "2026-07-03",
- timePeriod: "\u4E0A\u5348",
- visitTime: "09:30",
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- remainingSlots: 3,
- fee: "30\u5143"
- },
- {
- candidateId: "candidate-slot-1030",
- visitDate: "2026-07-03",
- timePeriod: "\u4E0A\u5348",
- visitTime: "10:30",
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- remainingSlots: 1,
- fee: "30\u5143"
- },
- {
- candidateId: "candidate-slot-1430",
- visitDate: "2026-07-03",
- timePeriod: "\u4E0B\u5348",
- visitTime: "14:30",
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- remainingSlots: 5,
- fee: "30\u5143"
- }
- ]);
- var registrationResultText = [
- `\u5DF2\u4E3A\u4F60\u627E\u5230 ${registrationItems[0].visitDate} ${registrationItems[0].departmentName}${registrationItems[0].doctorName}${registrationItems[0].doctorTitle}\u7684 ${registrationItems.length} \u4E2A\u53F7\u6E90\uFF1A`,
- ...registrationItems.map((item) => `${item.visitDate} ${item.timePeriod} ${item.visitTime}\uFF0C${item.departmentName}\uFF0C${item.doctorName}${item.doctorTitle}\uFF0C\u4F59\u53F7 ${item.remainingSlots}\uFF0C\u6302\u53F7\u8D39 ${item.fee}\uFF1B`),
- "\u8BF7\u9009\u62E9\u4E00\u4E2A\u53F7\u6E90\u3002"
- ].join("");
- var nextMessageId = (messages, role) => {
- const usedIds = new Set(
- messages.filter((message) => message && typeof message === "object").map(({ id }) => id)
- );
- let sequence = messages.length + 1;
- while (usedIds.has(`message-${role}-${sequence}`)) {
- sequence += 1;
- }
- return `message-${role}-${sequence}`;
- };
- var simulateRegistrationQueryBackendFixture = (interaction) => {
- if (interaction?.text !== registrationDemoText) {
- return {
- type: "NO_RESULT",
- text: interaction?.text ? "\u6682\u65F6\u6CA1\u6709\u627E\u5230\u5339\u914D\u7ED3\u679C\u3002\u4F60\u53EF\u4EE5\u6362\u4E2A\u8BF4\u6CD5\uFF0C\u8BD5\u8BD5\u667A\u80FD\u5BFC\u8BCA\uFF0C\u6216\u8F6C\u4EBA\u5DE5\u670D\u52A1\u3002" : "\u8BF7\u8F93\u5165\u60F3\u529E\u7406\u7684\u5C31\u533B\u670D\u52A1\u3002\u4F60\u4E5F\u53EF\u4EE5\u8BD5\u8BD5\u667A\u80FD\u5BFC\u8BCA\u6216\u4EBA\u5DE5\u670D\u52A1\u3002"
- };
- }
- return {
- type: "REGISTRATION_CANDIDATES",
- task: {
- taskId: "agent-task-registration-001",
- type: "REGISTRATION",
- stage: "CANDIDATE_SELECTION",
- constraints: {
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- visitDate: "2026-07-03"
- },
- missingFields: [],
- activeResultRef: registrationResultRef
- },
- result: {
- resultRef: registrationResultRef,
- resultVersion: registrationResultVersion,
- expiresAt: "2026-07-03T10:00:00+08:00",
- text: registrationResultText,
- items: registrationItems.map((item) => ({ ...item }))
- }
- };
- };
- var applyRegistrationQueryResponseFixture = (state, interaction, fixture) => {
- const messages = Array.isArray(state.messages) ? state.messages : [];
- const userMessage = interaction.text ? [{
- id: nextMessageId(messages, "user"),
- role: "user",
- text: interaction.text
- }] : [];
- const assistantMessage = {
- id: nextMessageId([...messages, ...userMessage], "assistant"),
- role: "assistant",
- text: fixture.text ?? fixture.result?.text,
- ...fixture.result?.resultRef ? { resultRef: fixture.result.resultRef } : {}
- };
- if (fixture.type !== "REGISTRATION_CANDIDATES") {
- return {
- ...state,
- activeTab: "assistant",
- messages: [...messages, ...userMessage, assistantMessage]
- };
- }
- return {
- ...state,
- activeTab: "assistant",
- activeAgentTaskProjection: fixture.task,
- resultSnapshotProjections: {
- ...state.resultSnapshotProjections ?? {},
- [fixture.result.resultRef]: fixture.result
- },
- messages: [...messages, ...userMessage, assistantMessage]
- };
- };
- function submitInteraction(state, text) {
- if (!state || typeof state !== "object" || Array.isArray(state)) {
- return state;
- }
- const normalizedText = typeof text === "string" ? text.trim() : "";
- const interaction = {
- interactionType: "USER_MESSAGE",
- text: normalizedText
- };
- const responseFixture = simulateRegistrationQueryBackendFixture(
- interaction
- );
- return applyRegistrationQueryResponseFixture(
- state,
- interaction,
- responseFixture
- );
- }
- function buildCandidateSelectionInteraction(state, candidateSelection, now) {
- const task = state?.activeAgentTaskProjection;
- const resultRef = task?.activeResultRef;
- const result = typeof resultRef === "string" ? state?.resultSnapshotProjections?.[resultRef] : null;
- const normalizedSelection = typeof candidateSelection === "string" ? candidateSelection.trim() : candidateSelection;
- const candidateId = normalizedSelection === "\u9009\u7B2C\u4E00\u4E2A" ? result?.items?.[0]?.candidateId : normalizedSelection;
- const expiresAt = Date.parse(result?.expiresAt);
- const evaluatedAt = Date.parse(now ?? state?.demoNow);
- const isExpired = !Number.isFinite(expiresAt) || !Number.isFinite(evaluatedAt) || expiresAt <= evaluatedAt;
- const hasCandidate = Array.isArray(result?.items) && result.items.some(
- (item) => item?.candidateId === candidateId
- );
- if (task?.type !== "REGISTRATION" || task.stage !== "CANDIDATE_SELECTION" || result?.resultRef !== resultRef || !Number.isInteger(result?.resultVersion) || result.resultVersion <= 0 || isExpired || !hasCandidate) {
- return null;
- }
- return {
- interactionType: "SELECT_CANDIDATE",
- resultRef,
- resultVersion: result.resultVersion,
- candidateId
- };
- }
- function applyCandidateSelectionResponseFixture(state, fixture) {
- if (!state || typeof state !== "object" || !fixture?.id) {
- return state;
- }
- const appliedIds = Array.isArray(state.appliedProjectionFixtureIds) ? state.appliedProjectionFixtureIds : [];
- if (appliedIds.includes(fixture.id)) {
- return state;
- }
- const task = state?.activeAgentTaskProjection;
- const result = task?.activeResultRef ? state?.resultSnapshotProjections?.[task.activeResultRef] : null;
- const isActiveSelection = task?.type === "REGISTRATION" && task.stage === "CANDIDATE_SELECTION" && result?.resultRef === task.activeResultRef;
- if (!isActiveSelection) {
- return state;
- }
- const canonicalErrors = /* @__PURE__ */ new Set([
- "RESULT_VERSION_CONFLICT",
- "RESULT_EXPIRED",
- "SLOT_UNAVAILABLE"
- ]);
- const validation = fixture.validation ?? {};
- const derivedError = validation.versionMatched === false ? "RESULT_VERSION_CONFLICT" : validation.notExpired === false ? "RESULT_EXPIRED" : validation.realtimeAvailable === false || validation.l1LockSucceeded === false ? "SLOT_UNAVAILABLE" : null;
- const baseState = {
- ...state,
- appliedProjectionFixtureIds: [...appliedIds, fixture.id]
- };
- const evaluatedAt = Date.parse(state.demoNow);
- const snapshotExpiresAt = Date.parse(result.expiresAt);
- const snapshotNotExpired = Number.isFinite(evaluatedAt) && Number.isFinite(snapshotExpiresAt) && snapshotExpiresAt > evaluatedAt;
- const lockExpiresAt = Date.parse(fixture.lock?.expiresAt);
- const lockIdValid = typeof fixture.lock?.lockId === "string" && fixture.lock.lockId.trim().length > 0;
- const lockExpiryValid = Number.isFinite(evaluatedAt) && Number.isFinite(lockExpiresAt) && lockExpiresAt > evaluatedAt;
- const candidateExists = Array.isArray(result?.items) && result.items.some(
- (item) => item?.candidateId === fixture.candidateId
- );
- const validationPassed = [
- "tenantMatched",
- "patientMatched",
- "sessionMatched",
- "versionMatched",
- "notExpired",
- "realtimeAvailable",
- "l1LockSucceeded"
- ].every((key) => validation[key] === true);
- const successIsValid = fixture.outcome === "SUCCESS" && fixture.resultRef === result.resultRef && fixture.resultVersion === result.resultVersion && candidateExists && validationPassed && snapshotNotExpired && lockIdValid && lockExpiryValid;
- if (!successIsValid) {
- const errorCode = canonicalErrors.has(fixture.errorCode) ? fixture.errorCode : derivedError ?? (!snapshotNotExpired ? "RESULT_EXPIRED" : !lockIdValid && fixture.outcome === "SUCCESS" ? "SLOT_UNAVAILABLE" : !lockExpiryValid && fixture.outcome === "SUCCESS" ? "RESULT_EXPIRED" : "UNKNOWN_SELECTION_ERROR");
- const unavailableAdvice = errorCode === "SLOT_UNAVAILABLE" ? " \u5F53\u524D\u53F7\u6E90\u5DF2\u65E0\u53F7\uFF0C\u53EF\u67E5\u770B\u5176\u4ED6\u65F6\u6BB5\u6216\u533B\u751F\u3002" : "";
- const messages2 = Array.isArray(state.messages) ? state.messages : [];
- return {
- ...baseState,
- activeAgentTaskProjection: {
- ...task,
- stage: "REQUERY_REQUIRED",
- activeResultRef: null
- },
- messages: [
- ...messages2,
- {
- id: nextMessageId(messages2, "assistant"),
- role: "assistant",
- text: `\u7ED3\u679C\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u3002${unavailableAdvice}`
- }
- ]
- };
- }
- const messages = Array.isArray(state.messages) ? state.messages : [];
- return {
- ...baseState,
- activeAgentTaskProjection: {
- ...task,
- stage: "AWAITING_REGISTRATION_CONFIRMATION",
- selectedCandidateId: fixture.candidateId,
- lock: { ...fixture.lock }
- },
- messages: [
- ...messages,
- {
- id: nextMessageId(messages, "assistant"),
- role: "assistant",
- text: "\u9501\u53F7\u6210\u529F\uFF0C\u8BF7\u5728\u9501\u5B9A\u65F6\u95F4\u5185\u5B8C\u6210\u4E8C\u6B21\u786E\u8BA4\u540E\u6302\u53F7\u3002"
- }
- ]
- };
- }
- var commandStatuses = /* @__PURE__ */ new Set([
- "PREPARED",
- "AWAITING_CONFIRMATION",
- "EXECUTING",
- "SUCCEEDED",
- "FAILED",
- "EXPIRED",
- "UNKNOWN",
- "REJECTED"
- ]);
- var commandTransitions = {
- L1: {
- PREPARED: /* @__PURE__ */ new Set(["EXECUTING"]),
- EXECUTING: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED", "UNKNOWN"]),
- UNKNOWN: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED"])
- },
- L2: {
- PREPARED: /* @__PURE__ */ new Set(["AWAITING_CONFIRMATION"]),
- AWAITING_CONFIRMATION: /* @__PURE__ */ new Set([
- "EXECUTING",
- "REJECTED",
- "EXPIRED"
- ]),
- EXECUTING: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED", "UNKNOWN"]),
- UNKNOWN: /* @__PURE__ */ new Set(["SUCCEEDED", "FAILED"])
- }
- };
- var initialCommandStatuses = {
- L1: /* @__PURE__ */ new Set(["PREPARED", "UNKNOWN"]),
- L2: /* @__PURE__ */ new Set(["PREPARED", "AWAITING_CONFIRMATION", "UNKNOWN"])
- };
- var commandFixtureError = (message) => {
- throw new Error(`Command projection fixture invalid: ${message}`);
- };
- var legacyAppointmentResult = {
- type: "APPOINTMENT_CREATED",
- eventId: "appointment-created-001",
- appointmentId: "appointment-001",
- patientId: "patient-zhang-san",
- summary: {
- visitDate: "2026-07-03",
- visitTime: "09:30",
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- fee: "30\u5143"
- }
- };
- var applyAppointmentCreatedProjection = (state, fixture) => {
- const result = fixture.result ?? (fixture.resultFixtureId === "appointment-created-001" ? legacyAppointmentResult : null);
- if (!result || result.type !== "APPOINTMENT_CREATED") {
- return state;
- }
- const eventId = result.eventId ?? fixture.resultFixtureId;
- if (typeof eventId !== "string" || !eventId || typeof result.appointmentId !== "string" || !result.appointmentId) {
- commandFixtureError("appointment result identifiers are required");
- }
- if (state.appliedProjectionFixtureIds.includes(eventId)) {
- return state;
- }
- const summary = result.summary ?? {};
- const doctor = `${summary.doctorName ?? ""}${summary.doctorTitle ?? ""}`;
- return {
- ...state,
- appliedProjectionFixtureIds: [
- ...state.appliedProjectionFixtureIds,
- eventId
- ],
- patientWorkItems: [
- ...state.patientWorkItems,
- {
- id: `work-item-${result.appointmentId}`,
- appointmentId: result.appointmentId,
- patientId: result.patientId,
- type: "APPOINTMENT",
- status: "UPCOMING",
- stage: "WAITING_ARRIVAL",
- priority: 80,
- title: `${summary.departmentName ?? "\u95E8\u8BCA"}\u9884\u7EA6\u6210\u529F`,
- description: `${summary.visitDate ?? ""} ${summary.visitTime ?? ""} \xB7 ${doctor} \xB7 ${summary.fee ?? ""}`,
- fee: summary.fee,
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- }
- ],
- journeyProjections: [
- ...state.journeyProjections,
- {
- id: `journey-${result.appointmentId}`,
- stage: "WAITING_ARRIVAL",
- title: "\u5F85\u5230\u9662",
- appointmentId: result.appointmentId,
- patientId: result.patientId
- }
- ]
- };
- };
- function applyCommandProjectionFixture(state, fixture) {
- if (!state || typeof state !== "object" || Array.isArray(state)) {
- commandFixtureError("state is required");
- }
- if (!fixture || typeof fixture !== "object" || Array.isArray(fixture)) {
- commandFixtureError("fixture is required");
- }
- const { id, commandId, status } = fixture;
- if (typeof id !== "string" || !id.trim()) {
- commandFixtureError("id is required");
- }
- if (typeof commandId !== "string" || !commandId.trim()) {
- commandFixtureError("commandId is required");
- }
- if (status === "COMPLETED") {
- commandFixtureError("COMPLETED is not a Command status");
- }
- if (!commandStatuses.has(status)) {
- commandFixtureError(`unknown status ${String(status)}`);
- }
- const appliedIds = Array.isArray(state.appliedProjectionFixtureIds) ? state.appliedProjectionFixtureIds : [];
- if (appliedIds.includes(id)) {
- return state;
- }
- const current = state.commandProjections?.[commandId];
- const riskLevel = fixture.riskLevel ?? current?.riskLevel ?? "L2";
- if (riskLevel !== "L1" && riskLevel !== "L2") {
- commandFixtureError(`unknown riskLevel ${String(riskLevel)}`);
- }
- if (current && current.riskLevel !== riskLevel) {
- commandFixtureError("riskLevel cannot change");
- }
- const version = fixture.version ?? current?.version;
- if (version !== void 0 && (!Number.isInteger(version) || version <= 0)) {
- commandFixtureError("version must be a positive integer");
- }
- if (!current && !initialCommandStatuses[riskLevel].has(status)) {
- commandFixtureError(
- `illegal initial ${riskLevel} status ${status}`
- );
- }
- if (current) {
- const isFinalBusinessSnapshot = riskLevel === "L2" && current.status === "AWAITING_CONFIRMATION" && status === "SUCCEEDED" && fixture.resultFixtureId === "appointment-created-001" && fixture.result === void 0 && fixture.version === void 0;
- const transitionAllowed = commandTransitions[riskLevel][current.status]?.has(status);
- if (!isFinalBusinessSnapshot && (!Number.isInteger(fixture.version) || !Number.isInteger(current.version) || fixture.version <= current.version)) {
- commandFixtureError("version must explicitly increase");
- }
- if (!transitionAllowed && !isFinalBusinessSnapshot) {
- commandFixtureError(
- `illegal ${riskLevel} transition ${current.status} -> ${status}`
- );
- }
- }
- const projection = {
- ...current,
- commandId,
- status,
- riskLevel,
- ...version === void 0 ? {} : { version },
- ...fixture.details === void 0 ? {} : { details: structuredClone(fixture.details) },
- ...fixture.resultFixtureId === void 0 ? {} : { resultFixtureId: fixture.resultFixtureId },
- ...fixture.result === void 0 ? {} : { result: structuredClone(fixture.result) }
- };
- let nextState = {
- ...state,
- appliedProjectionFixtureIds: [...appliedIds, id],
- commandProjections: {
- ...state.commandProjections ?? {},
- [commandId]: projection
- },
- overlays: {
- ...state.overlays,
- commandId
- }
- };
- if (status === "SUCCEEDED" && (fixture.result?.type === "APPOINTMENT_CREATED" || fixture.resultFixtureId === "appointment-created-001")) {
- nextState = applyAppointmentCreatedProjection(nextState, fixture);
- }
- return nextState;
- }
- function buildCommandConfirmationRequest(state, commandId, { channel, idempotencyKey } = {}) {
- const command = state?.commandProjections?.[commandId];
- if (!command) {
- throw new Error("Command \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u786E\u8BA4");
- }
- if (command.status === "UNKNOWN") {
- throw new Error("\u7ED3\u679C\u786E\u8BA4\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u64CD\u4F5C");
- }
- if (command.status === "EXPIRED") {
- throw new Error("Command \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u53F7\u6E90");
- }
- if (command.status !== "AWAITING_CONFIRMATION") {
- throw new Error(`\u5F53\u524D\u72B6\u6001 ${command.status} \u4E0D\u5141\u8BB8\u91CD\u590D\u786E\u8BA4`);
- }
- if (!Number.isInteger(command.version) || command.version <= 0) {
- throw new Error("Command version \u65E0\u6548");
- }
- if (channel !== "CARD") {
- throw new Error("\u786E\u8BA4\u6E20\u9053\u4E0D\u53D7\u652F\u6301");
- }
- if (typeof idempotencyKey !== "string" || !idempotencyKey.trim()) {
- throw new Error("\u5E42\u7B49\u952E\u4E0D\u80FD\u4E3A\u7A7A");
- }
- return {
- path: `/api/v2/commands/${encodeURIComponent(commandId)}/confirm`,
- method: "POST",
- commandId,
- body: {
- commandVersion: command.version,
- confirmation: {
- channel,
- explicit: true
- }
- },
- headers: {
- "X-Emoon-Idempotency-Key": idempotencyKey
- }
- };
- }
- function buildCommandRejectionRequest(state, commandId, { reason, idempotencyKey } = {}) {
- const command = state?.commandProjections?.[commandId];
- if (!command) {
- throw new Error("Command \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u62D2\u7EDD");
- }
- if (command.status !== "AWAITING_CONFIRMATION") {
- throw new Error(`\u5F53\u524D\u72B6\u6001 ${command.status} \u4E0D\u5141\u8BB8\u62D2\u7EDD`);
- }
- if (!Number.isInteger(command.version) || command.version <= 0) {
- throw new Error("Command version \u65E0\u6548");
- }
- if (typeof idempotencyKey !== "string" || !idempotencyKey.trim()) {
- throw new Error("\u5E42\u7B49\u952E\u4E0D\u80FD\u4E3A\u7A7A");
- }
- return {
- path: `/api/v2/commands/${encodeURIComponent(commandId)}/reject`,
- method: "POST",
- commandId,
- body: {
- commandVersion: command.version,
- ...typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : {}
- },
- headers: {
- "X-Emoon-Idempotency-Key": idempotencyKey
- }
- };
- }
- var statusRank = {
- ACTION_REQUIRED: 0,
- IN_PROGRESS: 1,
- UPCOMING: 2,
- COMPLETED: 3
- };
- var reportInterpretationProjectionFixture = deepFreeze({
- resultRef: "result-report-interpretation-001",
- resultVersion: 1,
- interpretation: {
- headline: "3 \u9879\u6307\u6807\u8F7B\u5EA6\u5F02\u5E38",
- summary: "\u8FD9\u4EFD\u62A5\u544A\u6709 3 \u9879\u6307\u6807\u8D85\u51FA\u53C2\u8003\u8303\u56F4\uFF0C\u53D8\u5316\u5E45\u5EA6\u8F83\u5C0F\uFF0C\u9700\u8981\u7ED3\u5408\u8FD1\u671F\u75C7\u72B6\u548C\u75C5\u53F2\u7531\u533B\u751F\u7EFC\u5408\u5224\u65AD\u3002",
- safety: "\u672C\u89E3\u91CA\u4EC5\u7528\u4E8E\u5E2E\u52A9\u7406\u89E3\u62A5\u544A\u6307\u6807\uFF0C\u4E0D\u7528\u4E8E\u5224\u65AD\u75BE\u75C5\u6216\u66FF\u4EE3\u533B\u751F\u610F\u89C1\u3002\u5982\u6709\u660E\u663E\u4E0D\u9002\uFF0C\u8BF7\u53CA\u65F6\u8054\u7CFB\u533B\u52A1\u4EBA\u5458\u3002",
- highlights: [
- { name: "\u767D\u7EC6\u80DE\u8BA1\u6570", value: "11.2", flag: "\u504F\u9AD8" },
- { name: "\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B", value: "78%", flag: "\u504F\u9AD8" },
- { name: "\u6DCB\u5DF4\u7EC6\u80DE\u6BD4\u4F8B", value: "18%", flag: "\u504F\u4F4E" }
- ],
- explanations: [
- {
- indicator: "\u767D\u7EC6\u80DE 11.2 \xD710\u2079/L",
- explanation: "\u8F7B\u5EA6\u9AD8\u4E8E\u62A5\u544A\u6240\u793A\u53C2\u8003\u8303\u56F4\uFF0C\u5355\u9879\u53D8\u5316\u4E0D\u80FD\u72EC\u7ACB\u8BF4\u660E\u5177\u4F53\u539F\u56E0\u3002",
- commonFactors: "\u8FD1\u671F\u611F\u67D3\u3001\u7761\u7720\u4E0D\u8DB3\u3001\u8FD0\u52A8\u6216\u60C5\u7EEA\u7D27\u5F20\u7B49\u3002"
- },
- {
- indicator: "\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B 78%",
- explanation: "\u6BD4\u4F8B\u8F7B\u5EA6\u5347\u9AD8\uFF0C\u9700\u7ED3\u5408\u767D\u7EC6\u80DE\u603B\u6570\u548C\u5F53\u524D\u75C7\u72B6\u5224\u65AD\u3002",
- commonFactors: "\u7EC6\u83CC\u611F\u67D3\u3001\u6025\u6027\u708E\u75C7\u6216\u751F\u7406\u5E94\u6FC0\u7B49\u3002"
- },
- {
- indicator: "\u6DCB\u5DF4\u7EC6\u80DE\u6BD4\u4F8B 18%",
- explanation: "\u6BD4\u4F8B\u8F7B\u5EA6\u504F\u4F4E\uFF0C\u53EF\u80FD\u4E0E\u4E2D\u6027\u7C92\u7EC6\u80DE\u6BD4\u4F8B\u5347\u9AD8\u540C\u65F6\u51FA\u73B0\u3002",
- commonFactors: "\u611F\u67D3\u6062\u590D\u671F\u3001\u538B\u529B\u6216\u4E2A\u4F53\u77ED\u671F\u6CE2\u52A8\u7B49\u3002"
- }
- ],
- canDo: [
- "\u67E5\u770B\u5E76\u4FDD\u5B58\u533B\u9662\u539F\u59CB\u62A5\u544A\u3002",
- "\u7ED3\u5408\u8FD1\u671F\u75C7\u72B6\u548C\u65E2\u5F80\u60C5\u51B5\uFF0C\u5411\u5F00\u5355\u533B\u751F\u54A8\u8BE2\u3002",
- "\u5982\u6709\u660E\u663E\u4E0D\u9002\uFF0C\u53CA\u65F6\u8054\u7CFB\u533B\u52A1\u4EBA\u5458\u3002"
- ],
- evidence: [
- "\u533B\u9662\u539F\u59CB\u62A5\u544A\u4E2D\u7684\u6307\u6807\u7ED3\u679C\u3001\u5355\u4F4D\u3001\u53C2\u8003\u8303\u56F4\u548C\u5F02\u5E38\u6807\u8BB0\u3002",
- "\u533B\u9662\u5DF2\u5BA1\u6838\u7684\u7EC8\u7248\u62A5\u544A\u3002"
- ]
- }
- });
- var reportConsentId = (reportId) => `consent-ai-${reportId}`;
- var encodeReportId = (reportId) => String(reportId).replace(
- /[^A-Za-z0-9._-]/g,
- (character) => `~${character.charCodeAt(0).toString(16).padStart(4, "0")}`
- );
- var reportResultSnapshotId = (reportId) => `result-report-interpretation-${encodeReportId(reportId)}`;
- var reportFeedbackCategories = /* @__PURE__ */ new Set([
- "HELPFUL",
- "METRIC_INACCURATE",
- "HARD_TO_UNDERSTAND",
- "DIFFERS_FROM_DOCTOR",
- "OTHER"
- ]);
- var readReportIssuedFixture = (fixture) => {
- if (fixture.payload !== void 0 && !isRecord(fixture.payload)) {
- return null;
- }
- const payload = fixture.payload ?? {};
- const isExamination = payload.family === "EXAM";
- const report = {
- ...bloodRoutineReportFixture,
- ...payload,
- interpretationStatus: "NOT_REQUESTED",
- metrics: Array.isArray(payload.metrics) ? payload.metrics : isExamination ? [] : bloodRoutineReportFixture.metrics
- };
- if (typeof report.reportId !== "string" || !reportIdPattern.test(report.reportId) || !Number.isInteger(report.sourceVersion) || report.sourceVersion <= 0 || typeof report.sourceHash !== "string" || !reportHashPattern.test(report.sourceHash) || !reportSourceStatuses.has(report.sourceStatus) || !reportFamilies.has(report.family) || !reportTypes.has(report.reportType) || !reportVerificationStatuses.has(report.verificationStatus) || report.family === "LAB" && report.reportType !== "BLOOD_ROUTINE" || report.family === "EXAM" && report.reportType !== "IMAGING") {
- return null;
- }
- return deepFreeze(structuredClone(report));
- };
- var hasValidReportConsent = (state, reportId) => {
- if (typeof reportId !== "string" || reportId.length === 0 || !isRecord(state?.consents) || !isRecord(state?.consentScopeBindings)) {
- return false;
- }
- const consentId = reportConsentId(reportId);
- const consent = Object.hasOwn(state.consents, consentId) ? state.consents[consentId] : null;
- const binding = Object.hasOwn(state.consentScopeBindings, consentId) ? state.consentScopeBindings[consentId] : null;
- const expiresAt = Date.parse(consent?.expiresAt);
- const evaluatedAt = Date.parse(state?.demoNow);
- return consent?.id === consentId && consent.status === "GRANTED" && Number.isFinite(expiresAt) && Number.isFinite(evaluatedAt) && expiresAt > evaluatedAt && binding?.patientId === state?.activePatientId && binding?.reportId === reportId;
- };
- var selectValidReportResult = (state, reportId) => {
- if (typeof reportId !== "string" || reportId.length === 0) {
- return null;
- }
- const resultRef = reportResultSnapshotId(reportId);
- const reportSources = state?.reportSourceProjections;
- const results = state?.resultSnapshotProjections;
- const report = isRecord(reportSources) && Object.hasOwn(reportSources, reportId) ? reportSources[reportId] : null;
- const result = isRecord(results) && Object.hasOwn(results, resultRef) ? results[resultRef] : null;
- const hasScopedTask = Array.isArray(state?.patientWorkItems) && state.patientWorkItems.some((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId);
- return isRecord(report) && report.reportId === reportId && report.interpretationStatus === "READY" && hasScopedTask && hasValidReportConsent(state, reportId) && evaluateReportInterpretationGate(state, reportId).allowed && isRecord(result) && result.resultRef === resultRef && result.reportId === reportId && result.sourceVersion === report.sourceVersion && result.sourceHash === report.sourceHash && Number.isInteger(result.resultVersion) && result.resultVersion > 0 && result.valid === true && isRecord(result.interpretation) ? result : null;
- };
- function selectHomeTasks(state) {
- const patientWorkItems = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.filter(
- (item) => item !== null && typeof item === "object" && !Array.isArray(item)
- ) : [];
- const priorityOf = ({ priority }) => Number.isFinite(priority) ? priority : 0;
- const rankOf = ({ status }) => typeof status === "string" && Object.hasOwn(statusRank, status) ? statusRank[status] : 4;
- return patientWorkItems.sort((left, right) => {
- const statusDelta = rankOf(left) - rankOf(right);
- return statusDelta || priorityOf(right) - priorityOf(left);
- });
- }
- function selectReports(state, filters = {}) {
- const family = filters?.family ?? "ALL";
- const status = filters?.status ?? "ALL";
- const projections = state?.reportSourceProjections;
- if (!projections || typeof projections !== "object" || Array.isArray(projections)) {
- return [];
- }
- return Object.values(projections).filter((report) => report && typeof report === "object" && !Array.isArray(report)).filter((report) => family === "ALL" || report.family === family).filter((report) => status === "ALL" || report.readStatus === status).sort((left, right) => {
- const issuedAtDelta = String(right.issuedAt ?? "").localeCompare(String(left.issuedAt ?? ""));
- return issuedAtDelta || String(left.reportId ?? "").localeCompare(String(right.reportId ?? ""));
- });
- }
- var upsertProjection = (items, nextItem) => {
- const source = Array.isArray(items) ? items : [];
- const index = source.findIndex((item) => item?.id === nextItem.id);
- if (index < 0) {
- return [...source, nextItem];
- }
- return source.map((item, itemIndex) => itemIndex === index ? { ...item, ...nextItem } : item);
- };
- var journeyTitleByStage = {
- WAITING_ARRIVAL: "\u5F85\u5230\u9662",
- CHECKIN_AVAILABLE: "\u53EF\u4EE5\u7B7E\u5230",
- QUEUE_WAITING: "\u5019\u8BCA\u4E2D",
- VISIT_IN_PROGRESS: "\u5C31\u8BCA\u4E2D",
- POST_VISIT: "\u8BCA\u540E\u670D\u52A1"
- };
- var updateJourneyProjection = (journeys, appointmentId, stage, details = {}) => upsertProjection(journeys, {
- id: `journey-${appointmentId}`,
- appointmentId,
- stage,
- title: journeyTitleByStage[stage],
- ...details
- });
- var appendJourneyMessage = (messages, fixture, text) => {
- if (!text) {
- return Array.isArray(messages) ? messages : [];
- }
- return [
- ...Array.isArray(messages) ? messages : [],
- {
- id: `message-${fixture.id}`,
- type: "JOURNEY",
- unread: true,
- title: text,
- createdAt: fixture.occurredAt
- }
- ];
- };
- var readReportInvalidationFixture = (fixture) => {
- const payload = isRecord(fixture?.payload) ? fixture.payload : null;
- const reportId = payload?.reportId;
- const sourceVersion = payload?.sourceVersion;
- const sourceHash = payload?.sourceHash;
- if (typeof reportId !== "string" || reportId.trim().length === 0 || !/^[A-Za-z0-9._-]+$/.test(reportId) || !Number.isInteger(sourceVersion) || sourceVersion <= 0 || typeof sourceHash !== "string" || sourceHash.trim().length === 0 || !/^[A-Za-z0-9._:-]+$/.test(sourceHash)) {
- return null;
- }
- return {
- reportId,
- sourceVersion,
- sourceHash: sourceHash.trim()
- };
- };
- var applyReportInvalidationProjection = (state, nextState, fixture) => {
- const { reportId, sourceVersion, sourceHash } = readReportInvalidationFixture(fixture);
- const report = typeof reportId === "string" && isRecord(state.reportSourceProjections) && Object.hasOwn(state.reportSourceProjections, reportId) ? state.reportSourceProjections[reportId] : null;
- const latestKnownVersion = Math.max(
- Number.isInteger(report?.sourceVersion) ? report.sourceVersion : 0,
- Number.isInteger(report?.invalidationVersion) ? report.invalidationVersion : 0
- );
- if (!isRecord(report) || report.reportId !== reportId || sourceVersion <= latestKnownVersion) {
- return nextState;
- }
- const withdrawn = fixture.type === "REPORT_WITHDRAWN";
- const resultSnapshotProjections = Object.fromEntries(
- Object.entries(state.resultSnapshotProjections ?? {}).map(
- ([resultRef, result]) => isRecord(result) && result.reportId === reportId ? [resultRef, {
- ...result,
- valid: false,
- invalidatedBy: fixture.id,
- invalidatedReason: withdrawn ? "REPORT_WITHDRAWN" : "REPORT_CORRECTED",
- invalidatedSourceVersion: sourceVersion
- }] : [resultRef, result]
- )
- );
- const reportSourceProjections = {
- ...state.reportSourceProjections,
- [reportId]: {
- ...report,
- interpretationStatus: "INVALIDATED",
- invalidationVersion: sourceVersion,
- invalidationHash: sourceHash,
- invalidatedAt: fixture.occurredAt,
- invalidationReason: withdrawn ? "REPORT_WITHDRAWN" : "REPORT_CORRECTED"
- }
- };
- const patientWorkItems = Array.isArray(state.patientWorkItems) ? state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? {
- ...item,
- status: "ACTION_REQUIRED",
- stage: "REPORT_INVALIDATED",
- title: withdrawn ? "\u62A5\u544A\u5DF2\u64A4\u56DE" : "\u62A5\u544A\u5DF2\u66F4\u6B63",
- description: withdrawn ? "\u533B\u9662\u5DF2\u64A4\u56DE\u5F53\u524D\u62A5\u544A\uFF0C\u8BF7\u4EE5\u533B\u9662\u540E\u7EED\u901A\u77E5\u4E3A\u51C6" : "\u533B\u9662\u5DF2\u66F4\u65B0\u62A5\u544A\uFF0C\u539F\u8F85\u52A9\u89E3\u91CA\u5DF2\u5931\u6548",
- primaryAction: "VIEW_REPORT",
- secondaryAction: null
- } : item) : state.patientWorkItems;
- const isActiveReport = state.overlays?.reportId === reportId;
- return {
- ...nextState,
- reportSourceProjections,
- patientWorkItems,
- resultSnapshotProjections,
- overlays: isActiveReport ? {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportView: "REPORT_UNAVAILABLE",
- reportUnavailableReason: "REPORT_INVALIDATED",
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- } : state.overlays
- };
- };
- function applyServerProjectionFixture(state, fixture) {
- if (!isRecord(state) || !Array.isArray(state.appliedProjectionFixtureIds) || !isRecord(fixture) || typeof fixture.id !== "string" || fixture.id.trim().length === 0 || typeof fixture.type !== "string" || fixture.type.trim().length === 0) {
- return state;
- }
- if (state.appliedProjectionFixtureIds.includes(fixture.id)) {
- return state;
- }
- const isReportInvalidation = fixture.type === "REPORT_CORRECTED" || fixture.type === "REPORT_WITHDRAWN";
- if (isReportInvalidation && !readReportInvalidationFixture(fixture)) {
- return state;
- }
- const issuedReport = fixture.type === "REPORT_ISSUED" ? readReportIssuedFixture(fixture) : null;
- if (fixture.type === "REPORT_ISSUED" && !issuedReport) {
- return state;
- }
- const nextState = {
- ...state,
- appliedProjectionFixtureIds: [
- ...state.appliedProjectionFixtureIds,
- fixture.id
- ]
- };
- if (fixture.type === "REPORT_ISSUED") {
- const report = issuedReport;
- const isExamination = report.family === "EXAM";
- const reportId = report.reportId;
- const currentReport = state.reportSourceProjections?.[reportId];
- const currentVersion = currentReport?.sourceVersion;
- const invalidationVersion = currentReport?.invalidationVersion;
- const conflictsWithInvalidation = Number.isInteger(invalidationVersion) && (report.sourceVersion < invalidationVersion || report.sourceVersion === invalidationVersion && report.sourceHash !== currentReport.invalidationHash);
- if (currentReport && (!Number.isInteger(report.sourceVersion) || !Number.isInteger(currentVersion) || report.sourceVersion <= currentVersion || conflictsWithInvalidation)) {
- return nextState;
- }
- const resultRef = reportResultSnapshotId(reportId);
- const currentResult = isRecord(state.resultSnapshotProjections) && Object.hasOwn(state.resultSnapshotProjections, resultRef) ? state.resultSnapshotProjections[resultRef] : null;
- const resultSnapshotProjections = currentReport && isRecord(currentResult) ? {
- ...state.resultSnapshotProjections,
- [resultRef]: {
- ...currentResult,
- valid: false,
- invalidatedReason: "SOURCE_UPDATED",
- invalidatedAt: fixture.occurredAt ?? state.demoNow
- }
- } : state.resultSnapshotProjections;
- const consentId = reportConsentId(reportId);
- const currentConsent = isRecord(state.consents) && Object.hasOwn(state.consents, consentId) ? state.consents[consentId] : null;
- const consents = currentReport && isRecord(currentConsent) ? {
- ...state.consents,
- [consentId]: {
- ...currentConsent,
- status: "REVOKED",
- revokedAt: fixture.occurredAt ?? state.demoNow
- }
- } : state.consents;
- const consentScopeBindings = isRecord(state.consentScopeBindings) ? Object.fromEntries(
- Object.entries(state.consentScopeBindings).filter(
- ([bindingId]) => !currentReport || bindingId !== consentId
- )
- ) : state.consentScopeBindings;
- return {
- ...nextState,
- reportSourceProjections: {
- ...state.reportSourceProjections ?? {},
- [reportId]: report
- },
- resultSnapshotProjections,
- consents,
- consentScopeBindings,
- overlays: currentReport && state.overlays?.reportId === reportId ? {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportView: "REPORT_DETAIL",
- reportUnavailableReason: null,
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- } : state.overlays,
- patientWorkItems: upsertProjection(
- state.patientWorkItems,
- {
- id: `work-item-${reportId}`,
- reportId,
- type: "REPORT_REVIEW",
- status: "ACTION_REQUIRED",
- stage: "REPORT_AVAILABLE",
- priority: 90,
- title: `${typeof report.title === "string" && report.title.trim() ? report.title.trim() : isExamination ? "\u68C0\u67E5" : "\u68C0\u9A8C"}\u62A5\u544A\u5DF2\u51FA`,
- description: isExamination ? "\u533B\u9662\u68C0\u67E5\u62A5\u544A\u5DF2\u5BA1\u6838\uFF0C\u53EF\u67E5\u770B\u62A5\u544A\u8BE6\u60C5" : "18\u9879\u6307\u6807\u4E2D\u67093\u9879\u8D85\u51FA\u53C2\u8003\u8303\u56F4",
- primaryAction: isExamination ? "VIEW_REPORT" : "INTERPRET_REPORT",
- secondaryAction: isExamination ? null : "VIEW_REPORT",
- expiresAt: null
- }
- ),
- messages: upsertProjection(
- state.messages,
- {
- id: `message-${reportId}`,
- type: "REPORT",
- reportId,
- unread: true,
- title: `${typeof report.title === "string" && report.title.trim() ? report.title.trim() : isExamination ? "\u68C0\u67E5" : "\u68C0\u9A8C"}\u62A5\u544A\u5DF2\u51FA`,
- createdAt: fixture.occurredAt
- }
- )
- };
- }
- if (isReportInvalidation) {
- return applyReportInvalidationProjection(state, nextState, fixture);
- }
- const payload = fixture.payload ?? {};
- const appointmentId = payload.appointmentId ?? "appointment-001";
- let patientWorkItems = state.patientWorkItems;
- let journeyProjections = state.journeyProjections;
- let messages = state.messages;
- switch (fixture.type) {
- case "APPOINTMENT_CREATED": {
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-${appointmentId}`,
- appointmentId,
- type: "APPOINTMENT",
- status: "UPCOMING",
- stage: "WAITING_ARRIVAL",
- priority: 80,
- title: `${payload.departmentName ?? "\u95E8\u8BCA"}\u9884\u7EA6\u6210\u529F`,
- description: `${payload.visitTime ?? "09:30"} \xB7 ${payload.doctorName ?? "\u63A5\u8BCA\u533B\u751F"}`,
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "WAITING_ARRIVAL",
- {
- visitTime: payload.visitTime ?? "09:30",
- departmentName: payload.departmentName ?? "\u795E\u7ECF\u5185\u79D1",
- doctorName: payload.doctorName ?? "\u674E\u660E"
- }
- );
- messages = appendJourneyMessage(messages, fixture, "\u9884\u7EA6\u6210\u529F\uFF0C\u8BF7\u6309\u65F6\u5230\u9662");
- break;
- }
- case "CHECKIN_AVAILABLE": {
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-checkin-${appointmentId}`,
- appointmentId,
- type: "CHECK_IN",
- status: "ACTION_REQUIRED",
- stage: "CONFIRMATION_REQUIRED",
- priority: 95,
- title: "\u795E\u7ECF\u5185\u79D1\u53EF\u4EE5\u7B7E\u5230",
- description: `\u5DF2\u5230\u8FBE${payload.location ?? "\u95E8\u8BCA\u8BCA\u533A"}\uFF0C\u8BF7\u786E\u8BA4\u540E\u7B7E\u5230`,
- location: payload.location,
- primaryAction: "CONFIRM_CHECKIN",
- secondaryAction: "VIEW_ROUTE",
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "CHECKIN_AVAILABLE",
- { location: payload.location, arrivedAt: fixture.occurredAt }
- );
- messages = appendJourneyMessage(messages, fixture, "\u5DF2\u5230\u8FBE\u8BCA\u533A\uFF0C\u53EF\u4EE5\u7B7E\u5230");
- break;
- }
- case "CHECKIN_COMPLETED": {
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-checkin-${appointmentId}`,
- appointmentId,
- type: "CHECK_IN",
- status: "COMPLETED",
- stage: "COMPLETED",
- priority: 95,
- title: "\u7B7E\u5230\u5DF2\u5B8C\u6210",
- description: "\u5DF2\u8FDB\u5165\u5019\u8BCA\u961F\u5217",
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-queue-${appointmentId}`,
- appointmentId,
- type: "WAITING_QUEUE",
- status: "IN_PROGRESS",
- stage: "QUEUE_WAITING",
- priority: 90,
- title: "\u795E\u7ECF\u5185\u79D1\u5019\u8BCA\u4E2D",
- description: `\u6392\u961F\u53F7 ${payload.queueNumber ?? "\u5F85\u66F4\u65B0"}`,
- queueNumber: payload.queueNumber,
- room: payload.room,
- primaryAction: "VIEW_QUEUE",
- secondaryAction: "VIEW_ROUTE",
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "QUEUE_WAITING",
- {
- checkedInAt: fixture.occurredAt,
- queueNumber: payload.queueNumber,
- room: payload.room
- }
- );
- messages = appendJourneyMessage(messages, fixture, "\u7B7E\u5230\u6210\u529F\uFF0C\u5DF2\u8FDB\u5165\u5019\u8BCA\u961F\u5217");
- break;
- }
- case "QUEUE_UPDATED": {
- const currentQueue = patientWorkItems.find(
- (item) => item?.id === `work-item-queue-${appointmentId}`
- );
- patientWorkItems = upsertProjection(patientWorkItems, {
- ...currentQueue ?? {},
- id: `work-item-queue-${appointmentId}`,
- appointmentId,
- type: "WAITING_QUEUE",
- status: "IN_PROGRESS",
- stage: "QUEUE_WAITING",
- priority: 90,
- title: "\u795E\u7ECF\u5185\u79D1\u5019\u8BCA\u4E2D",
- description: `\u524D\u65B9 ${payload.peopleAhead ?? 0} \u4EBA\uFF0C\u9884\u8BA1 ${payload.estimatedMinutes ?? 0} \u5206\u949F`,
- queueNumber: payload.queueNumber ?? currentQueue?.queueNumber,
- peopleAhead: payload.peopleAhead,
- estimatedMinutes: payload.estimatedMinutes,
- room: payload.room ?? currentQueue?.room,
- delayed: Boolean(payload.delayed),
- updatedAt: fixture.occurredAt,
- primaryAction: "VIEW_QUEUE",
- secondaryAction: "VIEW_ROUTE",
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "QUEUE_WAITING",
- {
- queueNumber: payload.queueNumber,
- peopleAhead: payload.peopleAhead,
- estimatedMinutes: payload.estimatedMinutes,
- room: payload.room,
- delayed: Boolean(payload.delayed),
- updatedAt: fixture.occurredAt
- }
- );
- messages = appendJourneyMessage(messages, fixture, "\u5019\u8BCA\u961F\u5217\u5DF2\u66F4\u65B0");
- break;
- }
- case "VISIT_STARTED": {
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-queue-${appointmentId}`,
- appointmentId,
- type: "WAITING_QUEUE",
- status: "COMPLETED",
- stage: "COMPLETED",
- priority: 90,
- title: "\u5019\u8BCA\u5DF2\u7ED3\u675F",
- description: "\u533B\u751F\u5DF2\u5F00\u59CB\u63A5\u8BCA",
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-visit-${appointmentId}`,
- appointmentId,
- type: "VISIT",
- status: "IN_PROGRESS",
- stage: "VISIT_IN_PROGRESS",
- priority: 100,
- title: "\u533B\u751F\u63A5\u8BCA\u4E2D",
- description: payload.room ?? "\u8BF7\u5728\u8BCA\u5BA4\u5185\u5B8C\u6210\u5C31\u8BCA",
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "VISIT_IN_PROGRESS",
- { room: payload.room, startedAt: fixture.occurredAt }
- );
- messages = appendJourneyMessage(messages, fixture, "\u533B\u751F\u5DF2\u5F00\u59CB\u63A5\u8BCA");
- break;
- }
- case "VISIT_COMPLETED": {
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-visit-${appointmentId}`,
- appointmentId,
- type: "VISIT",
- status: "COMPLETED",
- stage: "COMPLETED",
- priority: 100,
- title: "\u672C\u6B21\u5C31\u8BCA\u5DF2\u5B8C\u6210",
- description: "\u8BCA\u540E\u8BB0\u5F55\u4E0E\u670D\u52A1\u5DF2\u66F4\u65B0",
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- patientWorkItems = upsertProjection(patientWorkItems, {
- id: `work-item-post-visit-${appointmentId}`,
- appointmentId,
- type: "POST_VISIT",
- status: "ACTION_REQUIRED",
- stage: "POST_VISIT",
- priority: 70,
- title: "\u67E5\u770B\u8BCA\u540E\u670D\u52A1",
- description: "\u67E5\u770B\u95E8\u8BCA\u8BB0\u5F55\u3001\u5904\u65B9\u548C\u590D\u8BCA\u5EFA\u8BAE",
- primaryAction: null,
- secondaryAction: null,
- expiresAt: null
- });
- journeyProjections = updateJourneyProjection(
- journeyProjections,
- appointmentId,
- "POST_VISIT",
- { completedAt: fixture.occurredAt }
- );
- messages = appendJourneyMessage(messages, fixture, "\u5C31\u8BCA\u5B8C\u6210\uFF0C\u8BCA\u540E\u670D\u52A1\u5DF2\u66F4\u65B0");
- break;
- }
- default:
- return nextState;
- }
- return {
- ...nextState,
- patientWorkItems,
- journeyProjections,
- messages,
- activeAgentTaskProjection: state.activeAgentTaskProjection,
- commandProjections: state.commandProjections
- };
- }
- function requestReportInterpretation(state, reportId) {
- if (!Array.isArray(state?.patientWorkItems)) {
- return state;
- }
- const itemIndex = state.patientWorkItems.findIndex(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId
- ) ?? -1;
- if (itemIndex < 0) {
- return state;
- }
- const hasConsent = hasValidReportConsent(state, reportId);
- const gate = evaluateReportInterpretationGate(state, reportId);
- const consentId = reportConsentId(reportId);
- if (!gate.allowed) {
- const unavailableReason = [
- "OCR_REVIEW_REQUIRED",
- "CRITICAL_NOTICE",
- "AI_UNAVAILABLE",
- "UNSUPPORTED"
- ].includes(gate.reason) ? gate.reason : "UNSUPPORTED";
- const currentInterpretationStatus = state.reportSourceProjections?.[reportId]?.interpretationStatus;
- return {
- ...state,
- patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "REPORT_UNAVAILABLE" } : item),
- reportSourceProjections: {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: currentInterpretationStatus === "INVALIDATED" ? "INVALIDATED" : "NOT_REQUESTED"
- }
- },
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportId,
- reportView: "REPORT_UNAVAILABLE",
- reportUnavailableReason: unavailableReason
- }
- };
- }
- const existingResult = selectValidReportResult(state, reportId);
- if (hasConsent && gate.allowed && state.reportSourceProjections?.[reportId]?.interpretationStatus === "READY" && existingResult) {
- return {
- ...state,
- patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "INTERPRETED" } : item),
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportId,
- reportView: "INTERPRETED"
- }
- };
- }
- const patientWorkItems = state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: hasConsent ? "INTERPRETING" : "CONSENT_REQUIRED" } : item);
- return {
- ...state,
- patientWorkItems,
- reportSourceProjections: hasConsent ? {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: "GENERATING"
- }
- } : state.reportSourceProjections,
- overlays: {
- ...state.overlays,
- consentId: hasConsent ? null : consentId,
- consentScope: hasConsent ? null : {
- patientId: state.activePatientId,
- reportId
- },
- reportId,
- reportView: hasConsent ? "INTERPRETING" : state?.overlays?.reportView ?? null
- }
- };
- }
- function evaluateReportInterpretationGate(state, reportId) {
- const report = state?.reportSourceProjections?.[reportId];
- if (!report || typeof report !== "object" || Array.isArray(report)) {
- return { allowed: false, reason: "REPORT_NOT_FOUND" };
- }
- if (report.family !== "LAB" || report.reportType !== "BLOOD_ROUTINE") {
- return { allowed: false, reason: "UNSUPPORTED" };
- }
- if (report.sourceStatus !== "FINAL") {
- return { allowed: false, reason: "SOURCE_NOT_FINAL" };
- }
- if (report.verificationStatus !== "VERIFIED") {
- return {
- allowed: false,
- reason: typeof report.verificationReason === "string" ? report.verificationReason : "VERIFICATION_REQUIRED"
- };
- }
- if (report.criticalNotice === true) {
- return { allowed: false, reason: "CRITICAL_NOTICE" };
- }
- if (report.interpretationStatus === "INVALIDATED") {
- return { allowed: false, reason: "REPORT_INVALIDATED" };
- }
- if (report.interpretationUnavailableReason === "AI_UNAVAILABLE" || report.interpretationUnavailableReason === "UNSUPPORTED") {
- return {
- allowed: false,
- reason: report.interpretationUnavailableReason
- };
- }
- return { allowed: true, reason: null };
- }
- function grantConsent(state, consentId) {
- const reportId = state?.overlays?.reportId;
- const expectedConsentId = typeof reportId === "string" ? reportConsentId(reportId) : null;
- const scope = state?.overlays?.consentScope;
- if (consentId !== expectedConsentId || state?.overlays?.consentId !== consentId || scope?.patientId !== state?.activePatientId || scope?.reportId !== reportId || !Array.isArray(state?.patientWorkItems)) {
- return state;
- }
- const itemIndex = state?.patientWorkItems?.findIndex(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "CONSENT_REQUIRED"
- ) ?? -1;
- if (itemIndex < 0 || !evaluateReportInterpretationGate(state, reportId).allowed) {
- return state;
- }
- const consent = {
- id: consentId,
- purpose: "\u7528\u4E8E\u672C\u6B21\u62A5\u544A\u7684 AI \u8F85\u52A9\u89E3\u91CA",
- scope: "\u5F53\u524D\u8840\u5E38\u89C4\u62A5\u544A",
- expiresAt: "2026-07-09T23:59:59+08:00",
- revocable: true,
- status: "GRANTED"
- };
- const binding = {
- patientId: state.activePatientId,
- reportId
- };
- const pendingConsentState = {
- ...state,
- consents: {
- ...state.consents,
- [consentId]: consent
- },
- consentScopeBindings: {
- ...state.consentScopeBindings,
- [consentId]: binding
- }
- };
- if (!hasValidReportConsent(pendingConsentState, reportId)) {
- return {
- ...state,
- patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "REPORT_AVAILABLE" } : item),
- reportSourceProjections: {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: "NOT_REQUESTED"
- }
- },
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportView: "REPORT_DETAIL"
- }
- };
- }
- return {
- ...state,
- consents: pendingConsentState.consents,
- consentScopeBindings: pendingConsentState.consentScopeBindings,
- reportSourceProjections: {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: "GENERATING"
- }
- },
- patientWorkItems: state.patientWorkItems.map((item, index) => {
- if (index === itemIndex) {
- return { ...item, stage: "INTERPRETING" };
- }
- return item;
- }),
- resultSnapshotProjections: {
- ...state.resultSnapshotProjections ?? {}
- },
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportView: "INTERPRETING"
- }
- };
- }
- function openReportDetails(state, reportId) {
- const exists = state?.patientWorkItems?.some(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId
- );
- const report = state?.reportSourceProjections?.[reportId];
- const hasConsistentSource = report && typeof report === "object" && !Array.isArray(report) && (report.reportId === void 0 || report.reportId === reportId);
- if (!exists || !hasConsistentSource) {
- return state;
- }
- const isExamination = report.family === "EXAM";
- const gate = isExamination ? { allowed: true, reason: null } : evaluateReportInterpretationGate(state, reportId);
- const unavailableReason = gate.allowed ? null : [
- "OCR_REVIEW_REQUIRED",
- "CRITICAL_NOTICE",
- "AI_UNAVAILABLE",
- "UNSUPPORTED"
- ].includes(gate.reason) ? gate.reason : "UNSUPPORTED";
- return {
- ...state,
- reportSourceProjections: {
- ...state.reportSourceProjections ?? {},
- [reportId]: {
- ...report,
- readStatus: "READ"
- }
- },
- overlays: {
- ...state.overlays,
- reportId,
- reportView: unavailableReason ? "REPORT_UNAVAILABLE" : "REPORT_DETAIL",
- reportUnavailableReason: unavailableReason,
- reportExpanded: false,
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- }
- };
- }
- function closeReportView(state) {
- if (!state?.overlays?.reportId && !state?.overlays?.reportView) {
- return state;
- }
- return {
- ...state,
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportId: null,
- reportView: null,
- reportUnavailableReason: null,
- reportExpanded: false,
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- }
- };
- }
- function rejectConsent(state, consentId) {
- const reportId = state?.overlays?.reportId;
- if (consentId !== reportConsentId(reportId) || state?.overlays?.consentId !== consentId || state?.overlays?.consentScope?.patientId !== state?.activePatientId || state?.overlays?.consentScope?.reportId !== reportId || !Array.isArray(state?.patientWorkItems)) {
- return state;
- }
- const hasPendingItem = state.patientWorkItems?.some(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "CONSENT_REQUIRED"
- );
- if (typeof reportId !== "string" || !hasPendingItem) {
- return state;
- }
- return {
- ...state,
- patientWorkItems: state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? { ...item, stage: "REPORT_AVAILABLE" } : item),
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportView: "REPORT_DETAIL"
- }
- };
- }
- function applyReportInterpretationProjectionFixture(state, reportId) {
- const itemIndex = state?.patientWorkItems?.findIndex(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "INTERPRETING"
- ) ?? -1;
- const consentGranted = hasValidReportConsent(state, reportId);
- const gate = evaluateReportInterpretationGate(state, reportId);
- const source = state?.reportSourceProjections?.[reportId];
- if (itemIndex < 0 || !consentGranted || !gate.allowed || !isRecord(source)) {
- return state;
- }
- const result = {
- ...reportInterpretationProjectionFixture,
- resultRef: reportResultSnapshotId(reportId),
- reportId,
- sourceVersion: source.sourceVersion,
- sourceHash: source.sourceHash,
- valid: true,
- interpretation: {
- ...reportInterpretationProjectionFixture.interpretation,
- highlights: reportInterpretationProjectionFixture.interpretation.highlights.map((highlight) => ({ ...highlight })),
- explanations: reportInterpretationProjectionFixture.interpretation.explanations.map((explanation) => ({ ...explanation })),
- canDo: [
- ...reportInterpretationProjectionFixture.interpretation.canDo
- ],
- evidence: [
- ...reportInterpretationProjectionFixture.interpretation.evidence
- ]
- }
- };
- return {
- ...state,
- patientWorkItems: state.patientWorkItems.map((item, index) => index === itemIndex ? { ...item, stage: "INTERPRETED" } : item),
- reportSourceProjections: {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: "READY"
- }
- },
- resultSnapshotProjections: {
- ...state.resultSnapshotProjections,
- [result.resultRef]: result
- },
- overlays: {
- ...state.overlays,
- consentId: null,
- reportId,
- reportView: "INTERPRETED"
- }
- };
- }
- function submitReportFeedback(state, payload = {}) {
- const reportId = payload?.reportId;
- const category = payload?.category;
- const reportSources = state?.reportSourceProjections;
- const report = typeof reportId === "string" && reportId.length > 0 && reportSources && typeof reportSources === "object" && !Array.isArray(reportSources) && Object.hasOwn(reportSources, reportId) ? reportSources[reportId] : null;
- const hasReportTask = Array.isArray(state?.patientWorkItems) && state.patientWorkItems.some((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId);
- const result = selectValidReportResult(state, reportId);
- if (!report || typeof report !== "object" || Array.isArray(report) || report.reportId !== reportId || !hasReportTask || !reportFeedbackCategories.has(category) || !Array.isArray(state?.reportFeedback) || !hasValidReportConsent(state, reportId) || !evaluateReportInterpretationGate(state, reportId).allowed || report.interpretationStatus !== "READY" || !result) {
- return state;
- }
- const feedback = {
- id: [
- "feedback",
- encodeReportId(reportId),
- report.sourceVersion,
- category
- ].join("-"),
- reportId,
- sourceVersion: report.sourceVersion,
- sourceHash: report.sourceHash,
- resultRef: result.resultRef,
- resultVersion: result.resultVersion,
- category
- };
- if (state.reportFeedback.some((item) => item?.id === feedback.id)) {
- return state;
- }
- return {
- ...state,
- reportFeedback: [...state.reportFeedback, feedback]
- };
- }
- function revokeConsent(state, consentId) {
- if (!isRecord(state) || typeof consentId !== "string" || !isRecord(state.consents) || !isRecord(state.consentScopeBindings) || !isRecord(state.reportSourceProjections) || !isRecord(state.resultSnapshotProjections) || !isRecord(state.overlays) || !Array.isArray(state.patientWorkItems) || !Object.hasOwn(state.consents, consentId) || !Object.hasOwn(state.consentScopeBindings, consentId)) {
- return state;
- }
- const consent = state.consents[consentId];
- const binding = state.consentScopeBindings[consentId];
- if (!isRecord(consent) || !isRecord(binding) || typeof binding.reportId !== "string" || !Object.hasOwn(state.reportSourceProjections, binding.reportId) || !isRecord(state.reportSourceProjections[binding.reportId]) || consentId !== reportConsentId(binding.reportId) || consent?.id !== consentId || binding?.patientId !== state?.activePatientId) {
- return state;
- }
- const reportId = binding.reportId;
- const consentScopeBindings = Object.fromEntries(
- Object.entries(state.consentScopeBindings).filter(
- ([bindingConsentId]) => bindingConsentId !== consentId
- )
- );
- const resultSnapshotProjections = Object.fromEntries(
- Object.entries(state.resultSnapshotProjections ?? {}).filter(
- ([, result]) => result?.reportId !== reportId
- )
- );
- return {
- ...state,
- consents: {
- ...state.consents,
- [consentId]: {
- ...consent,
- status: "REVOKED"
- }
- },
- consentScopeBindings,
- reportSourceProjections: {
- ...state.reportSourceProjections,
- [reportId]: {
- ...state.reportSourceProjections[reportId],
- interpretationStatus: "NOT_REQUESTED"
- }
- },
- patientWorkItems: state.patientWorkItems.map((item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId ? { ...item, stage: "REPORT_AVAILABLE" } : item),
- resultSnapshotProjections,
- overlays: {
- ...state.overlays,
- consentId: null,
- consentScope: null,
- reportId,
- reportView: "REPORT_DETAIL",
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- }
- };
- }
- var scenarioNames = /* @__PURE__ */ new Set([
- "REPORT_ARRIVED",
- "REPORT_DETAIL",
- "REPORT_CONSENT",
- "REPORT_GENERATING",
- "REPORT_READY",
- "REPORT_NEED_REVIEW",
- "REPORT_CRITICAL",
- "REPORT_INVALIDATED",
- "EMPTY_HOME",
- "REGISTRATION_QUERY",
- "COMMAND_PREPARED",
- "APPOINTMENT_CREATED",
- "CHECKIN_AVAILABLE",
- "QUEUE_UPDATED"
- ]);
- var loadRegistrationScenario = (base) => submitInteraction(
- base,
- registrationDemoText
- );
- var loadLockedRegistrationScenario = (base) => {
- const queried = loadRegistrationScenario(base);
- return applyCandidateSelectionResponseFixture(queried, {
- id: "scenario-selection-success-001",
- outcome: "SUCCESS",
- resultRef: "result-slot-001",
- resultVersion: 1,
- candidateId: "candidate-slot-0930",
- validation: {
- tenantMatched: true,
- patientMatched: true,
- sessionMatched: true,
- versionMatched: true,
- notExpired: true,
- realtimeAvailable: true,
- l1LockSucceeded: true
- },
- lock: {
- lockId: "lock-slot-001",
- expiresAt: "2026-07-03T09:05:00+08:00"
- }
- });
- };
- var loadCommandPreparedScenario = (base) => {
- const locked = loadLockedRegistrationScenario(base);
- return applyCommandProjectionFixture(locked, {
- id: "scenario-command-awaiting-001",
- commandId: "command-register-001",
- version: 1,
- riskLevel: "L2",
- status: "AWAITING_CONFIRMATION",
- details: {
- operationName: "\u786E\u8BA4\u6302\u53F7",
- patientName: "\u5F20\u5148\u751F",
- departmentName: "\u795E\u7ECF\u5185\u79D1",
- doctorName: "\u674E\u660E",
- doctorTitle: "\u4E3B\u4EFB\u533B\u5E08",
- visitAt: "2026-07-03 09:30",
- fee: "30\u5143",
- expiresAt: "2026-07-03T09:05:00+08:00",
- countdown: "\u5269\u4F59 5 \u5206\u949F",
- candidateId: "candidate-slot-0930"
- }
- });
- };
- function loadScenario(state, scenarioName) {
- const normalized = scenarioNames.has(scenarioName) ? scenarioName : "EMPTY_HOME";
- const base = {
- ...createInitialState({
- activePatientId: state?.activePatientId ?? "patient-zhang-san"
- }),
- demoScenario: normalized
- };
- const reportFixture = {
- id: "scenario-report-arrived-001",
- type: "REPORT_ISSUED",
- occurredAt: "2026-07-03T09:30:00+08:00",
- payload: { reportId: "report-blood-20260702" }
- };
- switch (normalized) {
- case "REPORT_ARRIVED":
- return { ...applyServerProjectionFixture(base, reportFixture), demoScenario: normalized };
- case "REPORT_DETAIL": {
- const report = applyServerProjectionFixture(base, reportFixture);
- return {
- ...openReportDetails(report, "report-blood-20260702"),
- demoScenario: normalized
- };
- }
- case "REPORT_CONSENT": {
- const report = openReportDetails(
- applyServerProjectionFixture(base, reportFixture),
- "report-blood-20260702"
- );
- return {
- ...requestReportInterpretation(report, "report-blood-20260702"),
- demoScenario: normalized
- };
- }
- case "REPORT_GENERATING": {
- const pending = loadScenario(base, "REPORT_CONSENT");
- return {
- ...grantConsent(
- pending,
- "consent-ai-report-blood-20260702"
- ),
- demoScenario: normalized
- };
- }
- case "REPORT_NEED_REVIEW": {
- const report = applyServerProjectionFixture(base, {
- ...reportFixture,
- payload: {
- ...reportFixture.payload,
- verificationStatus: "NEED_REVIEW",
- verificationReason: "OCR_REVIEW_REQUIRED"
- }
- });
- return {
- ...openReportDetails(report, "report-blood-20260702"),
- demoScenario: normalized
- };
- }
- case "REPORT_CRITICAL": {
- const report = applyServerProjectionFixture(base, {
- ...reportFixture,
- payload: {
- ...reportFixture.payload,
- criticalNotice: true
- }
- });
- return {
- ...openReportDetails(report, "report-blood-20260702"),
- demoScenario: normalized
- };
- }
- case "REPORT_INVALIDATED": {
- const ready = loadScenario(base, "REPORT_READY");
- return {
- ...applyServerProjectionFixture(ready, {
- id: "scenario-report-corrected-002",
- type: "REPORT_CORRECTED",
- occurredAt: "2026-07-03T11:00:00+08:00",
- payload: {
- reportId: "report-blood-20260702",
- sourceVersion: 2,
- sourceHash: "sha256:blood-v2"
- }
- }),
- demoScenario: normalized
- };
- }
- case "REPORT_READY": {
- const report = applyServerProjectionFixture(base, reportFixture);
- const pending = requestReportInterpretation(
- report,
- "report-blood-20260702"
- );
- const interpreting = grantConsent(
- pending,
- "consent-ai-report-blood-20260702"
- );
- return {
- ...applyReportInterpretationProjectionFixture(
- interpreting,
- "report-blood-20260702"
- ),
- demoScenario: normalized
- };
- }
- case "REGISTRATION_QUERY":
- return { ...loadRegistrationScenario(base), demoScenario: normalized };
- case "COMMAND_PREPARED":
- return { ...loadCommandPreparedScenario(base), demoScenario: normalized };
- case "APPOINTMENT_CREATED": {
- const awaiting = loadCommandPreparedScenario(base);
- return {
- ...applyCommandProjectionFixture(awaiting, {
- id: "scenario-command-succeeded-001",
- commandId: "command-register-001",
- status: "SUCCEEDED",
- resultFixtureId: "appointment-created-001"
- }),
- activeTab: "home",
- demoScenario: normalized
- };
- }
- case "CHECKIN_AVAILABLE": {
- const appointment = loadScenario(base, "APPOINTMENT_CREATED");
- return {
- ...applyServerProjectionFixture(appointment, {
- id: "scenario-checkin-available-001",
- type: "CHECKIN_AVAILABLE",
- occurredAt: "2026-07-03T09:10:00+08:00",
- payload: {
- appointmentId: "appointment-001",
- location: "\u95E8\u8BCA\u4E09\u697C\u795E\u7ECF\u5185\u79D1\u8BCA\u533A"
- }
- }),
- demoScenario: normalized
- };
- }
- case "QUEUE_UPDATED": {
- const arrived = loadScenario(base, "CHECKIN_AVAILABLE");
- const checkedIn = applyServerProjectionFixture(arrived, {
- id: "scenario-checkin-completed-001",
- type: "CHECKIN_COMPLETED",
- occurredAt: "2026-07-03T09:12:00+08:00",
- payload: {
- appointmentId: "appointment-001",
- queueNumber: "A023",
- room: "3F-08\u8BCA\u5BA4"
- }
- });
- return {
- ...applyServerProjectionFixture(checkedIn, {
- id: "scenario-queue-updated-001",
- type: "QUEUE_UPDATED",
- occurredAt: "2026-07-03T09:18:00+08:00",
- payload: {
- appointmentId: "appointment-001",
- queueNumber: "A023",
- peopleAhead: 4,
- estimatedMinutes: 18,
- room: "3F-08\u8BCA\u5BA4",
- delayed: true
- }
- }),
- demoScenario: normalized
- };
- }
- default:
- return base;
- }
- }
- // docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
- var navItems = Object.freeze([
- { id: "home", label: "\u9996\u9875", icon: "house" },
- { id: "care", label: "\u5C31\u533B", icon: "stethoscope" },
- { id: "assistant", label: "AI\u52A9\u624B", icon: "sparkles" },
- { id: "records", label: "\u5065\u5EB7\u6863\u6848", icon: "folder-heart" },
- { id: "profile", label: "\u6211\u7684", icon: "user-round" }
- ]);
- var demoScenarioGroups = Object.freeze({
- REPORT_CLOSURE: Object.freeze([
- "REPORT_ARRIVED",
- "REPORT_DETAIL",
- "REPORT_CONSENT",
- "REPORT_GENERATING",
- "REPORT_READY",
- "REPORT_NEED_REVIEW",
- "REPORT_CRITICAL",
- "REPORT_INVALIDATED"
- ]),
- CARE_CLOSURE: Object.freeze([
- "EMPTY_HOME",
- "REGISTRATION_QUERY",
- "COMMAND_PREPARED",
- "APPOINTMENT_CREATED",
- "CHECKIN_AVAILABLE",
- "QUEUE_UPDATED"
- ])
- });
- var demoScenarios = Object.freeze(
- Object.values(demoScenarioGroups).flat()
- );
- var scenarioLabels = {
- EMPTY_HOME: "\u7A7A\u95F2\u9996\u9875",
- REPORT_ARRIVED: "\u62A5\u544A\u5230\u8FBE",
- REPORT_DETAIL: "\u62A5\u544A\u8BE6\u60C5",
- REPORT_CONSENT: "\u62A5\u544A\u6388\u6743",
- REPORT_GENERATING: "\u89E3\u8BFB\u751F\u6210\u4E2D",
- REPORT_READY: "\u62A5\u544A\u5DF2\u89E3\u8BFB",
- REPORT_NEED_REVIEW: "\u62A5\u544A\u9700\u590D\u6838",
- REPORT_CRITICAL: "\u62A5\u544A\u5371\u6025\u503C",
- REPORT_INVALIDATED: "\u62A5\u544A\u5DF2\u5931\u6548",
- REGISTRATION_QUERY: "\u81EA\u7136\u8BED\u8A00\u67E5\u53F7",
- COMMAND_PREPARED: "\u6302\u53F7\u5F85\u786E\u8BA4",
- APPOINTMENT_CREATED: "\u6302\u53F7\u6210\u529F",
- CHECKIN_AVAILABLE: "\u5230\u9662\u53EF\u7B7E\u5230",
- QUEUE_UPDATED: "\u5019\u8BCA\u66F4\u65B0"
- };
- var homeServices = Object.freeze([
- { label: "\u9884\u7EA6\u6302\u53F7", icon: "calendar-plus" },
- { label: "\u667A\u80FD\u5BFC\u8BCA", icon: "scan-heart" },
- { label: "\u62A5\u544A\u67E5\u8BE2", icon: "file-chart-column" },
- { label: "\u95E8\u8BCA\u7F34\u8D39", icon: "wallet-cards" },
- { label: "\u68C0\u67E5\u9884\u7EA6", icon: "clipboard-clock" },
- { label: "\u4F4F\u9662\u670D\u52A1", icon: "bed" },
- { label: "\u9662\u5185\u5BFC\u822A", icon: "map-pinned" },
- { label: "\u5728\u7EBF\u95EE\u8BCA", icon: "messages-square" }
- ]);
- var actionLabels = {
- INTERPRET_REPORT: "AI \u89E3\u8BFB\u62A5\u544A",
- VIEW_REPORT: "\u67E5\u770B\u539F\u62A5\u544A",
- CONFIRM_CHECKIN: "\u786E\u8BA4\u7B7E\u5230",
- VIEW_ROUTE: "\u67E5\u770B\u8DEF\u7EBF",
- VIEW_QUEUE: "\u67E5\u770B\u5019\u8BCA"
- };
- var actionTokens = {
- INTERPRET_REPORT: "interpret-report",
- VIEW_REPORT: "view-report",
- CONFIRM_CHECKIN: "open-checkin-confirmation",
- VIEW_ROUTE: "open-route",
- VIEW_QUEUE: "open-queue"
- };
- var statusPresentation = {
- ACTION_REQUIRED: {
- className: "action-required",
- icon: "circle-alert",
- label: "\u9700\u8981\u5904\u7406"
- },
- IN_PROGRESS: {
- className: "in-progress",
- icon: "loader-circle",
- label: "\u8FDB\u884C\u4E2D"
- },
- UPCOMING: {
- className: "upcoming",
- icon: "calendar-clock",
- label: "\u5373\u5C06\u5F00\u59CB"
- },
- COMPLETED: {
- className: "completed",
- icon: "circle-check-big",
- label: "\u5DF2\u5B8C\u6210"
- }
- };
- var unknownStatusPresentation = {
- className: "unknown",
- icon: "circle-help",
- label: "\u72B6\u6001\u5F85\u786E\u8BA4"
- };
- var escapeHtml = (value) => String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
- var getActionLabel = (action) => typeof action === "string" && Object.hasOwn(actionLabels, action) ? actionLabels[action] : null;
- var renderActionAttributes = (action, task) => {
- const token = typeof action === "string" && Object.hasOwn(actionTokens, action) ? actionTokens[action] : null;
- if (!token) {
- return "";
- }
- if (typeof task?.reportId === "string") {
- return ` data-action="${token}" data-report-id="${escapeHtml(task.reportId)}"`;
- }
- if (typeof task?.appointmentId === "string") {
- return ` data-action="${token}" data-appointment-id="${escapeHtml(task.appointmentId)}"`;
- }
- return "";
- };
- var getStatusPresentation = (status) => typeof status === "string" && Object.hasOwn(statusPresentation, status) ? statusPresentation[status] : unknownStatusPresentation;
- var getShellElements = (root) => {
- if (!root?.getElementById) {
- return null;
- }
- const appHeader = root.getElementById("appHeader");
- const appView = root.getElementById("appView");
- const bottomNav = root.getElementById("bottomNav");
- return appHeader && appView && bottomNav ? { appHeader, appView, bottomNav } : null;
- };
- var renderIcons = () => {
- const lucide = globalThis.lucide;
- if (lucide?.createIcons) {
- lucide.createIcons({ attrs: { "aria-hidden": "true" } });
- }
- };
- var patientDisplayNames = {
- "patient-zhang-san": "\u5F20\u5148\u751F",
- "patient-li-na": "\u674E\u5973\u58EB"
- };
- var patientDisplayName = (state) => patientDisplayNames[state?.activePatientId] ?? "\u5F53\u524D\u5C31\u8BCA\u4EBA";
- var renderHeader = (state) => {
- const messages = Array.isArray(state?.messages) ? state.messages : [];
- const unreadCount = messages.filter((message) => message?.unread).length;
- const messageLabel = unreadCount > 0 ? `\u6D88\u606F\uFF0C${unreadCount} \u6761\u672A\u8BFB` : "\u6D88\u606F";
- return `
- <div class="hospital-context">
- <span class="hospital-logo" aria-hidden="true">
- <i data-lucide="hospital"></i>
- </span>
- <div class="hospital-context__copy">
- <p class="app-kicker">\u7A7A\u6D77\u533B\u9662</p>
- <p class="campus-label">\u5F53\u524D\u9662\u533A \xB7 \u7A7A\u6D77\u533B\u9662\u672C\u9662</p>
- </div>
- <button class="icon-button" type="button" aria-label="${messageLabel}" data-action="open-messages">
- <i data-lucide="bell"></i>
- ${unreadCount > 0 ? `<span class="unread-badge" aria-hidden="true">${unreadCount}</span>` : ""}
- </button>
- <button class="icon-button" type="button" aria-label="\u9690\u79C1\u8BBE\u7F6E">
- <i data-lucide="shield-check"></i>
- </button>
- </div>
- <button class="patient-switcher" type="button" aria-label="\u5207\u6362\u5F53\u524D\u5C31\u8BCA\u4EBA" data-action="switch-patient">
- <span>\u5F53\u524D\u5C31\u8BCA\u4EBA</span>
- <strong>${patientDisplayName(state)}</strong>
- <i data-lucide="chevron-down"></i>
- </button>
- `;
- };
- var journeyStages = [
- { stage: "WAITING_ARRIVAL", label: "\u5230\u9662", time: "09:10" },
- { stage: "CHECKIN_AVAILABLE", label: "\u7B7E\u5230", time: "09:20" },
- { stage: "QUEUE_WAITING", label: "\u5019\u8BCA", time: "09:30" },
- { stage: "VISIT_IN_PROGRESS", label: "\u533B\u751F\u63A5\u8BCA", time: "10:05" },
- { stage: "POST_VISIT", label: "\u8BCA\u540E\u670D\u52A1", time: "10:25" }
- ];
- var renderJourneyProjection = (journey) => {
- const activeIndex = Math.max(
- 0,
- journeyStages.findIndex(({ stage }) => stage === journey.stage)
- );
- const start = Math.max(
- 0,
- Math.min(activeIndex - 1, journeyStages.length - 3)
- );
- const visibleStages = journeyStages.slice(start, start + 3);
- return `
- <article class="journey-card">
- <div class="journey-card__heading">
- <div>
- <strong>${escapeHtml(journey.title)}</strong>
- <p>${escapeHtml(journey.location ?? journey.room ?? "\u9662\u5185\u884C\u7A0B\u6301\u7EED\u66F4\u65B0")}</p>
- </div>
- ${journey.stage === "QUEUE_WAITING" ? `
- <button class="text-button" type="button" data-action="open-queue" data-appointment-id="${escapeHtml(journey.appointmentId)}">
- \u67E5\u770B\u5019\u8BCA
- </button>
- ` : ""}
- </div>
- <ol class="journey-steps">
- ${visibleStages.map((node) => `
- <li class="${node.stage === journey.stage ? "is-current" : ""}">
- <time>${node.time}</time>
- <span>${node.label}</span>
- </li>
- `).join("")}
- </ol>
- </article>
- `;
- };
- var renderHome = (state) => {
- const tasks = selectHomeTasks(state);
- const journeys = Array.isArray(state?.journeyProjections) ? state.journeyProjections : [];
- const primaryTaskIndex = tasks.findIndex(
- ({ primaryAction, status }) => (status === "ACTION_REQUIRED" || status === "IN_PROGRESS") && Boolean(getActionLabel(primaryAction))
- );
- const activeTaskCount = tasks.filter(
- ({ status }) => status === "ACTION_REQUIRED" || status === "IN_PROGRESS"
- ).length;
- return `
- <section class="ai-welcome" aria-labelledby="welcomeTitle">
- <div>
- <p class="section-eyebrow">AI \u5C31\u533B\u52A9\u624B</p>
- <h1 id="welcomeTitle">${patientDisplayName(state)}\uFF0C\u4E0A\u5348\u597D</h1>
- <p>${activeTaskCount > 0 ? `\u6709 ${activeTaskCount} \u9879\u5C31\u533B\u4E8B\u9879\u9700\u8981\u5173\u6CE8` : "\u4ECA\u5929\u6682\u65E0\u5F85\u5904\u7406\u7684\u5C31\u533B\u4E8B\u9879"}</p>
- </div>
- <img class="doctor-avatar" src="./assets/doctor.png" alt="\u7A7A\u6D77\u533B\u9662 AI \u533B\u751F\u52A9\u624B" />
- <button class="assistant-input" type="button" data-action="open-assistant">
- <i data-lucide="sparkles"></i>
- <span>\u95EE\u75C5\u60C5\u3001\u67E5\u62A5\u544A\u3001\u627E\u533B\u751F\u3001\u529E\u5C31\u533B\u670D\u52A1</span>
- <i data-lucide="arrow-up-right"></i>
- </button>
- </section>
- <section class="home-section" aria-labelledby="tasksTitle">
- <div class="section-heading">
- <div>
- <p class="section-eyebrow">\u5C31\u533B\u4EFB\u52A1</p>
- <h2 id="tasksTitle">\u73B0\u5728\u8981\u505A</h2>
- </div>
- </div>
- <div class="task-list">
- ${tasks.length === 0 ? `
- <div class="empty-card">
- <i data-lucide="circle-check-big"></i>
- <div>
- <strong>\u4ECA\u5929\u6CA1\u6709\u5F85\u5904\u7406\u4E8B\u9879</strong>
- <p>\u65B0\u7684\u62A5\u544A\u3001\u7F34\u8D39\u6216\u9884\u7EA6\u63D0\u9192\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002</p>
- </div>
- </div>
- ` : tasks.map((task, index) => {
- const isCompleted = task.status === "COMPLETED";
- const status = getStatusPresentation(task.status);
- const isPrimaryTask = index === primaryTaskIndex;
- const primaryActionLabel = getActionLabel(task.primaryAction);
- const secondaryActionLabel = getActionLabel(task.secondaryAction);
- const canAct = (task.status === "ACTION_REQUIRED" || task.status === "IN_PROGRESS") && Boolean(primaryActionLabel);
- const recommendation = isCompleted ? "\u65E0\u9700\u64CD\u4F5C" : canAct ? primaryActionLabel : task.status === "UPCOMING" ? "\u7B49\u5F85\u4E8B\u9879\u5F00\u59CB" : "\u786E\u8BA4\u72B6\u6001\u4E0E\u53EF\u7528\u64CD\u4F5C";
- return `
- <article class="task-card task-card--${status.className}">
- <div class="task-card__status">
- <i data-lucide="${status.icon}"></i>
- <span>${status.label}</span>
- </div>
- <h3>${escapeHtml(task.title)}</h3>
- <p><strong>\u53D1\u751F\u4E86\u4EC0\u4E48\uFF1A</strong>${escapeHtml(task.description)}</p>
- <p><strong>\u4E3A\u4EC0\u4E48\u5173\u6CE8\uFF1A</strong>${isCompleted ? "\u8BE5\u4E8B\u9879\u5DF2\u5904\u7406\u5B8C\u6210\u3002" : "\u53CA\u65F6\u5904\u7406\u6709\u52A9\u4E8E\u987A\u5229\u63A8\u8FDB\u540E\u7EED\u5C31\u533B\u5B89\u6392\u3002"}</p>
- <div class="task-card__recommendation">
- <span>\u5EFA\u8BAE\u64CD\u4F5C</span>
- <strong>${recommendation}</strong>
- </div>
- ${canAct ? `
- <div class="task-card__actions">
- <button class="button ${isPrimaryTask ? "button--primary" : "button--secondary"}" type="button"${renderActionAttributes(task.primaryAction, task)}>
- ${primaryActionLabel}
- </button>
- ${secondaryActionLabel ? `
- <button class="button button--secondary" type="button"${renderActionAttributes(task.secondaryAction, task)}>
- ${secondaryActionLabel}
- </button>
- ` : ""}
- </div>
- ` : ""}
- </article>
- `;
- }).join("")}
- </div>
- </section>
- <section class="home-section" aria-labelledby="journeyTitle">
- <div class="section-heading">
- <div>
- <p class="section-eyebrow">\u9662\u5185\u8FDB\u5EA6</p>
- <h2 id="journeyTitle">\u4ECA\u65E5\u65C5\u7A0B</h2>
- </div>
- </div>
- ${journeys.length > 0 ? journeys.map(renderJourneyProjection).join("") : `
- <div class="journey-empty">
- <i data-lucide="route"></i>
- <div>
- <strong>\u4ECA\u5929\u6682\u65E0\u9662\u5185\u884C\u7A0B</strong>
- <p>\u6302\u53F7\u6216\u68C0\u67E5\u5B89\u6392\u786E\u8BA4\u540E\uFF0C\u5C06\u5C55\u793A\u524D\u540E\u6B65\u9AA4\u548C\u5F53\u524D\u8282\u70B9\u3002</p>
- </div>
- </div>
- `}
- </section>
- <section class="home-section" aria-labelledby="servicesTitle">
- <div class="section-heading">
- <div>
- <p class="section-eyebrow">\u5FEB\u6377\u5165\u53E3</p>
- <h2 id="servicesTitle">\u5E38\u7528\u670D\u52A1</h2>
- </div>
- </div>
- <div class="service-grid">
- ${homeServices.map(({ label, icon }) => `
- <button class="service-entry" type="button">
- <span><i data-lucide="${icon}"></i></span>
- <strong>${label}</strong>
- </button>
- `).join("")}
- </div>
- </section>
- <section class="home-section care-section" aria-labelledby="careTitle">
- <div class="section-heading">
- <div>
- <p class="section-eyebrow">\u5065\u5EB7\u63D0\u9192</p>
- <h2 id="careTitle">\u4E3B\u52A8\u5173\u6000</h2>
- </div>
- </div>
- <div class="care-empty">
- <i data-lucide="heart-handshake"></i>
- <div>
- <strong>\u6682\u65E0\u65B0\u7684\u4E3B\u52A8\u5173\u6000\u63D0\u9192</strong>
- <p>\u7528\u836F\u3001\u590D\u8BCA\u4E0E\u68C0\u67E5\u51C6\u5907\u63D0\u9192\u4F1A\u5728\u786E\u8BA4\u540E\u5C55\u793A\u3002</p>
- </div>
- </div>
- </section>
- `;
- };
- var candidateToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
- var renderAssistantMessages = (messages) => Array.isArray(messages) ? messages.filter((message) => message && typeof message === "object" && (message.role === "user" || message.role === "assistant")).map(({ role, text }) => `
- <article class="assistant-message assistant-message--${role}">
- <strong>${role === "user" ? "\u4F60" : "AI \u52A9\u624B"}</strong>
- <p>${escapeHtml(text)}</p>
- </article>
- `).join("") : "";
- var renderAssistant = (state) => {
- const task = state?.activeAgentTaskProjection;
- const result = typeof task?.activeResultRef === "string" ? state?.resultSnapshotProjections?.[task.activeResultRef] : null;
- const items = task?.stage === "CANDIDATE_SELECTION" && result?.resultRef === task.activeResultRef && Array.isArray(result?.items) ? result.items : [];
- const candidates = items.filter((item) => item && typeof item === "object" && typeof item.candidateId === "string" && candidateToken.test(item.candidateId));
- const hasTask = task?.type === "REGISTRATION";
- return `
- <section class="assistant-view" aria-labelledby="assistantTitle">
- <header class="assistant-view__header">
- <button class="text-button" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div>
- <p class="section-eyebrow">\u81EA\u7136\u8BED\u8A00\u5C31\u533B\u670D\u52A1</p>
- <h1 id="assistantTitle">AI \u52A9\u624B</h1>
- </div>
- ${hasTask ? `
- <button class="text-button" type="button" data-action="return-current-task">\u8FD4\u56DE\u5F53\u524D\u4EFB\u52A1</button>
- ` : ""}
- </header>
- <section class="assistant-scenarios" aria-labelledby="scenarioTitle">
- <h2 id="scenarioTitle">\u573A\u666F\u5EFA\u8BAE</h2>
- <button type="button" data-action="open-report-center" data-filter="ALL">\u5E2E\u6211\u770B\u62A5\u544A</button>
- <button type="button" data-action="submit-registration-demo">\u6211\u60F3\u6302\u660E\u5929\u795E\u7ECF\u5185\u79D1\u674E\u660E\u4E3B\u4EFB\u7684\u53F7</button>
- <button type="button">\u667A\u80FD\u5BFC\u8BCA</button>
- <button type="button">\u4EBA\u5DE5\u670D\u52A1</button>
- </section>
- <div class="assistant-thread" aria-live="polite">
- ${renderAssistantMessages(state?.messages)}
- ${task?.stage === "REQUERY_REQUIRED" ? `
- <aside class="assistant-recovery">
- <strong>REQUERY_REQUIRED</strong>
- <p>\u65E7\u5019\u9009\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u540E\u518D\u9009\u62E9\u3002</p>
- <button class="button button--secondary" type="button" data-action="submit-registration-demo">\u91CD\u65B0\u67E5\u8BE2</button>
- </aside>
- ` : ""}
- ${result ? `
- <section class="assistant-result" aria-labelledby="slotResultTitle">
- <h2 id="slotResultTitle">\u53EF\u9009\u53F7\u6E90</h2>
- <p class="assistant-answer">${escapeHtml(result.text)}</p>
- ${candidates.length > 0 ? `
- <div class="slot-presentation">
- ${candidates.map((item) => `
- <button class="slot-candidate" type="button" data-action="select-candidate" data-candidate-id="${item.candidateId}">
- <strong>${escapeHtml(item.visitDate)} ${escapeHtml(item.timePeriod)} ${escapeHtml(item.visitTime)}</strong>
- <span>${escapeHtml(item.departmentName)} \xB7 ${escapeHtml(item.doctorName)} ${escapeHtml(item.doctorTitle)}</span>
- <span>\u4F59\u53F7 ${escapeHtml(item.remainingSlots)} \xB7 \u6302\u53F7\u8D39 ${escapeHtml(item.fee)}</span>
- </button>
- `).join("")}
- </div>
- ` : ""}
- </section>
- ` : ""}
- ${task?.stage === "AWAITING_REGISTRATION_CONFIRMATION" ? `
- <aside class="assistant-lock-success">
- <strong>\u9501\u53F7\u6210\u529F</strong>
- <p>\u53F7\u6E90\u5DF2\u4E34\u65F6\u9501\u5B9A\uFF0C\u5DF2\u6536\u5230\u670D\u52A1\u7AEF\u4E8C\u6B21\u786E\u8BA4\u6295\u5F71\u3002</p>
- </aside>
- ` : ""}
- </div>
- <div class="assistant-composer">
- <label class="sr-only" for="assistantComposerInput">\u8F93\u5165\u5C31\u533B\u9700\u6C42</label>
- <input id="assistantComposerInput" type="text" placeholder="\u63CF\u8FF0\u4F60\u60F3\u529E\u7406\u7684\u5C31\u533B\u670D\u52A1" />
- <button type="button" data-action="submit-assistant-text" aria-label="\u53D1\u9001">
- <i data-lucide="arrow-up"></i>
- </button>
- </div>
- </section>
- `;
- };
- var reportFamilyLabels = { LAB: "\u68C0\u9A8C\u62A5\u544A", EXAM: "\u68C0\u67E5\u62A5\u544A" };
- var reportSourceStatusLabels = {
- FINAL: "\u5DF2\u5BA1\u6838",
- PRELIMINARY: "\u5F85\u5BA1\u6838"
- };
- var reportInterpretationLabels = {
- NOT_REQUESTED: "\u5C1A\u672A\u8FDB\u884C AI \u8F85\u52A9\u89E3\u8BFB",
- INTERPRETING: "AI \u8F85\u52A9\u89E3\u8BFB\u4E2D",
- READY: "AI \u8F85\u52A9\u89E3\u8BFB\u5DF2\u751F\u6210",
- NEED_REVIEW: "\u89E3\u8BFB\u7ED3\u679C\u5EFA\u8BAE\u7531\u533B\u751F\u786E\u8BA4"
- };
- var metricFlagLabels = { HIGH: "\u504F\u9AD8", LOW: "\u504F\u4F4E", NORMAL: "\u6B63\u5E38" };
- var abnormalMetricsOf = (report) => Array.isArray(report?.metrics) ? report.metrics.filter((metric) => ["HIGH", "LOW"].includes(metric?.sourceFlag)) : [];
- var renderMetric = (metric) => {
- const name = escapeHtml(metric?.name || "\u672A\u547D\u540D\u6307\u6807");
- const value = metric?.value === null || metric?.value === void 0 ? "\u2014" : escapeHtml(metric.value);
- const unit = metric?.unit ? ` ${escapeHtml(metric.unit)}` : "";
- const range = metric?.referenceRange ? `<small>\u53C2\u8003\u8303\u56F4 ${escapeHtml(metric.referenceRange)}${unit}</small>` : "<small>\u53C2\u8003\u8303\u56F4\u672A\u63D0\u4F9B</small>";
- const flag = metricFlagLabels[metric?.sourceFlag] ?? "\u7ED3\u679C\u5F85\u786E\u8BA4";
- return `<li class="report-metric report-metric--${escapeHtml(String(metric?.sourceFlag ?? "unknown").toLowerCase())}">
- <span>${name}</span><strong>${value}${unit}</strong>${range}<em>${flag}</em>
- </li>`;
- };
- var renderReportCenter = (state) => {
- const filters = {
- family: ["ALL", "LAB", "EXAM"].includes(state?.reportFilters?.family) ? state.reportFilters.family : "ALL",
- status: ["ALL", "UNREAD", "READ"].includes(state?.reportFilters?.status) ? state.reportFilters.status : "ALL"
- };
- const reports = selectReports(state, filters);
- return `
- <section class="module-page report-center" aria-labelledby="reportCenterTitle">
- ${pageHeader("\u5065\u5EB7\u6863\u6848", "\u62A5\u544A\u4E2D\u5FC3", "\u533B\u9662\u539F\u59CB\u62A5\u544A\u548C AI \u8F85\u52A9\u89E3\u8BFB\u7EDF\u4E00\u67E5\u770B\u3002")}
- <p class="report-center__patient">\u5F53\u524D\u5C31\u8BCA\u4EBA ${escapeHtml(patientDisplayName(state))}</p>
- <div class="report-filter-group" role="group" aria-label="\u62A5\u544A\u7C7B\u578B">
- ${[["ALL", "\u5168\u90E8"], ["LAB", "\u68C0\u9A8C\u62A5\u544A"], ["EXAM", "\u68C0\u67E5\u62A5\u544A"]].map(([id, label]) => `
- <button type="button" data-action="set-report-family-filter" data-filter="${id}" aria-pressed="${filters.family === id}">${label}</button>
- `).join("")}
- </div>
- <div class="report-filter-group report-filter-group--status" role="group" aria-label="\u9605\u8BFB\u72B6\u6001">
- ${[["ALL", "\u5168\u90E8"], ["UNREAD", "\u672A\u8BFB"], ["READ", "\u5DF2\u8BFB"]].map(([id, label]) => `
- <button type="button" data-action="set-report-status-filter" data-filter="${id}" aria-pressed="${filters.status === id}">${label}</button>
- `).join("")}
- </div>
- <div class="report-center__list">
- ${reports.length ? reports.map((report) => {
- const abnormalCount = abnormalMetricsOf(report).length;
- return `
- <button class="report-list-card" type="button" data-action="view-report" data-report-id="${escapeHtml(report.reportId)}">
- <span class="report-list-card__top"><b>${escapeHtml(reportFamilyLabels[report.family] ?? "\u533B\u9662\u62A5\u544A")}</b><em>${report.readStatus === "READ" ? "\u5DF2\u8BFB" : "\u672A\u8BFB"}</em></span>
- <strong>${escapeHtml(report.title || "\u672A\u547D\u540D\u62A5\u544A")}</strong>
- <span>${escapeHtml(report.hospitalName || "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${escapeHtml(report.departmentName || "\u79D1\u5BA4\u5F85\u786E\u8BA4")}</span>
- <time>${escapeHtml(report.issuedAt || "\u51FA\u5177\u65F6\u95F4\u5F85\u786E\u8BA4")}</time>
- <span class="report-list-card__status">${escapeHtml(reportSourceStatusLabels[report.sourceStatus] ?? "\u62A5\u544A\u72B6\u6001\u5F85\u786E\u8BA4")}${report.family === "LAB" ? ` \xB7 ${abnormalCount ? `${abnormalCount} \u9879\u6307\u6807\u5F02\u5E38` : "\u672A\u6807\u8BB0\u5F02\u5E38\u6307\u6807"}` : ""}</span>
- <span>${report.family === "EXAM" ? "\u8BE5\u7C7B\u578B\u6682\u672A\u5F00\u653EAI\u8F85\u52A9\u89E3\u8BFB" : escapeHtml(reportInterpretationLabels[report.interpretationStatus] ?? "AI \u8F85\u52A9\u89E3\u8BFB\u72B6\u6001\u5F85\u786E\u8BA4")}</span>
- </button>`;
- }).join("") : `<div class="report-empty"><i data-lucide="file-search"></i><strong>\u6682\u65E0\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u62A5\u544A</strong><p>\u53EF\u4EE5\u8C03\u6574\u62A5\u544A\u7C7B\u578B\u6216\u9605\u8BFB\u72B6\u6001\u3002</p></div>`}
- </div>
- </section>`;
- };
- var renderReportDetail = (report = {}, expanded = false) => {
- const metrics = Array.isArray(report?.metrics) ? report.metrics : [];
- const abnormalMetrics = abnormalMetricsOf(report);
- const hiddenMetrics = metrics.filter(
- (metric) => !abnormalMetrics.includes(metric)
- );
- const visibleMetrics = expanded ? [...abnormalMetrics, ...hiddenMetrics] : abnormalMetrics;
- const reportId = report?.reportId ?? "";
- return `
- <section class="report-view" aria-labelledby="reportTitle">
- <button class="text-button report-view__back" type="button" data-action="open-report-center">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u62A5\u544A\u4E2D\u5FC3
- </button>
- <div class="report-hero">
- <p class="section-eyebrow">\u533B\u9662\u539F\u59CB\u62A5\u544A</p>
- <h1 id="reportTitle">${escapeHtml(report?.title || "\u672A\u547D\u540D\u62A5\u544A")}</h1>
- <p>${escapeHtml(report?.hospitalName || "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${escapeHtml(report?.departmentName || "\u79D1\u5BA4\u5F85\u786E\u8BA4")}</p>
- <p>\u51FA\u5177\u65F6\u95F4\uFF1A${escapeHtml(report?.issuedAt || "\u5F85\u786E\u8BA4")}</p>
- <span class="report-audit"><i data-lucide="badge-check"></i>${escapeHtml(reportSourceStatusLabels[report?.sourceStatus] ?? "\u62A5\u544A\u72B6\u6001\u5F85\u786E\u8BA4")}</span>
- </div>
- <div class="report-notice">
- <i data-lucide="shield-alert"></i>
- <p><strong>AI \u8F85\u52A9\u89E3\u8BFB\u4EC5\u4F9B\u53C2\u8003</strong>\uFF0C\u62A5\u544A\u7ED3\u679C\u9700\u7ED3\u5408\u75C7\u72B6\u3001\u75C5\u53F2\u5E76\u7531\u533B\u751F\u5224\u65AD\u3002</p>
- </div>
- <section class="report-card" aria-labelledby="abnormalTitle">
- <div class="section-heading">
- <div>
- <p class="section-eyebrow">\u6307\u6807\u6982\u89C8</p>
- <h2 id="abnormalTitle">${abnormalMetrics.length ? `${abnormalMetrics.length} \u9879\u5F02\u5E38\u6307\u6807` : "\u672A\u6807\u8BB0\u5F02\u5E38\u6307\u6807"}</h2>
- </div>
- </div>
- <ul class="indicator-list">
- ${visibleMetrics.length ? visibleMetrics.map(renderMetric).join("") : '<li class="report-metric"><span>\u6682\u65E0\u6307\u6807\u6570\u636E</span></li>'}
- </ul>
- ${hiddenMetrics.length ? `
- <button class="button button--secondary" type="button" data-action="toggle-report-metrics">
- ${expanded ? "\u6536\u8D77\u5168\u90E8\u6307\u6807" : "\u67E5\u770B\u5168\u90E8\u6307\u6807"}
- </button>
- ` : ""}
- </section>
- <div class="report-links" aria-label="\u62A5\u544A\u76F8\u5173\u5165\u53E3">
- <button type="button" data-action="view-original-report" data-report-id="${escapeHtml(reportId)}"><i data-lucide="file-text"></i>\u67E5\u770B\u539F\u59CB\u62A5\u544A</button>
- </div>
- <button class="button button--primary report-primary" type="button" data-action="interpret-report" data-report-id="${escapeHtml(reportId)}">
- <i data-lucide="sparkles"></i>AI \u8F85\u52A9\u89E3\u8BFB
- </button>
- </section>
- `;
- };
- var safeReportText = (value, fallback) => typeof value === "string" && value.trim() ? escapeHtml(value.trim()) : fallback;
- var renderExaminationReportDetail = (report = {}) => {
- const reportId = typeof report?.reportId === "string" ? report.reportId : "";
- return `
- <section class="report-view examination-report" aria-labelledby="examinationReportTitle">
- <button class="text-button report-view__back" type="button" data-action="open-report-center">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u62A5\u544A\u4E2D\u5FC3
- </button>
- <div class="report-hero">
- <p class="section-eyebrow">\u533B\u9662\u68C0\u67E5\u62A5\u544A</p>
- <h1 id="examinationReportTitle">${safeReportText(report?.title, "\u68C0\u67E5\u540D\u79F0\u5F85\u786E\u8BA4")}</h1>
- <p>\u68C0\u67E5\u90E8\u4F4D\uFF1A${safeReportText(report?.bodyPart, "\u5F85\u786E\u8BA4")}</p>
- <p>${safeReportText(report?.hospitalName, "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${safeReportText(report?.departmentName, "\u79D1\u5BA4\u5F85\u786E\u8BA4")}</p>
- <p>\u51FA\u5177\u65F6\u95F4\uFF1A${safeReportText(report?.issuedAt, "\u5F85\u786E\u8BA4")}</p>
- </div>
- <section class="report-card" aria-labelledby="examinationFindingsTitle">
- <p class="section-eyebrow">\u68C0\u67E5\u6240\u89C1</p>
- <h2 id="examinationFindingsTitle">\u68C0\u67E5\u6240\u89C1</h2>
- <p>${safeReportText(report?.findings, "\u68C0\u67E5\u6240\u89C1\u5F85\u786E\u8BA4")}</p>
- </section>
- <section class="report-card" aria-labelledby="examinationConclusionTitle">
- <p class="section-eyebrow">\u62A5\u544A\u7ED3\u8BBA</p>
- <h2 id="examinationConclusionTitle">\u62A5\u544A\u7ED3\u8BBA</h2>
- <p>${safeReportText(report?.conclusion, "\u62A5\u544A\u7ED3\u8BBA\u5F85\u786E\u8BA4")}</p>
- </section>
- <section class="report-card examination-report__audit" aria-label="\u62A5\u544A\u5BA1\u6838\u4FE1\u606F">
- <p><strong>\u62A5\u544A\u533B\u751F\uFF1A</strong>${safeReportText(report?.reportingDoctor, "\u62A5\u544A\u533B\u751F\u5F85\u786E\u8BA4")}</p>
- <p><strong>\u5BA1\u6838\u72B6\u6001\uFF1A</strong>${safeReportText(
- report?.reviewStatus,
- reportSourceStatusLabels[report?.sourceStatus] ?? "\u5BA1\u6838\u72B6\u6001\u5F85\u786E\u8BA4"
- )}</p>
- </section>
- <div class="report-links" aria-label="\u68C0\u67E5\u62A5\u544A\u539F\u4EF6\u5165\u53E3">
- <button type="button" data-action="view-original-report" data-report-id="${escapeHtml(reportId)}">
- <i data-lucide="images"></i>\u67E5\u770B\u56FE\u50CF\u6216PDF
- </button>
- </div>
- <div class="report-notice">
- <i data-lucide="info"></i>
- <p>\u8BE5\u7C7B\u578B\u6682\u672A\u5F00\u653EAI\u8F85\u52A9\u89E3\u8BFB</p>
- </div>
- </section>
- `;
- };
- var renderOriginalReport = (report = {}) => {
- if (report?.family === "EXAM") {
- return `
- <section class="report-view original-report examination-original" aria-labelledby="originalExaminationTitle">
- <button class="text-button report-view__back" type="button" data-action="view-report" data-report-id="${escapeHtml(report?.reportId)}">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u62A5\u544A\u8BE6\u60C5
- </button>
- <div class="report-hero">
- <p class="section-eyebrow">\u533B\u9662\u68C0\u67E5\u62A5\u544A\u539F\u4EF6</p>
- <h1 id="originalExaminationTitle">${safeReportText(report?.title, "\u68C0\u67E5\u540D\u79F0\u5F85\u786E\u8BA4")}</h1>
- <p>${safeReportText(report?.originalFormat, "\u56FE\u50CF\u6216PDF")}</p>
- </div>
- <div class="report-notice">
- <i data-lucide="file-check"></i>
- <p>\u539F\u59CB\u56FE\u50CF\u6216PDF\u7531\u533B\u9662\u62A5\u544A\u7CFB\u7EDF\u63D0\u4F9B\uFF0C\u672C\u539F\u578B\u5C55\u793A\u7EDF\u4E00\u67E5\u770B\u5165\u53E3\u3002</p>
- </div>
- </section>
- `;
- }
- const metrics = Array.isArray(report?.metrics) ? report.metrics : [];
- return `
- <section class="report-view original-report" aria-labelledby="originalReportTitle">
- <button class="text-button report-view__back" type="button" data-action="view-report" data-report-id="${escapeHtml(report?.reportId)}">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u62A5\u544A\u8BE6\u60C5
- </button>
- <div class="report-hero">
- <p class="section-eyebrow">\u533B\u9662\u539F\u59CB\u62A5\u544A</p>
- <h1 id="originalReportTitle">\u539F\u59CB\u62A5\u544A\u5185\u5BB9</h1>
- <p>${escapeHtml(report?.title || "\u672A\u547D\u540D\u62A5\u544A")}</p>
- <p>${escapeHtml(report?.hospitalName || "\u533B\u9662\u4FE1\u606F\u5F85\u786E\u8BA4")} \xB7 ${escapeHtml(report?.departmentName || "\u79D1\u5BA4\u5F85\u786E\u8BA4")}</p>
- <p>\u51FA\u5177\u65F6\u95F4\uFF1A${escapeHtml(report?.issuedAt || "\u5F85\u786E\u8BA4")}</p>
- </div>
- <section class="report-card" aria-label="\u539F\u59CB\u6307\u6807">
- <ul class="indicator-list">
- ${metrics.length ? metrics.map(renderMetric).join("") : '<li class="report-metric"><span>\u6682\u65E0\u6307\u6807\u6570\u636E</span></li>'}
- </ul>
- </section>
- </section>
- `;
- };
- var renderConsentOverlay = (state = {}) => {
- const reportId = state?.overlays?.reportId;
- const report = state?.reportSourceProjections?.[reportId];
- return `
- <div class="consent-backdrop" role="presentation">
- <section class="consent-sheet" role="dialog" aria-modal="true" aria-labelledby="consentTitle" tabindex="-1">
- <div class="consent-sheet__icon"><i data-lucide="shield-check"></i></div>
- <p class="section-eyebrow">\u9690\u79C1\u6388\u6743</p>
- <h2 id="consentTitle">\u6388\u6743 AI \u8F85\u52A9\u89E3\u91CA\u672C\u6B21\u62A5\u544A</h2>
- <dl class="consent-details">
- <div><dt>\u5C31\u8BCA\u4EBA</dt><dd>${escapeHtml(patientDisplayName(state))}</dd></div>
- <div><dt>\u7528\u9014</dt><dd>\u4EC5\u7528\u4E8E\u8FD9\u4EFD\u62A5\u544A\u7684 AI \u8F85\u52A9\u89E3\u91CA</dd></div>
- <div><dt>\u8303\u56F4</dt><dd>${escapeHtml(report?.title || "\u5F53\u524D\u8840\u5E38\u89C4\u62A5\u544A")}</dd></div>
- <div><dt>\u6709\u6548\u671F</dt><dd>\u81F3 2026-07-09 23:59</dd></div>
- </dl>
- <div class="consent-note">
- <p><strong>AI \u8F85\u52A9\u6027\u8D28\uFF1A</strong>\u7528\u4E8E\u5E2E\u52A9\u7406\u89E3\u6307\u6807\uFF0C\u4E0D\u66FF\u4EE3\u533B\u751F\u8BCA\u65AD\u6216\u8BCA\u7597\u5EFA\u8BAE\u3002</p>
- <p><strong>\u98CE\u9669\u8BF4\u660E\uFF1A</strong>\u89E3\u91CA\u53EF\u80FD\u5B58\u5728\u9057\u6F0F\u6216\u504F\u5DEE\uFF0C\u8BF7\u7ED3\u5408\u75C7\u72B6\u7531\u533B\u751F\u5224\u65AD\u3002</p>
- <p><strong>\u64A4\u56DE\u65B9\u5F0F\uFF1A</strong>\u53EF\u968F\u65F6\u5728\u9690\u79C1\u8BBE\u7F6E\u4E2D\u64A4\u56DE\uFF0C\u64A4\u56DE\u540E\u4E0D\u518D\u5C55\u793A\u5386\u53F2\u89E3\u91CA\u3002</p>
- </div>
- <div class="consent-actions">
- <button class="button button--primary" type="button" data-action="grant-report-consent">\u540C\u610F\u5E76\u7EE7\u7EED</button>
- <button class="button button--secondary" type="button" data-action="reject-report-consent">\u6682\u4E0D\u6388\u6743</button>
- </div>
- </section>
- </div>
- `;
- };
- var commandPresentation = {
- PREPARED: { label: "\u51C6\u5907\u4E2D", icon: "loader-circle", tone: "pending" },
- AWAITING_CONFIRMATION: { label: "\u5F85\u786E\u8BA4", icon: "shield-check", tone: "warning" },
- EXECUTING: { label: "\u6267\u884C\u4E2D", icon: "loader-circle", tone: "pending" },
- SUCCEEDED: { label: "\u6302\u53F7\u6210\u529F", icon: "circle-check-big", tone: "success" },
- FAILED: { label: "\u6267\u884C\u5931\u8D25", icon: "circle-x", tone: "danger" },
- EXPIRED: { label: "\u5DF2\u8FC7\u671F", icon: "clock-alert", tone: "danger" },
- UNKNOWN: { label: "\u7ED3\u679C\u786E\u8BA4\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u64CD\u4F5C", icon: "circle-help", tone: "warning" },
- REJECTED: { label: "\u5DF2\u6682\u4E0D\u64CD\u4F5C", icon: "circle-minus", tone: "muted" }
- };
- var renderCommandOverlay = (command) => {
- const presentation = commandPresentation[command?.status] ?? commandPresentation.PREPARED;
- const details = command?.details ?? {};
- const statusLabel = command?.status === "SUCCEEDED" && !details.patientName ? "\u6A21\u62DF\u5B8C\u6210" : presentation.label;
- const awaiting = command?.status === "AWAITING_CONFIRMATION";
- const canRequery = command?.status === "EXPIRED" || command?.status === "FAILED";
- const canClose = command?.status === "SUCCEEDED" || command?.status === "REJECTED";
- return `
- <div class="consent-backdrop command-backdrop" role="presentation">
- <section class="consent-sheet command-sheet command-sheet--${presentation.tone}" role="dialog" aria-modal="true" aria-labelledby="commandTitle" tabindex="-1">
- <div class="command-status">
- <i data-lucide="${presentation.icon}"></i>
- <strong>${statusLabel}</strong>
- </div>
- <p class="section-eyebrow">\u53D7\u63A7\u4E1A\u52A1\u64CD\u4F5C</p>
- <h2 id="commandTitle">${escapeHtml(details.operationName ?? "\u786E\u8BA4\u6302\u53F7")}</h2>
- <dl class="command-details">
- <div><dt>\u5C31\u8BCA\u4EBA</dt><dd>${escapeHtml(details.patientName)}</dd></div>
- <div><dt>\u79D1\u5BA4 / \u533B\u751F</dt><dd>${escapeHtml(details.departmentName)} \xB7 ${escapeHtml(details.doctorName)}${escapeHtml(details.doctorTitle)}</dd></div>
- <div><dt>\u5C31\u8BCA\u65F6\u95F4</dt><dd>${escapeHtml(details.visitAt)}</dd></div>
- <div><dt>\u91D1\u989D</dt><dd>${escapeHtml(details.fee)}</dd></div>
- <div><dt>\u6709\u6548\u65F6\u95F4</dt><dd>${escapeHtml(details.countdown ?? details.expiresAt)}</dd></div>
- </dl>
- ${command?.status === "UNKNOWN" ? `
- <p class="command-guidance">\u7ED3\u679C\u786E\u8BA4\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u64CD\u4F5C\u3002\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u67E5\u8BE2\u6700\u7EC8\u7ED3\u679C\u3002</p>
- ` : ""}
- ${command?.status === "FAILED" ? `
- <p class="command-guidance">\u672C\u6B21\u64CD\u4F5C\u672A\u6210\u529F\u3002\u8BF7\u91CD\u65B0\u67E5\u8BE2\u53F7\u6E90\uFF0C\u4E0D\u76F4\u63A5\u91CD\u8BD5\u8BE5 Command\u3002</p>
- ` : ""}
- <div class="consent-actions">
- ${awaiting ? `
- <button class="button button--primary" type="button" data-action="confirm-command">\u786E\u8BA4\u6302\u53F7</button>
- <button class="button button--secondary" type="button" data-action="reject-command">\u6682\u4E0D\u64CD\u4F5C</button>
- ` : ""}
- ${canRequery ? `
- <button class="button button--secondary" type="button" data-action="requery-registration">\u91CD\u65B0\u67E5\u8BE2\u53F7\u6E90</button>
- ` : ""}
- ${canClose ? `
- <button class="button button--primary" type="button" data-action="close-command">${command.status === "SUCCEEDED" ? "\u67E5\u770B\u9996\u9875\u4E8B\u9879" : "\u5173\u95ED"}</button>
- ` : ""}
- </div>
- </section>
- </div>
- `;
- };
- var renderReportInterpreting = () => `
- <section class="report-view report-loading" aria-labelledby="loadingTitle" aria-busy="true">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="loading-orbit" aria-hidden="true"><i data-lucide="sparkles"></i></div>
- <p class="section-eyebrow">AI \u8F85\u52A9\u89E3\u91CA</p>
- <h1 id="loadingTitle">\u6B63\u5728\u6574\u7406\u62A5\u544A\u89E3\u8BFB</h1>
- <p>\u6B63\u5728\u7ED3\u5408\u53C2\u8003\u8303\u56F4\u6574\u7406\u4FBF\u4E8E\u7406\u89E3\u7684\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u5019\u3002\u7ED3\u679C\u4EC5\u4F9B\u8F85\u52A9\u7406\u89E3\uFF0C\u4E0D\u4EE3\u8868\u8BCA\u65AD\u7ED3\u8BBA\u3002</p>
- <div class="loading-progress" role="progressbar" aria-label="\u62A5\u544A\u89E3\u8BFB\u5904\u7406\u4E2D">
- <span></span>
- </div>
- <div class="loading-step"><i data-lucide="loader-circle"></i>\u5206\u6790\u4ECD\u5728\u8FDB\u884C\uFF0C\u6682\u4E0D\u63D0\u4F9B\u786E\u5B9A\u6027\u7ED3\u8BBA</div>
- </section>
- `;
- var reportUnavailablePresentation = Object.freeze({
- OCR_REVIEW_REQUIRED: Object.freeze({
- title: "\u5F53\u524D\u62A5\u544A\u6682\u4E0D\u9002\u5408\u81EA\u52A8\u89E3\u8BFB",
- description: "\u62A5\u544A\u5185\u5BB9\u9700\u8981\u4EBA\u5DE5\u6838\u5BF9\uFF0C\u60A8\u4ECD\u53EF\u67E5\u770B\u533B\u9662\u539F\u59CB\u62A5\u544A\u3002",
- actions: Object.freeze([
- Object.freeze({
- action: "view-original-report",
- label: "\u67E5\u770B\u539F\u59CB\u62A5\u544A",
- tone: "primary"
- }),
- Object.freeze({
- action: "contact-human-service",
- label: "\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1",
- tone: "secondary"
- })
- ])
- }),
- CRITICAL_NOTICE: Object.freeze({
- title: "\u62A5\u544A\u542B\u533B\u9662\u5371\u6025\u63D0\u793A",
- description: "\u8BF7\u7ACB\u5373\u6309\u533B\u9662\u901A\u77E5\u8054\u7CFB\u533B\u52A1\u4EBA\u5458\uFF0C\u5E76\u4F18\u5148\u9075\u5FAA\u533B\u9662\u65E2\u6709\u5904\u7F6E\u6D41\u7A0B\u3002",
- actions: Object.freeze([
- Object.freeze({
- action: "contact-human-service",
- label: "\u7ACB\u5373\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1",
- tone: "primary"
- }),
- Object.freeze({
- action: "view-original-report",
- label: "\u67E5\u770B\u539F\u59CB\u62A5\u544A",
- tone: "secondary"
- })
- ])
- }),
- AI_UNAVAILABLE: Object.freeze({
- title: "AI\u8F85\u52A9\u89E3\u8BFB\u6682\u4E0D\u53EF\u7528",
- description: "\u533B\u9662\u539F\u59CB\u62A5\u544A\u4ECD\u53EF\u6B63\u5E38\u67E5\u770B\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"
- }),
- REPORT_INVALIDATED: Object.freeze({
- title: "\u533B\u9662\u5DF2\u66F4\u65B0\u6216\u64A4\u56DE\u62A5\u544A",
- description: "\u539FAI\u8F85\u52A9\u89E3\u91CA\u5DF2\u5931\u6548\u3002\u6700\u65B0\u7248\u672C\u5C1A\u672A\u540C\u6B65\u5B8C\u6210\uFF0C\u8BF7\u8FD4\u56DE\u62A5\u544A\u4E2D\u5FC3\u67E5\u770B\u72B6\u6001\u3002",
- actions: Object.freeze([
- Object.freeze({
- action: "open-report-center",
- label: "\u67E5\u770B\u62A5\u544A\u72B6\u6001",
- tone: "primary"
- }),
- Object.freeze({
- action: "contact-human-service",
- label: "\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1",
- tone: "secondary"
- })
- ])
- }),
- UNSUPPORTED: Object.freeze({
- title: "\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u89E3\u8BFB",
- description: "\u60A8\u53EF\u4EE5\u67E5\u770B\u539F\u59CB\u62A5\u544A\u6216\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1\u3002"
- })
- });
- var renderReportUnavailable = ({
- reason,
- reportId,
- serviceNotice
- } = {}) => {
- const presentation = Object.hasOwn(reportUnavailablePresentation, reason) ? reportUnavailablePresentation[reason] : reportUnavailablePresentation.UNSUPPORTED;
- const safeReportId = typeof reportId === "string" && /^[A-Za-z0-9._-]+$/.test(reportId) ? reportId : "";
- const actions = presentation.actions ?? [
- {
- action: "view-original-report",
- label: presentation.primaryActionLabel ?? "\u67E5\u770B\u539F\u59CB\u62A5\u544A",
- tone: "primary"
- },
- {
- action: "contact-human-service",
- label: "\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1",
- tone: "secondary"
- }
- ];
- return `
- <section class="report-view report-unavailable" role="status" aria-labelledby="reportUnavailableTitle">
- <div class="report-unavailable__icon" aria-hidden="true"><i data-lucide="shield-alert"></i></div>
- <h1 id="reportUnavailableTitle">${escapeHtml(presentation.title)}</h1>
- <p>${escapeHtml(presentation.description)}</p>
- ${typeof serviceNotice === "string" && serviceNotice ? `
- <p class="result-service-notice" role="status">${escapeHtml(serviceNotice)}</p>
- ` : ""}
- <div class="report-unavailable__actions">
- ${actions.map(({ action, label, tone }) => `
- <button class="button button--${tone}" type="button" data-action="${escapeHtml(action)}" data-report-id="${escapeHtml(safeReportId)}">${escapeHtml(label)}</button>
- `).join("")}
- </div>
- </section>
- `;
- };
- var renderReportServiceOverlay = (state = {}) => {
- const reason = state?.overlays?.reportUnavailableReason;
- const presentation = Object.hasOwn(reportUnavailablePresentation, reason) ? reportUnavailablePresentation[reason] : reportUnavailablePresentation.UNSUPPORTED;
- return `
- <div class="consent-backdrop report-service-backdrop" role="presentation">
- <section class="consent-sheet report-service-sheet" role="dialog" aria-modal="true" aria-labelledby="reportServiceTitle" tabindex="-1">
- <div class="consent-sheet__icon"><i data-lucide="headphones"></i></div>
- <p class="section-eyebrow">\u4EBA\u5DE5\u534F\u52A9</p>
- <h2 id="reportServiceTitle">\u4EBA\u5DE5\u670D\u52A1\u7A97\u53E3</h2>
- <p>${escapeHtml(presentation.title)}</p>
- <p>${escapeHtml(presentation.description)}</p>
- <div class="consent-note">
- <p>\u533B\u9662\u670D\u52A1\u4EBA\u5458\u53EF\u534F\u52A9\u6838\u5BF9\u62A5\u544A\u72B6\u6001\u548C\u539F\u59CB\u5185\u5BB9\u3002</p>
- <p>\u672C\u539F\u578B\u4EC5\u6F14\u793A\u670D\u52A1\u5165\u53E3\uFF0C\u4E0D\u4F1A\u771F\u5B9E\u53D1\u8D77\u7535\u8BDD\u6216\u5728\u7EBF\u4F1A\u8BDD\u3002</p>
- </div>
- <div class="consent-actions">
- <button class="button button--primary" type="button" data-action="close-report-service">\u77E5\u9053\u4E86</button>
- </div>
- </section>
- </div>
- `;
- };
- var renderReportInterpretationResult = (result, options = {}) => {
- const interpretation = result?.interpretation;
- if (!interpretation || typeof interpretation !== "object" || Array.isArray(interpretation)) {
- return "";
- }
- const highlights = Array.isArray(interpretation.highlights) ? interpretation.highlights : [];
- const explanations = Array.isArray(interpretation.explanations) ? interpretation.explanations : [];
- const commonFactors = Array.isArray(interpretation.commonFactors) ? interpretation.commonFactors : explanations.map((item) => item?.commonFactors).filter(Boolean);
- const canDo = Array.isArray(interpretation.canDo) ? interpretation.canDo : [];
- const evidence = Array.isArray(interpretation.evidence) ? interpretation.evidence : [];
- const reportId = typeof result?.reportId === "string" ? result.reportId : "";
- const reportAction = (action) => `data-action="${action}" data-report-id="${escapeHtml(reportId)}"`;
- const serviceNotice = typeof options?.serviceNotice === "string" ? options.serviceNotice : "";
- const feedbackCategories = [
- ["HELPFUL", "\u6709\u5E2E\u52A9"],
- ["METRIC_INACCURATE", "\u6307\u6807\u89E3\u91CA\u4E0D\u51C6\u786E"],
- ["HARD_TO_UNDERSTAND", "\u5185\u5BB9\u96BE\u7406\u89E3"],
- ["DIFFERS_FROM_DOCTOR", "\u4E0E\u533B\u751F\u8BF4\u660E\u4E0D\u4E00\u81F4"],
- ["OTHER", "\u5176\u4ED6\u95EE\u9898"]
- ];
- return `
- <section class="report-view result-view" aria-labelledby="resultTitle">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="result-summary">
- <p class="section-eyebrow">AI \u8F85\u52A9\u89E3\u91CA \xB7 \u7ED3\u679C\u7248\u672C ${escapeHtml(result.resultVersion)}</p>
- <h2>\u7ED3\u679C\u6982\u62EC</h2>
- <h1 id="resultTitle">${escapeHtml(interpretation.headline)}</h1>
- <p>${escapeHtml(interpretation.summary)}</p>
- </div>
- <div class="safety-callout">
- <strong><i data-lucide="shield-check"></i>\u5B89\u5168\u63D0\u793A</strong>
- <p>${escapeHtml(interpretation.safety)}</p>
- </div>
- <section class="result-card">
- <h2>\u9700\u8981\u5173\u6CE8</h2>
- <ul>${highlights.map((item) => `
- <li>
- <span>${escapeHtml(item?.name)}</span>
- <strong>${escapeHtml(item?.value)}</strong>
- <em>${escapeHtml(item?.flag)}</em>
- </li>
- `).join("")}</ul>
- </section>
- <section class="result-card">
- <h2>\u6307\u6807\u89E3\u91CA</h2>
- ${explanations.map((item) => `
- <article class="explanation-item">
- <h3>${escapeHtml(item.indicator)}</h3>
- <p>${escapeHtml(item.explanation)}</p>
- </article>
- `).join("")}
- </section>
- <section class="result-card">
- <h2>\u5E38\u89C1\u76F8\u5173\u56E0\u7D20</h2>
- <ul>${commonFactors.map((factor) => `<li>${escapeHtml(factor)}</li>`).join("")}</ul>
- <p class="result-factors-note">\u4E0A\u8FF0\u56E0\u7D20\u53EA\u662F\u5E2E\u52A9\u7406\u89E3\u6307\u6807\u7684\u5E38\u89C1\u60C5\u51B5\uFF0C\u4E0D\u4EE3\u8868\u4F60\u60A3\u6709\u76F8\u5E94\u75BE\u75C5\uFF0C\u4E5F\u4E0D\u80FD\u4F5C\u4E3A\u8BCA\u65AD\u7ED3\u679C\u3002</p>
- </section>
- <section class="result-card">
- <h2>\u53EF\u4EE5\u600E\u4E48\u505A</h2>
- <ul>${canDo.map((action) => `<li>${escapeHtml(action)}</li>`).join("")}</ul>
- </section>
- <section class="result-card result-evidence">
- <h2>\u5185\u5BB9\u4F9D\u636E</h2>
- <ul>${evidence.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
- <p>\u7ED3\u679C\u7248\u672C ${escapeHtml(result.resultVersion)} \xB7 \u5BF9\u5E94\u5F53\u524D\u533B\u9662\u539F\u59CB\u62A5\u544A</p>
- </section>
- ${reportId ? `
- <section class="result-card result-operations" aria-labelledby="resultOperationsTitle">
- <h2 id="resultOperationsTitle">\u9700\u8981\u8FDB\u4E00\u6B65\u5E2E\u52A9</h2>
- ${serviceNotice ? `
- <p class="result-service-notice" role="status">${escapeHtml(serviceNotice)}</p>
- ` : ""}
- <div class="result-action-grid">
- <button class="button button--secondary" type="button" ${reportAction("view-original-report")}>\u67E5\u770B\u539F\u59CB\u62A5\u544A</button>
- <button class="button button--secondary" type="button" ${reportAction("consult-ordering-doctor")}>\u54A8\u8BE2\u5F00\u5355\u533B\u751F</button>
- <button class="button button--secondary" type="button" ${reportAction("online-consultation")}>\u5728\u7EBF\u54A8\u8BE2</button>
- <button class="button button--secondary" type="button" ${reportAction("contact-human-service")}>\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1</button>
- <button class="button button--secondary" type="button" ${reportAction("open-report-feedback")}>\u63D0\u4EA4\u53CD\u9988</button>
- <button class="button button--secondary result-revoke" type="button" ${reportAction("revoke-report-consent")}>\u64A4\u56DE\u6388\u6743</button>
- </div>
- </section>
- ` : ""}
- ${reportId && options?.feedbackOpen ? `
- <section class="result-card report-feedback" aria-labelledby="reportFeedbackTitle">
- <h2 id="reportFeedbackTitle">\u8FD9\u6B21\u89E3\u91CA\u5BF9\u4F60\u6709\u5E2E\u52A9\u5417\uFF1F</h2>
- <p>\u8BF7\u9009\u62E9\u6700\u7B26\u5408\u7684\u4E00\u9879\uFF0C\u53CD\u9988\u4EC5\u7528\u4E8E\u6539\u8FDB\u672C\u6B21\u8F85\u52A9\u89E3\u91CA\u3002</p>
- <div class="feedback-options">
- ${feedbackCategories.map(([category, label]) => `
- <button
- class="button button--secondary"
- type="button"
- data-action="submit-report-feedback"
- data-report-id="${escapeHtml(reportId)}"
- data-filter="${category}"
- >${label}</button>
- `).join("")}
- </div>
- </section>
- ` : ""}
- ${options?.feedbackSubmitted ? `
- <p class="report-feedback-confirmation" role="status">\u611F\u8C22\u53CD\u9988\uFF0C\u5DF2\u8BB0\u5F55\u4F60\u7684\u9009\u62E9\u3002</p>
- ` : ""}
- </section>
- `;
- };
- var renderQueueStatus = (state, appointmentId) => {
- const queue = (Array.isArray(state?.patientWorkItems) ? state.patientWorkItems : []).find((item) => item?.type === "WAITING_QUEUE" && item.appointmentId === appointmentId);
- if (!queue) {
- return `
- <section class="report-view queue-view" aria-labelledby="queueTitle">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="empty-card">
- <i data-lucide="clock-3"></i>
- <div>
- <h1 id="queueTitle">\u5019\u8BCA\u4FE1\u606F\u5F85\u66F4\u65B0</h1>
- <p>\u5B8C\u6210\u7B7E\u5230\u540E\uFF0C\u6392\u961F\u53F7\u548C\u9884\u8BA1\u65F6\u95F4\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002</p>
- </div>
- </div>
- </section>
- `;
- }
- const updatedAt = typeof queue.updatedAt === "string" ? queue.updatedAt.slice(11, 16) : "\u5F85\u66F4\u65B0";
- return `
- <section class="report-view queue-view" aria-labelledby="queueTitle">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="queue-hero">
- <p class="section-eyebrow">\u795E\u7ECF\u5185\u79D1 \xB7 \u5B9E\u65F6\u5019\u8BCA</p>
- <h1 id="queueTitle">\u6392\u961F\u53F7 ${escapeHtml(queue.queueNumber ?? "\u5F85\u66F4\u65B0")}</h1>
- <p>${escapeHtml(queue.room ?? "\u8BCA\u5BA4\u5F85\u5206\u914D")}</p>
- </div>
- ${queue.delayed ? `
- <div class="queue-delay" role="status">
- <i data-lucide="triangle-alert"></i>
- <span>\u5019\u8BCA\u65F6\u95F4\u53EF\u80FD\u5EF6\u540E\uFF0C\u8BF7\u7559\u610F\u53EB\u53F7\u66F4\u65B0\u3002</span>
- </div>
- ` : ""}
- <div class="queue-metrics">
- <article><span>\u524D\u65B9\u4EBA\u6570</span><strong>\u524D\u65B9 ${escapeHtml(queue.peopleAhead ?? 0)} \u4EBA</strong></article>
- <article><span>\u9884\u8BA1\u65F6\u95F4</span><strong>\u9884\u8BA1\u7B49\u5F85 ${escapeHtml(queue.estimatedMinutes ?? 0)} \u5206\u949F</strong></article>
- </div>
- <div class="queue-detail">
- <p><i data-lucide="door-open"></i><span>\u5F53\u524D\u8BCA\u5BA4</span><strong>${escapeHtml(queue.room ?? "\u5F85\u5206\u914D")}</strong></p>
- <p><i data-lucide="refresh-cw"></i><span>\u961F\u5217\u66F4\u65B0\u65F6\u95F4</span><strong>${escapeHtml(updatedAt)}</strong></p>
- </div>
- <button class="button button--secondary queue-route" type="button" data-action="open-route" data-appointment-id="${escapeHtml(appointmentId)}">
- <i data-lucide="navigation"></i>\u9662\u5185\u5BFC\u822A
- </button>
- </section>
- `;
- };
- var renderRouteView = (location) => `
- <section class="report-view route-view" aria-labelledby="routeTitle">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="route-map" aria-hidden="true">
- <i data-lucide="map-pinned"></i>
- <span></span><span></span><span></span>
- </div>
- <div class="module-header">
- <p class="section-eyebrow">\u9662\u5185\u8DEF\u7EBF</p>
- <h1 id="routeTitle">${escapeHtml(location ?? "\u95E8\u8BCA\u4E09\u697C\u795E\u7ECF\u5185\u79D1\u8BCA\u533A")}</h1>
- <p>\u4ECE\u95E8\u8BCA\u5927\u5385\u4E58 2 \u53F7\u7535\u68AF\u81F3\u4E09\u697C\uFF0C\u51FA\u7535\u68AF\u540E\u53F3\u8F6C\u7EA6 30 \u7C73\u3002</p>
- </div>
- <div class="queue-detail">
- <p><i data-lucide="footprints"></i><span>\u9884\u8BA1\u6B65\u884C</span><strong>4 \u5206\u949F</strong></p>
- <p><i data-lucide="accessibility"></i><span>\u65E0\u969C\u788D\u8DEF\u7EBF</span><strong>\u53EF\u7528</strong></p>
- </div>
- </section>
- `;
- var pageHeader = (eyebrow, title, description) => `
- <div class="module-header">
- <p class="section-eyebrow">${eyebrow}</p>
- <h1>${title}</h1>
- <p>${description}</p>
- </div>
- `;
- var careGroupDefinitions = [
- {
- title: "\u5F85\u529E\u7406",
- statuses: ["ACTION_REQUIRED", "UPCOMING"],
- fallback: "\u95E8\u8BCA\u8D39\u7528\u5F85\u786E\u8BA4"
- },
- {
- title: "\u8FDB\u884C\u4E2D",
- statuses: ["IN_PROGRESS"],
- fallback: "\u4ECA\u65E5\u5C31\u533B\u65C5\u7A0B"
- },
- {
- title: "\u5DF2\u5B8C\u6210",
- statuses: ["COMPLETED"],
- fallback: "\u8EAB\u4EFD\u4FE1\u606F\u5DF2\u6838\u9A8C"
- },
- {
- title: "\u9700\u8981\u4EBA\u5DE5\u5904\u7406",
- statuses: ["MANUAL_REQUIRED"],
- fallback: "\u4EBA\u5DE5\u670D\u52A1\u968F\u65F6\u53EF\u7528"
- }
- ];
- var renderCareOverview = (state) => {
- const items = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.filter((item) => item && typeof item === "object") : [];
- return `
- <section class="module-page" aria-labelledby="careOverviewTitle">
- ${pageHeader("\u5C31\u533B\u603B\u89C8", "\u6211\u7684\u5C31\u533B", "\u6309\u529E\u7406\u72B6\u6001\u96C6\u4E2D\u67E5\u770B\u95E8\u8BCA\u3001\u68C0\u67E5\u548C\u8BCA\u540E\u4E8B\u9879\u3002")}
- <div class="care-groups">
- ${careGroupDefinitions.map((group) => {
- const matches = items.filter((item) => group.statuses.includes(item.status));
- const visible = matches.length > 0 ? matches : [{ title: group.fallback, description: "\u8FDB\u5165\u8BE6\u60C5\u67E5\u770B\u4E0B\u4E00\u6B65" }];
- return `
- <section class="care-group">
- <div class="section-heading">
- <h2>${group.title}</h2>
- <span>${visible.length}</span>
- </div>
- ${visible.slice(0, 3).map((item) => `
- <article class="compact-row">
- <span class="compact-row__icon"><i data-lucide="clipboard-check"></i></span>
- <div>
- <strong>${escapeHtml(item.title)}</strong>
- <p>${escapeHtml(item.description ?? "\u72B6\u6001\u548C\u53EF\u7528\u64CD\u4F5C\u6301\u7EED\u66F4\u65B0")}</p>
- </div>
- <i data-lucide="chevron-right"></i>
- </article>
- `).join("")}
- </section>
- `;
- }).join("")}
- </div>
- </section>
- `;
- };
- var recordEntries = [
- { id: "lab-reports", label: "\u68C0\u9A8C\u62A5\u544A", meta: "\u8840\u5E38\u89C4 \xB7 3 \u9879\u5F02\u5E38", filter: "abnormal", icon: "flask-conical", action: "open-report-center", reportFamily: "LAB" },
- { id: "exam-reports", label: "\u68C0\u67E5\u62A5\u544A", meta: "\u5F71\u50CF\u4E0E\u8D85\u58F0\u62A5\u544A", filter: "all", icon: "scan-line", action: "open-report-center", reportFamily: "EXAM" },
- { id: "visits", label: "\u95E8\u8BCA\u8BB0\u5F55", meta: "\u5386\u6B21\u63A5\u8BCA\u4E0E\u8BCA\u540E\u8BB0\u5F55", filter: "all", icon: "notebook-tabs" },
- { id: "medication", label: "\u5904\u65B9\u4E0E\u7528\u836F", meta: "\u5904\u65B9\u3001\u7528\u836F\u63D0\u9192", filter: "all", icon: "pill" },
- { id: "allergies", label: "\u8FC7\u654F\u53F2", meta: "\u6682\u672A\u8BB0\u5F55\u836F\u7269\u8FC7\u654F", filter: "all", icon: "shield-alert" },
- { id: "consents", label: "\u6388\u6743\u8BB0\u5F55", meta: "\u67E5\u770B\u548C\u64A4\u56DE\u6570\u636E\u6388\u6743", filter: "unread", icon: "file-lock-2" }
- ];
- var renderRecords = (state) => {
- const activeFilter = ["all", "abnormal", "unread"].includes(
- state?.recordsFilter
- ) ? state.recordsFilter : "all";
- const visible = activeFilter === "all" ? recordEntries : recordEntries.filter((entry) => entry.filter === activeFilter);
- return `
- <section class="module-page" aria-labelledby="recordsTitle">
- ${pageHeader("\u5065\u5EB7\u6863\u6848", "\u6211\u7684\u5065\u5EB7\u8BB0\u5F55", "\u62A5\u544A\u3001\u95E8\u8BCA\u8BB0\u5F55\u548C\u6388\u6743\u4FE1\u606F\u7EDF\u4E00\u5F52\u6863\u3002")}
- <div class="filter-tabs" role="group" aria-label="\u62A5\u544A\u7B5B\u9009">
- ${[
- ["all", "\u5168\u90E8"],
- ["abnormal", "\u5F02\u5E38"],
- ["unread", "\u672A\u89E3\u8BFB"]
- ].map(([id, label]) => `
- <button type="button" data-action="set-record-filter" data-filter="${id}" aria-pressed="${activeFilter === id}">
- ${label}
- </button>
- `).join("")}
- </div>
- <div class="module-list">
- ${visible.map((entry) => `
- <${entry.action ? "button" : "article"} class="compact-row"${entry.action ? ` type="button" data-action="${entry.action}" data-filter="${entry.reportFamily}"` : ""}>
- <span class="compact-row__icon"><i data-lucide="${entry.icon}"></i></span>
- <div><strong>${entry.label}</strong><p>${entry.meta}</p></div>
- <i data-lucide="chevron-right"></i>
- </${entry.action ? "button" : "article"}>
- `).join("")}
- </div>
- </section>
- `;
- };
- var profileEntries = [
- ["\u5C31\u8BCA\u4EBA\u7BA1\u7406", "users-round"],
- ["\u6388\u6743\u4E2D\u5FC3", "shield-check"],
- ["\u6D88\u606F\u8BBE\u7F6E", "bell-ring"],
- ["\u5E38\u7528\u9662\u533A", "hospital"],
- ["\u533B\u4FDD\u548C\u652F\u4ED8", "badge-cent"],
- ["\u7535\u5B50\u7968\u636E", "receipt-text"],
- ["\u4EBA\u5DE5\u5BA2\u670D", "headphones"],
- ["\u5173\u4E8E AI", "sparkles"]
- ];
- var renderProfile = () => `
- <section class="module-page" aria-labelledby="profileTitle">
- ${pageHeader("\u4E2A\u4EBA\u4E2D\u5FC3", "\u6211\u7684", "\u7BA1\u7406\u5C31\u8BCA\u4EBA\u3001\u9690\u79C1\u6388\u6743\u548C\u5E38\u7528\u670D\u52A1\u8BBE\u7F6E\u3002")}
- <div class="module-list">
- ${profileEntries.map(([label, icon]) => `
- <button class="compact-row compact-row--button" type="button">
- <span class="compact-row__icon"><i data-lucide="${icon}"></i></span>
- <strong>${label}</strong>
- <i data-lucide="chevron-right"></i>
- </button>
- `).join("")}
- </div>
- </section>
- `;
- var messageCategories = ["\u62A5\u544A", "\u5C31\u533B", "\u7F34\u8D39", "\u968F\u8BBF", "\u7CFB\u7EDF"];
- var renderMessages = (state) => {
- const messages = Array.isArray(state?.messages) ? state.messages.filter((message) => message && typeof message === "object") : [];
- return `
- <section class="module-page" aria-labelledby="messagesTitle">
- ${pageHeader("\u6D88\u606F\u4E2D\u5FC3", "\u6D88\u606F", "\u6309\u670D\u52A1\u7C7B\u578B\u5B9A\u4F4D\u5230\u5BF9\u5E94\u5C31\u533B\u4E8B\u9879\uFF0C\u4E0D\u4F1A\u76F4\u63A5\u6267\u884C\u529E\u7406\u64CD\u4F5C\u3002")}
- <div class="message-categories" aria-label="\u6D88\u606F\u7C7B\u578B">
- ${messageCategories.map((category) => `<span>${category}</span>`).join("")}
- </div>
- <div class="module-list">
- ${messages.length > 0 ? messages.map((message) => `
- <button class="compact-row compact-row--button" type="button" data-action="open-message" data-message-id="${escapeHtml(message.id)}">
- <span class="compact-row__icon"><i data-lucide="${message.type === "REPORT" ? "file-chart-column" : "bell"}"></i></span>
- <div>
- <strong>${escapeHtml(message.title ?? message.text ?? "\u670D\u52A1\u6D88\u606F")}</strong>
- <p>${escapeHtml(message.createdAt ?? "\u521A\u521A\u66F4\u65B0")}</p>
- </div>
- <i data-lucide="chevron-right"></i>
- </button>
- `).join("") : `
- <div class="empty-card">
- <i data-lucide="inbox"></i>
- <div><strong>\u6682\u65E0\u65B0\u6D88\u606F</strong><p>\u65B0\u7684\u62A5\u544A\u548C\u5C31\u533B\u63D0\u9192\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002</p></div>
- </div>
- `}
- </div>
- </section>
- `;
- };
- var renderSystemState = (type, state) => {
- const cachedTaskCount = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.length : 0;
- const definitions = {
- INITIAL_LOADING: ["\u6B63\u5728\u52A0\u8F7D\u60A3\u8005\u670D\u52A1\u53F0", "\u6B63\u5728\u540C\u6B65\u6700\u65B0\u5C31\u533B\u4E8B\u9879\uFF0C\u8BF7\u7A0D\u5019\u3002", "loader-circle"],
- EMPTY: ["\u6682\u65E0\u76F8\u5173\u5185\u5BB9", "\u53EF\u4EE5\u8FD4\u56DE\u9996\u9875\u6216\u5C1D\u8BD5\u5176\u4ED6\u670D\u52A1\u3002", "inbox"],
- OFFLINE: ["\u7F51\u7EDC\u8FDE\u63A5\u4E0D\u53EF\u7528", `\u5DF2\u4FDD\u7559 ${cachedTaskCount} \u9879\u672C\u5730\u4EFB\u52A1\uFF0C\u8054\u7F51\u540E\u5C06\u81EA\u52A8\u66F4\u65B0\u3002`, "wifi-off"],
- DEGRADED: ["\u90E8\u5206\u667A\u80FD\u670D\u52A1\u6682\u4E0D\u53EF\u7528", "\u57FA\u7840\u5C31\u533B\u4E8B\u9879\u4ECD\u53EF\u67E5\u770B\uFF0C\u4E5F\u53EF\u4EE5\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1\u3002", "shield-alert"],
- QUERY_FAILED: ["\u67E5\u8BE2\u5931\u8D25", "\u8BF7\u7A0D\u540E\u91CD\u65B0\u67E5\u8BE2\uFF0C\u65E7\u7ED3\u679C\u4E0D\u4F1A\u7528\u4E8E\u7EE7\u7EED\u529E\u7406\u3002", "search-x"],
- COMMAND_FAILED: ["\u529E\u7406\u5931\u8D25", "\u672C\u6B21\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u6216\u8054\u7CFB\u4EBA\u5DE5\u670D\u52A1\u3002", "circle-x"],
- COMMAND_UNKNOWN: ["\u7ED3\u679C\u786E\u8BA4\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u64CD\u4F5C", "\u6B63\u5728\u6838\u9A8C\u533B\u9662\u7CFB\u7EDF\u7ED3\u679C\uFF0C\u53EF\u4EE5\u7A0D\u540E\u67E5\u770B\u72B6\u6001\u3002", "circle-ellipsis"]
- };
- const [title, description, icon] = definitions[type] ?? definitions.EMPTY;
- return `
- <section class="system-state" role="status">
- <span><i data-lucide="${icon}"></i></span>
- <h1>${title}</h1>
- <p>${description}</p>
- <button class="button button--secondary" type="button" data-action="go-home">\u8FD4\u56DE\u9996\u9875</button>
- </section>
- `;
- };
- var renderScenarioPanel = (state) => {
- const activeScenario = demoScenarios.includes(state?.demoScenario) ? state.demoScenario : "EMPTY_HOME";
- const activeTask = state?.activeAgentTaskProjection;
- const commands = Object.values(state?.commandProjections ?? {});
- const latestFixture = state?.appliedProjectionFixtureIds?.at?.(-1) ?? "\u65E0";
- const renderScenarioGroup = (groupId, title, scenarios) => `
- <section
- class="scenario-group"
- aria-labelledby="${groupId}"
- >
- <h2 id="${groupId}">${title}</h2>
- <div class="scenario-list">
- ${scenarios.map((scenario) => `
- <button type="button" data-action="load-scenario" data-scenario="${scenario}" aria-pressed="${scenario === activeScenario}">
- <span>${String(demoScenarios.indexOf(scenario) + 1).padStart(2, "0")}</span>
- ${scenarioLabels[scenario]}
- </button>
- `).join("")}
- </div>
- </section>
- `;
- return `
- <div class="scenario-controller">
- <div class="scenario-controller__header">
- <div>
- <p>\u65B9\u6848\u6F14\u793A</p>
- <strong>${escapeHtml(scenarioLabels[activeScenario])}</strong>
- </div>
- <button type="button" data-action="toggle-scenario-panel" aria-label="\u9690\u85CF\u6F14\u793A\u63A7\u5236\u5668">
- <i data-lucide="panel-right-close"></i>
- </button>
- </div>
- <div class="scenario-actions">
- <button type="button" data-action="previous-scenario"><i data-lucide="arrow-left"></i>\u4E0A\u4E00\u6B65</button>
- <button type="button" data-action="next-scenario">\u4E0B\u4E00\u6B65<i data-lucide="arrow-right"></i></button>
- <button type="button" data-action="reset-scenario"><i data-lucide="rotate-ccw"></i>\u91CD\u7F6E</button>
- </div>
- <div class="scenario-groups" aria-label="\u6F14\u793A\u573A\u666F">
- ${renderScenarioGroup(
- "reportClosureScenarios",
- "\u62A5\u544A\u95ED\u73AF",
- demoScenarioGroups.REPORT_CLOSURE
- )}
- ${renderScenarioGroup(
- "careClosureScenarios",
- "\u5C31\u533B\u95ED\u73AF",
- demoScenarioGroups.CARE_CLOSURE
- )}
- </div>
- <div class="viewport-switcher" aria-label="\u6F14\u793A\u89C6\u53E3">
- <button type="button" data-action="set-demo-viewport" data-viewport="390">390\xD7844</button>
- <button type="button" data-action="set-demo-viewport" data-viewport="430">430\xD7932</button>
- </div>
- <dl class="projection-summary">
- <div><dt>\u5F53\u524D fixture</dt><dd>${escapeHtml(latestFixture)}</dd></div>
- <div><dt>Agent task</dt><dd>${escapeHtml(activeTask ? `${activeTask.type}/${activeTask.stage}` : "\u65E0")}</dd></div>
- <div><dt>PatientWorkItem</dt><dd>${escapeHtml(state?.patientWorkItems?.length ?? 0)} \u9879</dd></div>
- <div><dt>Command</dt><dd>${escapeHtml(commands.map((item) => `${item.commandId}/${item.status}`).join(", ") || "\u65E0")}</dd></div>
- </dl>
- <p class="scenario-shortcuts">\u2190/\u2192 \u5207\u6362 \xB7 R \u91CD\u7F6E \xB7 P \u663E\u793A/\u9690\u85CF</p>
- </div>
- `;
- };
- var renderCurrentView = (state) => {
- const { reportId, reportView } = state?.overlays ?? {};
- if (state?.systemState === "AI_INTERPRETING") {
- return renderReportInterpreting();
- }
- if (state?.systemState) {
- return renderSystemState(state.systemState, state);
- }
- if (state?.overlays?.journeyView === "CHECKIN_CONFIRMATION") {
- return `
- <section class="report-view checkin-view" aria-labelledby="checkinTitle">
- <button class="text-button report-view__back" type="button" data-action="go-home">
- <i data-lucide="arrow-left"></i>\u8FD4\u56DE\u9996\u9875
- </button>
- <div class="report-hero">
- <p class="section-eyebrow">\u5230\u9662\u7B7E\u5230</p>
- <h1 id="checkinTitle">\u8BF7\u786E\u8BA4\u672C\u4EBA\u5DF2\u5230\u8FBE\u8BCA\u533A</h1>
- <p>\u7B7E\u5230\u9700\u8981\u60A3\u8005\u660E\u786E\u786E\u8BA4\uFF1B\u672C\u9875\u9762\u4E0D\u4F1A\u6839\u636E\u4F4D\u7F6E\u81EA\u52A8\u5B8C\u6210\u7B7E\u5230\u3002</p>
- </div>
- <div class="report-notice">
- <i data-lucide="map-pin-check"></i>
- <p>\u786E\u8BA4\u540E\u4ECD\u4EE5\u533B\u9662\u7B7E\u5230\u63A5\u53E3\u8FD4\u56DE\u7ED3\u679C\u4E3A\u51C6\u3002</p>
- </div>
- <div class="task-card__actions">
- <button class="button button--primary" type="button" data-action="submit-checkin" data-appointment-id="${escapeHtml(state.overlays.appointmentId)}">
- \u786E\u8BA4\u7B7E\u5230
- </button>
- <button class="button button--secondary" type="button" data-action="go-home">
- \u6682\u4E0D\u7B7E\u5230
- </button>
- </div>
- </section>
- `;
- }
- if (state?.overlays?.journeyView === "ROUTE") {
- const journey = state.journeyProjections?.find(
- (item) => item?.appointmentId === state.overlays.appointmentId
- );
- const checkin = state.patientWorkItems?.find(
- (item) => item?.type === "CHECK_IN" && item.appointmentId === state.overlays.appointmentId
- );
- return renderRouteView(
- journey?.location ?? checkin?.location ?? journey?.room
- );
- }
- if (state?.overlays?.journeyView === "QUEUE" && state?.overlays?.appointmentId) {
- return renderQueueStatus(state, state.overlays.appointmentId);
- }
- if (reportView === "REPORT_DETAIL" && reportId) {
- const report = state.reportSourceProjections?.[reportId];
- return report?.family === "EXAM" ? renderExaminationReportDetail(report) : renderReportDetail(report, Boolean(state.overlays.reportExpanded));
- }
- if (reportView === "ORIGINAL_REPORT" && reportId) {
- return renderOriginalReport(state.reportSourceProjections?.[reportId]);
- }
- if (reportView === "REPORT_UNAVAILABLE" && reportId) {
- return renderReportUnavailable({
- reason: state?.overlays?.reportUnavailableReason,
- reportId,
- serviceNotice: state?.overlays?.reportServiceNotice
- });
- }
- if (reportView === "INTERPRETING" && reportId) {
- return renderReportInterpreting();
- }
- if (reportView === "INTERPRETED" && hasValidReportConsent(state, reportId)) {
- return renderReportInterpretationResult(
- selectValidReportResult(state, reportId),
- {
- feedbackOpen: state?.overlays?.reportFeedbackOpen === true,
- feedbackSubmitted: state?.overlays?.reportFeedbackSubmitted === true,
- serviceNotice: state?.overlays?.reportServiceNotice
- }
- );
- }
- if (state?.activeTab === "assistant") {
- return renderAssistant(state);
- }
- if (state?.activeTab === "care") {
- return renderCareOverview(state);
- }
- if (state?.activeTab === "records") {
- return state?.overlays?.reportView === "REPORT_CENTER" ? renderReportCenter(state) : renderRecords(state);
- }
- if (state?.activeTab === "profile") {
- return renderProfile(state);
- }
- if (state?.activeTab === "messages") {
- return renderMessages(state);
- }
- return renderHome(state);
- };
- var modalOverlayIdentity = null;
- var modalOverlayDocument = null;
- var consentFocusReturn = null;
- var scenarioPanelVisible = true;
- var demoViewport = "390";
- var hasConsentDialog = (state) => typeof state?.overlays?.consentId === "string" && state.overlays.consentId.startsWith("consent-ai-");
- var hasCommandDialog = (state) => Boolean(
- state?.overlays?.commandId && state?.commandProjections?.[state.overlays.commandId]?.status === "AWAITING_CONFIRMATION"
- );
- var hasReportServiceDialog = (state) => state?.overlays?.reportView === "REPORT_UNAVAILABLE" && state?.overlays?.reportServiceNotice === "\u5DF2\u4E3A\u4F60\u6253\u5F00\u4EBA\u5DE5\u670D\u52A1\u5165\u53E3\u3002";
- var focusModalDialog = (overlayRoot) => {
- const dialog = overlayRoot?.querySelector?.('[role="dialog"]');
- const firstButton = dialog?.querySelector?.("button");
- (firstButton ?? dialog)?.focus?.();
- };
- var restoreConsentFocus = (root, elements) => {
- const matchingTrigger = Array.from(
- root?.querySelectorAll?.('[data-action="interpret-report"]') ?? []
- ).find((trigger) => trigger.dataset?.reportId === consentFocusReturn?.reportId);
- const fallback = elements.appView.querySelector?.(
- "button, [href], [tabindex]"
- ) ?? elements.appView;
- (matchingTrigger ?? fallback)?.focus?.();
- consentFocusReturn = null;
- };
- var renderAppShell = (state = createInitialState()) => {
- const root = globalThis.document;
- const elements = getShellElements(root);
- if (!elements) {
- return false;
- }
- if (root !== modalOverlayDocument) {
- modalOverlayIdentity = null;
- modalOverlayDocument = root;
- }
- elements.appHeader.innerHTML = renderHeader(state);
- elements.appView.innerHTML = renderCurrentView(state);
- elements.bottomNav.innerHTML = navItems.map(
- ({ id, label, icon }) => `
- <button
- class="nav-item"
- type="button"
- data-nav-id="${id}"
- data-action="navigate-tab"
- data-tab-id="${id}"
- aria-label="${label}"
- ${state?.activeTab === id ? 'aria-current="page"' : ""}
- >
- <i data-lucide="${icon}"></i>
- <span>${label}</span>
- </button>
- `
- ).join("");
- const scenarioPanel = root?.getElementById?.("scenarioPanel");
- const phoneShell = root?.querySelector?.(".phone-shell");
- if (scenarioPanel) {
- scenarioPanel.innerHTML = renderScenarioPanel(state);
- scenarioPanel.hidden = !scenarioPanelVisible;
- }
- if (phoneShell?.dataset) {
- phoneShell.dataset.viewport = demoViewport;
- }
- const overlayRoot = root?.getElementById?.("overlayRoot");
- const hasConsentOverlay = Boolean(overlayRoot) && hasConsentDialog(state);
- const activeCommand = state?.overlays?.commandId ? state?.commandProjections?.[state.overlays.commandId] : null;
- const hasCommandOverlay = Boolean(overlayRoot) && hasCommandDialog(state);
- const hasReportServiceOverlay = Boolean(overlayRoot) && hasReportServiceDialog(state);
- const nextModalIdentity = hasConsentOverlay ? `consent:${state.overlays.consentId}` : hasCommandOverlay ? `command:${activeCommand.commandId}:${activeCommand.status}` : hasReportServiceOverlay ? `report-service:${state.overlays.reportId}` : null;
- const hasModalOverlay = Boolean(nextModalIdentity);
- for (const element of [
- elements.appHeader,
- elements.appView,
- elements.bottomNav,
- scenarioPanel
- ]) {
- if (!element) {
- continue;
- }
- element.inert = hasModalOverlay;
- element.setAttribute?.(
- "aria-hidden",
- hasModalOverlay ? "true" : "false"
- );
- }
- if (overlayRoot) {
- overlayRoot.innerHTML = hasConsentOverlay ? renderConsentOverlay(state) : hasCommandOverlay ? renderCommandOverlay(activeCommand) : hasReportServiceOverlay ? renderReportServiceOverlay(state) : "";
- if (nextModalIdentity && nextModalIdentity !== modalOverlayIdentity) {
- focusModalDialog(overlayRoot);
- } else if (!nextModalIdentity && modalOverlayIdentity) {
- restoreConsentFocus(root, elements);
- }
- }
- modalOverlayIdentity = nextModalIdentity;
- renderIcons();
- return true;
- };
- var demoState = loadScenario(createInitialState(), "EMPTY_HOME");
- var boundDocuments = /* @__PURE__ */ new WeakSet();
- var reportProjectionGeneration = 0;
- var activeReportProjectionTimer = null;
- var registrationSelectionFixtureSequence = 0;
- var commandConfirmationInFlight = null;
- var lastCommandConfirmationRequest = null;
- var commandRejectionInFlight = null;
- var lastCommandRejectionRequest = null;
- var getDemoState = () => demoState;
- var getLastCommandConfirmationRequest = () => lastCommandConfirmationRequest;
- var getLastCommandRejectionRequest = () => lastCommandRejectionRequest;
- var simulateCandidateSelectionBackendFixture = (state, interaction) => {
- registrationSelectionFixtureSequence += 1;
- const task = state?.activeAgentTaskProjection;
- const result = task?.activeResultRef ? state?.resultSnapshotProjections?.[task.activeResultRef] : null;
- const now = Date.parse(state?.demoNow);
- const expiresAt = Date.parse(result?.expiresAt);
- const versionMatched = interaction?.resultRef === result?.resultRef && interaction?.resultVersion === result?.resultVersion;
- const notExpired = Number.isFinite(now) && Number.isFinite(expiresAt) && expiresAt > now;
- const realtimeAvailable = Array.isArray(result?.items) && result.items.some(
- (item) => item?.candidateId === interaction?.candidateId
- );
- const errorCode = !notExpired ? "RESULT_EXPIRED" : !versionMatched ? "RESULT_VERSION_CONFLICT" : !realtimeAvailable ? "SLOT_UNAVAILABLE" : null;
- const fixtureId = `selection-response-${registrationSelectionFixtureSequence}`;
- if (errorCode) {
- return {
- id: fixtureId,
- errorCode,
- validation: {
- tenantMatched: true,
- patientMatched: true,
- sessionMatched: true,
- versionMatched,
- notExpired,
- realtimeAvailable,
- l1LockSucceeded: false
- }
- };
- }
- const lockExpiresAt = new Date(
- Math.min(now + 5 * 60 * 1e3, expiresAt - 1)
- ).toISOString();
- return {
- id: fixtureId,
- outcome: "SUCCESS",
- ...interaction,
- validation: {
- tenantMatched: true,
- patientMatched: true,
- sessionMatched: true,
- versionMatched,
- notExpired,
- realtimeAvailable,
- l1LockSucceeded: true
- },
- lock: {
- lockId: `appointment-lock-${registrationSelectionFixtureSequence}`,
- expiresAt: lockExpiresAt
- }
- };
- };
- var submitAssistantText = (state, text) => {
- const normalizedText = typeof text === "string" ? text.trim() : text;
- const interaction = buildCandidateSelectionInteraction(
- state,
- normalizedText
- );
- if (!interaction && !(normalizedText === "\u9009\u7B2C\u4E00\u4E2A" && state?.activeAgentTaskProjection?.type === "REGISTRATION")) {
- return submitInteraction(state, normalizedText);
- }
- const responseFixture = simulateCandidateSelectionBackendFixture(
- state,
- interaction
- );
- const selectedState = applyCandidateSelectionResponseFixture(
- state,
- responseFixture
- );
- return applyRegistrationCommandBackendFixture(selectedState);
- };
- var applyRegistrationCommandBackendFixture = (state) => {
- const task = state?.activeAgentTaskProjection;
- const result = state?.resultSnapshotProjections?.[task?.activeResultRef];
- const selected = result?.items?.find(
- (item) => item?.candidateId === task?.selectedCandidateId
- );
- if (task?.stage !== "AWAITING_REGISTRATION_CONFIRMATION" || !selected) {
- return state;
- }
- const commands = Object.values(state?.commandProjections ?? {});
- const nonTerminalStatuses = /* @__PURE__ */ new Set([
- "PREPARED",
- "AWAITING_CONFIRMATION",
- "EXECUTING",
- "UNKNOWN"
- ]);
- const existing = commands.find((command) => command?.details?.candidateId === selected.candidateId && nonTerminalStatuses.has(command.status));
- if (existing) {
- return {
- ...state,
- overlays: { ...state.overlays, commandId: existing.commandId }
- };
- }
- const nextSequence = commands.reduce((highest, command) => {
- const sequence = Number.parseInt(
- command?.commandId?.match(/^command-register-(\d+)$/)?.[1],
- 10
- );
- return Number.isInteger(sequence) ? Math.max(highest, sequence) : highest;
- }, 0) + 1;
- const commandId = `command-register-${String(nextSequence).padStart(3, "0")}`;
- return applyCommandProjectionFixture(state, {
- id: `command-awaiting-${commandId}`,
- commandId,
- version: 1,
- riskLevel: "L2",
- status: "AWAITING_CONFIRMATION",
- details: {
- operationName: "\u786E\u8BA4\u6302\u53F7",
- patientName: "\u5F20\u5148\u751F",
- departmentName: selected.departmentName,
- doctorName: selected.doctorName,
- doctorTitle: selected.doctorTitle,
- visitAt: `${selected.visitDate} ${selected.visitTime}`,
- fee: selected.fee,
- expiresAt: task.lock?.expiresAt,
- countdown: "\u5269\u4F59 5 \u5206\u949F",
- candidateId: selected.candidateId
- }
- });
- };
- var reduceDemoAction = (state, action, reportId, candidateId, assistantText, appointmentId, tabId, filter, messageId, scenarioName, viewport) => {
- switch (action) {
- case "view-report":
- return openReportDetails(state, reportId);
- case "view-original-report":
- return state.reportSourceProjections?.[reportId] ? {
- ...state,
- overlays: {
- ...state.overlays,
- reportId,
- reportView: "ORIGINAL_REPORT"
- }
- } : state;
- case "open-report-center": {
- const reportCenterState = closeReportView(state);
- return {
- ...reportCenterState,
- activeTab: "records",
- reportFilters: {
- ...reportCenterState.reportFilters,
- family: ["LAB", "EXAM"].includes(filter) ? filter : "ALL"
- },
- overlays: {
- ...reportCenterState.overlays,
- reportView: "REPORT_CENTER"
- }
- };
- }
- case "set-report-family-filter":
- return {
- ...state,
- activeTab: "records",
- reportFilters: {
- ...state.reportFilters,
- family: ["ALL", "LAB", "EXAM"].includes(filter) ? filter : "ALL"
- }
- };
- case "set-report-status-filter":
- return {
- ...state,
- activeTab: "records",
- reportFilters: {
- ...state.reportFilters,
- status: ["ALL", "UNREAD", "READ"].includes(filter) ? filter : "ALL"
- }
- };
- case "toggle-report-metrics":
- return {
- ...state,
- overlays: {
- ...state.overlays,
- reportExpanded: !state.overlays?.reportExpanded
- }
- };
- case "interpret-report":
- return requestReportInterpretation(state, reportId);
- case "grant-report-consent":
- return grantConsent(state, state?.overlays?.consentId);
- case "reject-report-consent":
- return rejectConsent(state, state?.overlays?.consentId);
- case "open-report-feedback":
- return reportId === state?.overlays?.reportId && state?.overlays?.reportView === "INTERPRETED" && hasValidReportConsent(state, reportId) && selectValidReportResult(state, reportId) ? {
- ...state,
- overlays: {
- ...state.overlays,
- reportFeedbackOpen: true,
- reportFeedbackSubmitted: false,
- reportServiceNotice: null
- }
- } : state;
- case "submit-report-feedback": {
- if (reportId !== state?.overlays?.reportId || state?.overlays?.reportView !== "INTERPRETED") {
- return state;
- }
- const feedbackState = submitReportFeedback(state, {
- reportId,
- category: filter
- });
- return feedbackState === state ? state : {
- ...feedbackState,
- overlays: {
- ...feedbackState.overlays,
- reportFeedbackOpen: false,
- reportFeedbackSubmitted: true,
- reportServiceNotice: null
- }
- };
- }
- case "consult-ordering-doctor":
- case "online-consultation":
- case "contact-human-service": {
- if (action === "contact-human-service" && reportId === state?.overlays?.reportId && state?.overlays?.reportView === "REPORT_UNAVAILABLE" && state?.reportSourceProjections?.[reportId]) {
- return {
- ...state,
- overlays: {
- ...state.overlays,
- reportServiceNotice: "\u5DF2\u4E3A\u4F60\u6253\u5F00\u4EBA\u5DE5\u670D\u52A1\u5165\u53E3\u3002"
- }
- };
- }
- if (reportId !== state?.overlays?.reportId || state?.overlays?.reportView !== "INTERPRETED" || !hasValidReportConsent(state, reportId) || !selectValidReportResult(state, reportId)) {
- return state;
- }
- const notices = {
- "consult-ordering-doctor": "\u5DF2\u4E3A\u4F60\u6253\u5F00\u5F00\u5355\u533B\u751F\u54A8\u8BE2\u5165\u53E3\u3002",
- "online-consultation": "\u5DF2\u4E3A\u4F60\u6253\u5F00\u5728\u7EBF\u54A8\u8BE2\u5165\u53E3\u3002",
- "contact-human-service": "\u5DF2\u4E3A\u4F60\u6253\u5F00\u4EBA\u5DE5\u670D\u52A1\u5165\u53E3\u3002"
- };
- return {
- ...state,
- overlays: {
- ...state.overlays,
- reportFeedbackOpen: false,
- reportServiceNotice: notices[action]
- }
- };
- }
- case "close-report-service":
- return hasReportServiceDialog(state) ? {
- ...state,
- overlays: {
- ...state.overlays,
- reportServiceNotice: null
- }
- } : state;
- case "revoke-report-consent": {
- const activeReportId = state?.overlays?.reportId;
- const activeConsentId = typeof activeReportId === "string" ? `consent-ai-${activeReportId}` : Object.keys(state?.consents ?? {}).find(
- (consentId) => state.consents[consentId]?.status === "GRANTED"
- );
- return revokeConsent(state, activeConsentId);
- }
- case "go-home": {
- const homeState = closeReportView(state);
- return {
- ...homeState,
- activeTab: "home",
- overlays: {
- ...homeState.overlays,
- appointmentId: null,
- journeyView: null
- }
- };
- }
- case "open-checkin-confirmation":
- return {
- ...state,
- overlays: {
- ...state.overlays,
- appointmentId,
- journeyView: "CHECKIN_CONFIRMATION"
- }
- };
- case "open-queue":
- return {
- ...state,
- overlays: {
- ...state.overlays,
- appointmentId,
- journeyView: "QUEUE"
- }
- };
- case "open-route":
- return {
- ...state,
- overlays: {
- ...state.overlays,
- appointmentId,
- journeyView: "ROUTE"
- }
- };
- case "switch-patient": {
- commandConfirmationInFlight = null;
- commandRejectionInFlight = null;
- invalidateReportProjectionTimer();
- const nextPatientId = state.activePatientId === "patient-li-na" ? "patient-zhang-san" : "patient-li-na";
- return {
- ...createInitialState({ activePatientId: nextPatientId }),
- demoScenario: "EMPTY_HOME"
- };
- }
- case "submit-checkin": {
- const checkedIn = applyServerProjectionFixture(state, {
- id: `checkin-completed-${appointmentId}`,
- type: "CHECKIN_COMPLETED",
- occurredAt: state.demoNow,
- payload: {
- appointmentId,
- queueNumber: "A023",
- room: "3F-08\u8BCA\u5BA4"
- }
- });
- return {
- ...checkedIn,
- overlays: {
- ...checkedIn.overlays,
- appointmentId,
- journeyView: "QUEUE"
- }
- };
- }
- case "open-assistant":
- case "return-current-task":
- return {
- ...closeReportView(state),
- activeTab: "assistant"
- };
- case "navigate-tab": {
- const tabState = closeReportView(state);
- return {
- ...tabState,
- activeTab: navItems.some((item) => item.id === tabId) ? tabId : "home",
- systemState: null,
- overlays: {
- ...tabState.overlays,
- appointmentId: null,
- journeyView: null
- }
- };
- }
- case "open-messages":
- return {
- ...closeReportView(state),
- activeTab: "messages",
- systemState: null
- };
- case "set-record-filter":
- return {
- ...state,
- activeTab: "records",
- recordsFilter: ["all", "abnormal", "unread"].includes(filter) ? filter : "all"
- };
- case "open-message": {
- const message = (Array.isArray(state.messages) ? state.messages : []).find((item) => item?.id === messageId);
- if (message?.type === "REPORT") {
- const reportId2 = typeof message.reportId === "string" ? message.reportId : null;
- return reportId2 ? openReportDetails(state, reportId2) : state;
- }
- if (message?.type === "JOURNEY") {
- const journey = state.journeyProjections?.[0];
- return journey ? {
- ...state,
- activeTab: journey.stage === "QUEUE_WAITING" ? state.activeTab : "care",
- overlays: {
- ...state.overlays,
- appointmentId: journey.appointmentId,
- journeyView: journey.stage === "QUEUE_WAITING" ? "QUEUE" : null
- }
- } : state;
- }
- return state;
- }
- case "load-scenario":
- case "reset-scenario": {
- commandConfirmationInFlight = null;
- commandRejectionInFlight = null;
- invalidateReportProjectionTimer();
- return loadScenario(
- state,
- action === "reset-scenario" ? state.demoScenario ?? "EMPTY_HOME" : scenarioName
- );
- }
- case "previous-scenario":
- case "next-scenario": {
- const currentIndex = Math.max(
- 0,
- demoScenarios.indexOf(state.demoScenario)
- );
- const delta = action === "previous-scenario" ? -1 : 1;
- const nextIndex = (currentIndex + delta + demoScenarios.length) % demoScenarios.length;
- commandConfirmationInFlight = null;
- commandRejectionInFlight = null;
- invalidateReportProjectionTimer();
- return loadScenario(state, demoScenarios[nextIndex]);
- }
- case "toggle-scenario-panel":
- scenarioPanelVisible = !scenarioPanelVisible;
- return state;
- case "set-demo-viewport":
- demoViewport = viewport === "430" ? "430" : "390";
- return state;
- case "submit-registration-demo":
- return submitAssistantText(
- state,
- "\u6211\u60F3\u6302\u660E\u5929\u795E\u7ECF\u5185\u79D1\u674E\u660E\u4E3B\u4EFB\u7684\u53F7"
- );
- case "submit-assistant-text":
- return submitAssistantText(state, assistantText);
- case "select-candidate": {
- const interaction = buildCandidateSelectionInteraction(
- state,
- candidateId
- );
- return applyRegistrationCommandBackendFixture(
- applyCandidateSelectionResponseFixture(
- state,
- simulateCandidateSelectionBackendFixture(state, interaction)
- )
- );
- }
- case "close-command":
- return {
- ...state,
- activeTab: "home",
- overlays: { ...state.overlays, commandId: null }
- };
- case "requery-registration":
- commandConfirmationInFlight = null;
- return submitInteraction({
- ...state,
- overlays: { ...state.overlays, commandId: null }
- }, "\u6211\u60F3\u6302\u660E\u5929\u795E\u7ECF\u5185\u79D1\u674E\u660E\u4E3B\u4EFB\u7684\u53F7");
- default:
- return state;
- }
- };
- var selectInterpretingReportId = (state) => {
- const reportId = state?.overlays?.reportId;
- const hasInterpretingItem = state?.patientWorkItems?.some(
- (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "INTERPRETING"
- );
- return typeof reportId === "string" && state?.overlays?.reportView === "INTERPRETING" && evaluateReportInterpretationGate(state, reportId).allowed && state?.reportSourceProjections?.[reportId]?.interpretationStatus === "GENERATING" && hasValidReportConsent(state, reportId) && hasInterpretingItem ? reportId : null;
- };
- var invalidateReportProjectionTimer = () => {
- activeReportProjectionTimer = null;
- reportProjectionGeneration += 1;
- };
- var ensureReportInterpretationProjection = () => {
- if (demoState?.demoScenario === "REPORT_GENERATING") {
- invalidateReportProjectionTimer();
- return;
- }
- const reportId = selectInterpretingReportId(demoState);
- if (!reportId) {
- invalidateReportProjectionTimer();
- return;
- }
- if (activeReportProjectionTimer?.reportId === reportId && activeReportProjectionTimer.generation === reportProjectionGeneration) {
- return;
- }
- if (typeof globalThis.setTimeout !== "function") {
- return;
- }
- const scheduledGeneration = reportProjectionGeneration;
- const consentId = `consent-ai-${reportId}`;
- const timerToken = {
- generation: scheduledGeneration,
- reportId,
- patientId: demoState?.activePatientId,
- stateContext: demoState,
- consentContext: demoState?.consents?.[consentId],
- bindingContext: demoState?.consentScopeBindings
- };
- activeReportProjectionTimer = timerToken;
- globalThis.setTimeout(() => {
- if (activeReportProjectionTimer !== timerToken) {
- return;
- }
- activeReportProjectionTimer = null;
- const isCurrentInterpretation = reportProjectionGeneration === scheduledGeneration && selectInterpretingReportId(demoState) === reportId && demoState?.activePatientId === timerToken.patientId && demoState === timerToken.stateContext && demoState?.consents?.[consentId] === timerToken.consentContext && demoState?.consentScopeBindings === timerToken.bindingContext;
- if (!isCurrentInterpretation) {
- return;
- }
- demoState = applyReportInterpretationProjectionFixture(
- demoState,
- reportId
- );
- renderAppShell(demoState);
- }, 120);
- };
- var beginCommandConfirmation = () => {
- const commandId = demoState?.overlays?.commandId;
- if (commandConfirmationInFlight || commandRejectionInFlight || !commandId) {
- return;
- }
- lastCommandConfirmationRequest = buildCommandConfirmationRequest(
- demoState,
- commandId,
- {
- channel: "CARD",
- idempotencyKey: `confirm-${commandId.replace(/^command-/, "")}`
- }
- );
- const token = { commandId, request: lastCommandConfirmationRequest };
- commandConfirmationInFlight = token;
- if (typeof globalThis.setTimeout !== "function") {
- return;
- }
- globalThis.setTimeout(() => {
- if (commandConfirmationInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "AWAITING_CONFIRMATION") {
- return;
- }
- demoState = applyCommandProjectionFixture(demoState, {
- id: `command-executing-${commandId}`,
- commandId,
- version: demoState.commandProjections[commandId].version + 1,
- status: "EXECUTING"
- });
- renderAppShell(demoState);
- globalThis.setTimeout(() => {
- if (commandConfirmationInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "EXECUTING") {
- return;
- }
- demoState = applyCommandProjectionFixture(demoState, {
- id: `command-succeeded-${commandId}`,
- commandId,
- version: demoState.commandProjections[commandId].version + 1,
- status: "SUCCEEDED",
- result: {
- type: "APPOINTMENT_CREATED",
- eventId: `appointment-event-${commandId}`,
- appointmentId: `appointment-${commandId}`,
- patientId: demoState.activePatientId,
- summary: {
- visitDate: demoState.commandProjections[commandId].details?.visitAt?.split(" ")?.[0],
- visitTime: demoState.commandProjections[commandId].details?.visitAt?.split(" ")?.[1],
- departmentName: demoState.commandProjections[commandId].details?.departmentName,
- doctorName: demoState.commandProjections[commandId].details?.doctorName,
- doctorTitle: demoState.commandProjections[commandId].details?.doctorTitle,
- fee: demoState.commandProjections[commandId].details?.fee
- }
- }
- });
- commandConfirmationInFlight = null;
- renderAppShell(demoState);
- }, 120);
- }, 80);
- };
- var beginCommandRejection = () => {
- const commandId = demoState?.overlays?.commandId;
- if (commandRejectionInFlight || commandConfirmationInFlight || !commandId) {
- return;
- }
- lastCommandRejectionRequest = buildCommandRejectionRequest(
- demoState,
- commandId,
- {
- reason: "\u6682\u4E0D\u64CD\u4F5C",
- idempotencyKey: `reject-${commandId.replace(/^command-/, "")}`
- }
- );
- const token = { commandId, request: lastCommandRejectionRequest };
- commandRejectionInFlight = token;
- if (typeof globalThis.setTimeout !== "function") {
- return;
- }
- globalThis.setTimeout(() => {
- if (commandRejectionInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "AWAITING_CONFIRMATION") {
- return;
- }
- demoState = applyCommandProjectionFixture(demoState, {
- id: `command-rejected-${commandId}`,
- commandId,
- version: demoState.commandProjections[commandId].version + 1,
- status: "REJECTED"
- });
- commandRejectionInFlight = null;
- renderAppShell(demoState);
- }, 80);
- };
- var initializePatientServiceDesk = (state = demoState) => {
- if (state !== demoState) {
- invalidateReportProjectionTimer();
- commandConfirmationInFlight = null;
- commandRejectionInFlight = null;
- lastCommandConfirmationRequest = null;
- lastCommandRejectionRequest = null;
- }
- demoState = state;
- const root = globalThis.document;
- if (root?.addEventListener && !boundDocuments.has(root)) {
- root.addEventListener("click", (event) => {
- const trigger = event.target?.closest?.("[data-action]");
- const overlayRoot = root.getElementById?.("overlayRoot");
- const appView = root.getElementById?.("appView");
- const scenarioPanel = root.getElementById?.("scenarioPanel");
- const appShell = root.querySelector?.(".phone-shell") ?? appView?.closest?.(".phone-shell");
- const withinApp = Boolean(
- appShell?.contains?.(trigger) || appView?.contains?.(trigger) || overlayRoot?.contains?.(trigger) || scenarioPanel?.contains?.(trigger)
- );
- if (!trigger || !withinApp) {
- return;
- }
- if ((hasConsentDialog(demoState) || hasCommandDialog(demoState) || hasReportServiceDialog(demoState)) && scenarioPanel?.contains?.(trigger)) {
- return;
- }
- if (trigger.dataset?.action === "interpret-report") {
- consentFocusReturn = {
- reportId: trigger.dataset?.reportId
- };
- }
- if (trigger.dataset?.action === "confirm-command") {
- beginCommandConfirmation();
- return;
- }
- if (trigger.dataset?.action === "reject-command") {
- beginCommandRejection();
- return;
- }
- demoState = reduceDemoAction(
- demoState,
- trigger.dataset?.action,
- trigger.dataset?.reportId,
- trigger.dataset?.candidateId,
- root.getElementById?.("assistantComposerInput")?.value,
- trigger.dataset?.appointmentId,
- trigger.dataset?.tabId,
- trigger.dataset?.filter,
- trigger.dataset?.messageId,
- trigger.dataset?.scenario,
- trigger.dataset?.viewport
- );
- renderAppShell(demoState);
- ensureReportInterpretationProjection();
- });
- root.addEventListener("keydown", (event) => {
- if (event.key === "Enter" && event.target?.id === "assistantComposerInput") {
- const appView = root.getElementById?.("appView");
- if (!appView?.contains?.(event.target)) {
- return;
- }
- event.preventDefault?.();
- demoState = reduceDemoAction(
- demoState,
- "submit-assistant-text",
- void 0,
- void 0,
- event.target.value
- );
- renderAppShell(demoState);
- ensureReportInterpretationProjection();
- return;
- }
- const targetTag = event.target?.tagName?.toLowerCase?.();
- const isTyping = ["input", "textarea", "select"].includes(targetTag) || event.target?.isContentEditable;
- const isPresentationShortcut = !isTyping && ["ArrowLeft", "ArrowRight", "r", "R", "p", "P"].includes(event.key);
- const consentDialogOpen = hasConsentDialog(demoState);
- const commandDialogOpen = hasCommandDialog(demoState);
- const reportServiceDialogOpen = hasReportServiceDialog(demoState);
- if (isPresentationShortcut && (consentDialogOpen || commandDialogOpen || reportServiceDialogOpen)) {
- event.preventDefault?.();
- return;
- }
- if (isPresentationShortcut) {
- event.preventDefault?.();
- if (event.key === "p" || event.key === "P") {
- scenarioPanelVisible = !scenarioPanelVisible;
- renderAppShell(demoState);
- return;
- }
- const currentIndex = Math.max(
- 0,
- demoScenarios.indexOf(demoState.demoScenario)
- );
- const nextScenario = event.key === "r" || event.key === "R" ? demoState.demoScenario ?? "EMPTY_HOME" : demoScenarios[(currentIndex + (event.key === "ArrowLeft" ? -1 : 1) + demoScenarios.length) % demoScenarios.length];
- initializePatientServiceDesk(loadScenario(demoState, nextScenario));
- return;
- }
- if (!consentDialogOpen && !commandDialogOpen && !reportServiceDialogOpen) {
- return;
- }
- if (event.key === "Escape" && reportServiceDialogOpen) {
- event.preventDefault?.();
- demoState = reduceDemoAction(
- demoState,
- "close-report-service"
- );
- renderAppShell(demoState);
- return;
- }
- if (event.key === "Escape" && consentDialogOpen) {
- event.preventDefault?.();
- demoState = rejectConsent(
- demoState,
- demoState.overlays.consentId
- );
- renderAppShell(demoState);
- ensureReportInterpretationProjection();
- return;
- }
- if (event.key !== "Tab") {
- return;
- }
- const overlayRoot = root.getElementById?.("overlayRoot");
- const focusables = Array.from(
- overlayRoot?.querySelectorAll?.(
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
- ) ?? []
- );
- if (focusables.length === 0) {
- event.preventDefault?.();
- overlayRoot?.querySelector?.('[role="dialog"]')?.focus?.();
- return;
- }
- const activeIndex = focusables.indexOf(root.activeElement);
- const nextIndex = event.shiftKey ? activeIndex <= 0 ? focusables.length - 1 : activeIndex - 1 : activeIndex === focusables.length - 1 ? 0 : activeIndex + 1;
- event.preventDefault?.();
- focusables[nextIndex]?.focus?.();
- });
- boundDocuments.add(root);
- }
- const rendered = renderAppShell(demoState);
- ensureReportInterpretationProjection();
- return rendered;
- };
- if (globalThis.document) {
- if (globalThis.document.readyState === "loading") {
- globalThis.document.addEventListener(
- "DOMContentLoaded",
- () => initializePatientServiceDesk(),
- { once: true }
- );
- } else {
- initializePatientServiceDesk();
- }
- }
- })();
|