docs(server): implementation plan for serving client-side knowledge rules (#582) - #583
docs(server): implementation plan for serving client-side knowledge rules (#582)#583halcwb wants to merge 4 commits into
Conversation
Implementation plan for informedica#582: the emergency list, continuous medication list, growth references and localization sheets that the client fetches from Google Sheets today are served through the resource provider and a new KnowledgeCmd command family, so that ReloadResources, GetResourceInfo and the informedica#580 scope gate cover them. Calculations stay in the browser. Refs informedica#582 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Greptile SummaryThis documentation PR plans to move emergency medication, growth-reference, and localization data fetching from the browser to an independent, cached server provider.
Confidence Score: 4/5The plan should not be approved until a throwing knowledge loader can no longer prevent the independent formulary reload. The previous findings were manually resolved and their documented corrections remain present. One blocking gap remains in the revised reload fan-out: the knowledge callback executes first without specified exception containment, so an unexpected loader exception can stop the GenORDER formulary reload. Files Needing Attention: docs/implementation-plans/582-server-knowledge-rules.md Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
C[Client] -->|KnowledgeCmd| D[Command dispatcher and scope gate]
C -->|getLocalization| A[Server API]
D --> K[KnowledgeProvider]
A --> K
K --> E[Emergency workbook]
K --> M[Main workbook localization]
R[ReloadResources] --> O[OrderContextService]
O --> K
O --> F[GenFORM provider]
Reviews (3): Last reviewed commit: "docs(server): classify growth references..." | Re-trigger Greptile |
Two availability regressions in the first draft, both caught in review: - The emergency-list id defaulted to GENPRES_URL_ID, whose workbook has no emergency tabs, so an unconfigured upgrade would have shown an empty emergency list. The default is now the workbook the client reads today. - The sheets were read through the formulary's CachedResourceProvider behind requireLoaded, so a fatal formulary load would have taken the emergency list down. They now have a small server-side provider over LoadEngine with its own cache and reload; KnowledgeCmd bypasses requireLoaded like GetDrugNames, and ReloadResources reloads both. GenFORM is no longer changed at all. Refs informedica#582 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Maintainer decision: the growth references (GetNormalValues) are an MDR function and map to a new Feature.Patient in the accredited list; the UI text (Localization) is not, so it leaves the KnowledgeCmd family and is served by a getLocalization remoting method outside processCmd, the way informedica#580 serves getSettings. Also addresses the second review round: - KnowledgeProvider.Warnings reads through the cache, and Host reads it right after construction, so a wrong workbook id is in the start-up log instead of first surfacing on a client request. - The reload fan-out no longer waits for an Ok that Api.reloadCache never returns. It runs in OrderContextService.evaluate after the password guard, unconditionally, so the admin reload refreshes the emergency list whatever the formulary reload does. Refs informedica#582 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| `OrderContextService.evaluate`, in the branch that runs after the password guard: it calls | ||
| `knowledge.Reload()` and logs its warnings before handing the command to GenORDER, so the | ||
| knowledge reload runs whenever the password was accepted, whatever the formulary then does. |
There was a problem hiding this comment.
KnowledgeProvider calls LoadEngine.ForceAll directly, but ofResultOrDefault does not catch exceptions thrown by a loader. Because the plan calls knowledge.Reload() before handing the command to GenORDER, such an exception can stop the formulary reload from running. The reload path therefore does not preserve the intended independence between the knowledge and formulary providers. Add exception containment to KnowledgeProvider.Reload, or otherwise ensure that GenORDER's reload always executes.
There was a problem hiding this comment.
Agreed, fixed in db1fe79. ofResultOrDefault maps an Error from a loader, not an exception, and LoadEngine.ForceAll raises ResourceLoadError itself on a cycle or an unregistered key, so Reload() could have escaped and, running first, skipped the formulary reload.
Two nets now, both in the plan:
KnowledgeProviderwraps its load intry/with, the wayloadAllResourcesWithRegistrywraps its ownForceAll. On an exception it caches a snapshot in which every key holds its empty value andWarningscarries oneErrorMsgwith the exception, the same "empty state on error, cached so it is not retried per request" rule thatCachedResourceProvider.loadFreshapplies.Get,ReloadandWarningsnever propagate; that is the type's contract, pinned by a step-2 test (a raising loader yields the empty value and the warning, and aReloadafter the loader is fixed serves data again).- The call site in
OrderContextService.evaluatewrapsknowledge.Reload()in atry/withof its own that logs and continues, so even a provider that broke the contract could not stop the GenORDER reload behind it. A step-3 service test with a stub reload that raises proves the command still reaches GenORDER.
Neither reload can prevent the other, in either direction.
Review round three: ofResultOrDefault maps an Error, not an exception, so a loader that raised inside ForceAll would have escaped Reload() and, because the knowledge reload runs first, skipped the formulary reload. KnowledgeProvider now wraps its load in try/with and caches an empty snapshot with an ErrorMsg, as CachedResourceProvider.loadFresh does, so Get, Reload and Warnings never propagate; the service call site adds a try/with of its own. Tests for both are added to steps 2 and 3. Refs informedica#582 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Description
Implementation plan for #582: the emergency list, continuous medication list, growth references and localization sheets that the client fetches straight from Google Sheets today are served through the server, so
ReloadResources,GetResourceInfoand the #580 scope gate cover them like every other knowledge rule. Calculations stay in the browser; only the data path moves.Key points:
KnowledgeCmdcommand family (GetEmergencyList,GetContinuousMeds,GetNormalValues,GetLocalization) with matching responses;GetProductsis dropped becauseState.Productsis never read.Resources.defaultRegistrywith its own keys andofResultOrDefaultloaders, so a failed or wrong emergency-list workbook is a warning in resource info, never a formulary outage. GenFORM itself gets one function,getCachedProviderWithRegistry, becauseGenFORM.Libis Core and may not referenceShared.GENPRES_EMERGENCY_URL_ID, defaulting toGENPRES_URL_IDwhen unset; verified against the public demo workbooks that the emergency tabs are not in the main workbook today whileLocalizationis, and that Google returns the first tab for an unknown tab name, which the parsers'KeyNotFoundExceptionturns into an error rather than wrong data.KnowledgePortonAppEnv, fourprocessCmdarms behindrequireLoaded;Feature.ofCommandclassification with a newFeature.Patientfor normal values and localization.docs.google.comleaves the CSP once localization moves.Six PRs, each under 200 lines. Depends on #580 (plan in #581) only for the scope classification; steps 1 to 3 change no behaviour until the client switches.
Author checklist
Reviewer checklist
🤖 Generated with Claude Code