NewInstance Documentation
View as Markdown

Changelog

Every NewInstance EHR release, newest first, with what changed and what each release cannot do yet.

0.3.0 (Phase 3)link

Billing and the revenue cycle. Seventy one operations are served. The operator guide is billing-guide.md and what the release cannot do is in phase-3-acceptance.md.

Setting the clinic up to bill:

  • Payers carrying a type, an electronic identifier, a submission method and a claim format, with four payer rules each stored as an answer including the answer "not required", so a rule nobody has answered is listed as unknown rather than read as not required. Every payer row says the clinic entered these rules and nobody has verified them.
  • A payer's name, type and claim identifier freeze the moment any consent names it, including a revoked one, so what a client agreed to cannot be rewritten underneath them.
  • Fee schedules as named, versioned lists of rates, each rate carrying modifiers, an amount in cents and a unit rule. A rate is resolved by payer and modifier, then payer, then default and modifier, then default. Two published schedules can never price the same service over the same dates, refused when you publish rather than discovered when you bill. A date that is not a calendar date refuses instead of finding no schedule.
  • Sliding scales with income bands that must meet exactly and may not overlap, discounting by percentage or replacing the charge with a flat fee, spread evenly by a flat fee and in proportion by a percentage, and never taking a line below zero. The federal poverty figures are entered by the organization rather than built into the product.
  • Authorizations holding units authorized, used and reserved, with units available derived rather than stored. Reserving is atomic, so two people cannot both take the last unit. Status is derived in a stated order and a release names the hold rather than an amount, so a caller cannot invent units.
  • A clearinghouse destination requiring five settings before a claim can say who is sending it and who is receiving it. Credentials are never decrypted on a read path and an unreadable credential can never render as an absent one.

Charges:

  • A charge is captured when a note is signed, when a signed note is amended, when an encounter is completed, or by hand. One encounter produces at most one charge forever, because the key is the encounter and never the note version or the service code.
  • Eleven named reasons a capture produces no charge, and two situations that refuse the whole request rather than guess: more than one coverage at the same priority on the date of service, and more than one active authorization covering the service.
  • A charge carries no diagnosis, no note content, no scores and no problems, because the billing screens are read by people with no clinical permission. A clinician can see that a charge exists, with no amount, rate or balance on it.
  • A batch containing a client the caller may not read drops that row silently rather than failing the batch or naming what was withheld, and refuses in generic words only when the caller may act on none of it.
  • Hold, release, void, write off and adjust, each needing a reason of 3 to 500 characters including the two that write nothing on the ledger. Void is refused once anything has been posted against the charge, and while a claim holds it. Write off takes the whole remaining balance rather than an amount somebody types.

Claims:

  • A professional claim and an institutional claim, each written to X12 from the record rather than from a form, refusing rather than guessing any element the product has no source for. A refusal names the field path and what is wrong with it.
  • Transmission to a synthetic destination inside the product, for development and testing. Every receipt says the claim left no network and no payer has seen it, the hand-off records no disclosure, and nothing reads it as sent. Production refuses it, so no claim can look sent before a clearinghouse is certified. A live destination is unreachable from data rather than switched off.
  • Functional and claim acknowledgements read from a file, moving the transmission rather than the claim. A rejection opens a claim rejected work item and is deliberately not a denial, because a rejection has not been adjudicated and cannot be appealed.
  • Corrected and voided claims quoting the payer's own claim control number, refused until the payer has given it. A file carrying a different control number than the one already held writes nothing and asks a person to decide, except a crossover payer's own remittance on the claim the first payer forwarded to it, whose number is recorded beside the one held.
  • Secondary claims built from a primary remittance, carrying the primary's adjustments, paid amount, remaining liability and adjudication date, and refusing whenever the two accounts of the same claim disagree.
  • A claim sent with no answer is chased by a daily sweep at a threshold the organization sets, defaulting to 30 days and clamped between 1 and 365. Silence is measured from the acknowledgement where there was one, so an acknowledged claim that goes quiet is a different word from one nobody ever acknowledged.
  • A consent can name a payer from the payer list as its recipient, by the payer's record, which is what the 42 CFR Part 2 check on a claim reads. The consent form offers Payer among its recipient types, chosen from the active payers on the payer list, and Claims, Remittances, Statements, Eligibility checks, Documents and Summaries among the information it releases, the records that check asks a consent for, so a 42 CFR Part 2 consent recorded on screen for a payer, for payment and releasing Claims, lets a protected client's claim to that payer through the check. The server takes only a payer on the organization's list, under the name the list holds. A new consent covers only the record types the form offers, which the server now holds as a list of its own, and one covering any other is refused in words naming it, such as "A consent can cover only assessments, progress notes, the treatment plan, medications, lab results, billing records, the discharge summary, claims, remittances, statements, eligibility checks, documents or summaries, and "xrays" is not one of those. Nothing was written. Choose the records it covers from those." A consent already stored is left as it is. A coverage chooses its payer from the payer list rather than taking a typed payer id.

Remittances and denials:

  • An 835 read, matched, posted and recorded in one call. The matching ladder stops on ambiguity and descends on absence, and a line that matched a charge belonging to a different client is unmatched rather than posted.
  • A denial never writes anything off, whatever adjustment group it carries. A group code answers whether the patient may be billed, not whether the clinic agreed to forgo the money. A denied charge keeps its balance until a person decides, and the product cannot write off a denial by itself.
  • Fourteen named reasons money on a remittance did not post. Two of them, a payer reduction the product refused to write off and patient responsibility it would not move to a client Medicaid or managed care covers, are held back on a separate kind of row carrying the group and reason code so a biller can see what the payer said.
  • Reason codes and payer explanations are on a detail read behind the billing permission and never on a list, a count, a title or a notification.
  • A denial queue with assign, appeal and dismiss. Dismissing requires a note saying why it will not be chased.

Client money:

  • Payments in cash, cheque or other, each with a receipt number. Card is refused rather than recorded as if the money had been taken, because no card adapter is implemented, and the refusal says to take the card on the provider the clinic already uses and record that payment as Other with the provider's reference.
  • Money that cannot be placed stays unapplied. Nothing is applied to the oldest charge and nothing is spread proportionally, because an open charge is visibly wrong and a wrong application looks derived.
  • A payment applied to the wrong charge comes off it as its own kind of ledger entry, not as a refund, so a payment's refunds, which record what has left the payment, never show money the clinic still holds on it. It has its own permission.
  • Refunds keyed on the payment and the amount, so a retry returns the refund already asked for rather than asking for it twice. Money not allocated to a named charge comes out of the payment's unapplied balance only.
  • Statements derived from the ledger and never from the balance cached on a charge, proved by writing a deliberately wrong cache and asserting the statement ignores it. A protected client's statement makes every line generic, not only the protected ones, because a partly redacted statement discloses through the gap.

Reports:

  • Accounts receivable aging anchored on the date of service, with buckets the organization sets, and three companions each labelled with whether it is already inside the outstanding total.
  • A revenue report of ten figures, each total declaring the figures it summarises with a sign. Five ledger kinds are explicitly listed as unreported, so a kind added later cannot silently vanish out of revenue.
  • Both reports say whether they cover every client, because a clinic wide total that silently omits protected clients is a lie and one that says it is partial is a fact.

Permissions an administrator should know about:

  • Seven new permissions rather than borrowed ones, each titled as the server titles it: "See that a charge exists", "Take client payments", "Issue client statements", "Ask for a refund", "Approve a refund", "Take payments off charges" and "See claim destinations". A front desk user can take client payments and issue client statements, and cannot ask for a refund or approve one.
  • Only an owner or an administrator of the organization can give the permissions held back from a bulk grant, "Access all clients", "Access Part 2 records", "Break the glass", "Export client data", "Export data", "Ask for a refund" and "Approve a refund", whether by putting one in a permission group or by giving someone a group that carries one, whatever spelling the permission or group id is given in. Anyone else is refused, in the same words as before.
  • Role grants are derived from the jobs each role does rather than listed by hand. The server refuses to start if a role is assigned work it cannot do, if a permission exists that no job asks for, or if a billing operation is served that no seeded role can perform.

Also in this release:

  • A list shows a reader whom a signed 42 CFR Part 2 consent names exactly the protected records that reader's own read of each opens, the consent's purpose, dates and revocation applying as they do at the read, and a row about several clients, such as a group booking, only when every one of them opens to that reader, by a consent, by breaking the glass or by the programme. A consent naming a payer opens no list, and no consent opens a protected client statement. Recording such a consent, or revoking it, moves the open Work queues screen and the queue counts on the EHR home of the reader it names without a reload, bringing on the work items it lets them see, or taking off those it let them see.
  • A tenant settings save sets only the values it is given, however deep inside an object, and replaces a list whole, so saving only the reminder confirmation instruction no longer switches back on reminders a clinic turned off. A billing address is saved whole, so a line 2 left out of it is cleared, and an NPI or a taxonomy code saved empty is cleared.
  • The client face sheet prints a client's status, each open enrollment's status, each coverage's status, each problem's type and rank, and each allergy's type, severity and status in the words the chart shows, such as Prospective client, On hold, Termed, Primary and Severe, rather than as the codes stored. A flag's type is printed in the chart's words too, Safety, Clinical alert, Allergy, Fall risk, Contact restriction or Administrative, and a type the chart does not offer is printed as it was stored. An active medication whose end date has passed is printed with End date passed, as the chart shows it. A problem that a 42 CFR Part 2 encounter, document or treatment plan names is protected as Part 2, read from those records when the problem is read: the face sheet prints it only for someone who may read it, and a face sheet that prints one carries the Part 2 redisclosure notice.
  • A document's PDF is not handed out from its stored file when it was printed before the document was voided, before the document's latest amendment, addendum or correction, or while an amendment waited for a co-signature that was then sent back. Each is refused in words saying why and to print the document again, such as "This copy of the document was printed before the document was voided, so it is not handed out. Print the document again, and the new copy says the document is void. Nothing was recorded as opened.", and nothing is recorded as opened. A copy printed again opens.
  • ehr.treatment_plan.review_due is sent, once for each plan and review date: by the nightly work queue sweep when an active plan's review date passes, and at signing, with ehr.treatment_plan.activated, for a plan whose review date has already passed. The sweep's first run tells of nothing that fell due before it, and nothing is sent for a plan about a 42 CFR Part 2 client, program or plan.
  • A denial's What the payer said gives the words the claim adjustment reason codes hold for each code the payer sent, read when the denial is opened, so denials already stored read them too. A message stored on a reason still wins.
  • An identifier is 24 hexadecimal characters. A 12 character text, which was read as naming a record, is refused like any other invalid identifier, in the same words and with the same codes.
  • An identifier written in upper or mixed case hexadecimal, the x-org-id header included, is read as its lower case spelling, so an upper case spelling is now read as the same record.
  • The unsigned notes queue, which 0.2.0 listed and nothing filled, is filled. A note puts an item on it when it is written and again when a supervisor returns it to draft, assigned to its author, or to nobody when the author's staff profile is no longer active, with no due date, and the item closes when the author signs the note, asks for a co-signature or deletes the draft, or when an administrator deletes a draft whose author is no longer active. A draft written before this release has no item, and nothing adds one. The other three queues 0.2.0 first announced, for expiring consents, incomplete intakes and import errors, were never built, and the 0.2.0 list below now names only the queues that exist.
  • A draft whose author is no longer active can be deleted, giving a reason, by someone who holds Void documents and Author documents, from its item on the Unsigned notes queue with Delete this abandoned draft. The reason is kept in the access log and the audit log, and the item closes. A draft of an active author stays its author's, and anyone else is refused, as before, with "Only the author of this draft can delete it."
  • An open item on the Visits that captured no charge queue for a visit with no service code or no units offers Set the code and units to someone who may work the item and holds Author documents, EHR Admin among the seeded roles, which changes the visit and captures it again. Anyone else reading the item is told "An EHR administrator sets this visit's service code and units from this queue."
  • A work item is assigned only to somebody, or to a role, that can open its queue, on the Work queues screen and the Denials screen alike. Anyone else is refused in words naming the queue, such as "This person cannot open the Denied claims queue, so they would never see this item. Nothing was changed. Assign it to somebody who can open that queue.", and nothing is changed. The staff each screen offers to assign an item to are only those who can open its queue, by the same rule.
  • A work item with no due date is no longer counted overdue. A queue's overdue figure, on the EHR home and beside its name on the Work queues screen, counts only open items whose due date has passed, as the queue itself already marked them.
  • Redelivering a webhook delivery reads one list of the statuses a delivery can be redelivered from, failed and dead, and its refusal is built from that list, so the words cannot fall behind the rule. Which deliveries can be redelivered, and the refusal's words, "Only failed or dead deliveries can be redelivered", are unchanged.

Operations: the background worker gains three scheduled jobs. ehr.claims.stale.sweep runs daily under a Redis lock. It refuses to run at all rather than run twice, and does not run if Redis cannot be reached. ehr.breakglass.expiry runs every five minutes unless EHR_BREAKGLASS_EXPIRY_CRON gives it another schedule, and tells the holder of each break the glass session whose window passed since its last run, once, so the work items the session let them see leave their open Work queues screen and EHR home. ehr.caseload.closing runs every five minutes unless EHR_CASELOAD_CLOSING_CRON gives it another schedule, and tells the member of staff on each caseload assignment ended with today as its last day that closed since its last run, once, so the work items it let them see leave their open Work queues screen and EHR home. With a cursor of its own, ehr.caseload.starting, it also tells the member of staff on each caseload assignment written to start on a later day that started since its last run, once, so the client's work items come onto their open Work queues screen and EHR home; the first run to do so tells of none that started before it.

Operations: the indexes REST checks on every start gain five that the 0.2.0 table below does not list, each checked with its exact options as 0.2.0 describes:

CollectionKeyOptions
ehr_integration_configs{ kind: 1, 'settings.fromNumber': 1 }unique, partial on settings.fromNumber exists
ehr_claims{ orgId: 1, createdAt: -1 }none
ehr_claims{ orgId: 1, controlNumber: 1 }none
ehr_claims{ orgId: 1, payerClaimControlNumber: 1 }none
ehr_clearinghouse_transmissions{ orgId: 1, claimId: 1 }none
  • The first keys an integration's sending number, the number an inbound text message arrives on and the one REST finds the organization by, so one sending number belongs to one organization. gql-server declares it too. It is unique: present as a unique index with other options, it stops REST from starting, as 0.2.0 says; held only by an index on the same keys that is not unique, it leaves REST running, logs the rows that would break it, and nothing enforces it until that index is replaced.
  • The other four enforce nothing. gql-server declares none of them and its claim lookups use them, so on a database REST has never started against, those lookups have no index to use.
  • No route stops serving when one of the five is not ready.
  • Since d87f3c4, every unique index REST checks, those in the 0.2.0 table included, behaves as the first does when only an index on the same keys that is not unique holds it: REST keeps running, every route that depends on it refuses, and the check runs again. Before that change such an index stopped the service starting. A unique index on the same keys that enforces something else still stops it starting, as 0.2.0 says.

Operations: one index gql-server builds is narrower. ehr_workqueue_items { orgId: 1, queue: 1, refType: 1, refId: 1 }, which the 0.2.0 notes list as unique, is unique among open items only, those open or in progress, so a closed item can stand beside the newer item that follows it on the same reference. On start, gql-server replaces an index on those keys that is not unique once no two open items share them, and leaves in place one that is unique with other options, as the 0.2.0 index is, logging that it is left for an operator to replace.

Operations: four scripts, each run with npm run in gql-server against a database that holds EHR data from before this release:

  • ehr:index:charge-encounters replaces the index that keeps an encounter to one charge with the one that keeps it to one charge that is not void, so a voided charge frees the encounter it billed and the service can be captured again. It is a dry run unless given --apply: it prints the index it found, the index declared, the steps it would take and how many encounters they would free, and writes nothing. With --apply it takes those steps, keeping an encounter to one charge that is not void at every moment in between, and changes no charge. While two charges that are not void hold one encounter it names them and writes nothing. It is run by whoever deploys, dry run first.
  • ehr:index:workqueue-items replaces the work item index with the one unique among open items only, above, in the same way: a dry run unless given --apply, steps that keep a reference to one open work item on a queue at every moment in between, no work item changed, and nothing written while two open items on one queue share a reference.
  • ehr:backfill:file-clients records, on each protected file the platform holds, the client record it belongs to and the kind of record content it is, read from the attachments, consents, client photos, guardian documents, document PDFs and statements that point at it, and the kind from the file's folder where no one kind of record points at it. It writes only where nothing is recorded yet and leaves unlinked a file the records of two clients point at. It has no dry run: it writes as it reads, then prints what it wrote and how many protected files still have no client or no kind, which the platform file door refuses until a record claims them.
  • ehr:backfill:consent-revocations writes a row in the consent revocation register for each consent marked revoked that the register does not hold, carrying the time and reason the consent recorded, or the time of the run and "Revoked before the revocation register existed" where it recorded none, so the consent gates, which leave out any consent the register names, read it as revoked. It has no dry run: it writes, then prints how many consents are marked revoked, how many the register already held and how many it wrote.

Operations: five more scripts, each run with npm run in gql-server against a database that holds EHR data from before this release, by whoever deploys, the dry run first. Each reads every organization in the database. The four that can write are a count unless given --apply, printing what they would change and "Dry run. Nothing was written."; with --apply each makes its changes one row at a time, so a row it leaves holds back no other, records each change as made by the organization's owner, and leaves, naming it, a row of an organization with no owner on record:

  • ehr:count:signature-images counts the staff profiles whose signature image names a file owner question 57 (b) would refuse today, one the organization does not hold, one a client record holds, one held as record content or one somebody else uploaded, and names each profile and why. It is a count only and writes nothing, given --apply or not.
  • ehr:backfill:authorization-days counts the authorizations whose first or last day is stored at exactly midnight UTC, what a date sent by a caller other than the screen became before entry 403. With --apply it moves a first day to the first instant of that day in the clinic's time zone and a last day to its last instant, so a service on that day is covered, with an access entry, and an audit entry once it commits. It changes no unit, hold or charge, and leaves, naming the charge, an authorization holding units for a charge its moved days would leave outside it.
  • ehr:backfill:authorization-alerts counts the authorizations at or below their low units warning that never had a work item for it, which nothing opened before entry 366. With --apply it takes for each the step a change to the authorization takes, which opens an item on the Authorizations running low on units queue with the note such a change writes, and also closes, as resolved by the organization's owner, any open item for that authorization on the Authorizations about to expire queue while the authorization is not within its warning before it ends. The dry run and --apply alike print each item --apply closes, one line naming the authorization, its organization, the item and its queue, such as "Authorization in organisation : --apply also closes its open item on the Authorizations about to expire queue, as a change to the authorization would, since that alert no longer holds.", and --apply ends by saying how many items it opened and, where it closed any, how many it closed. It changes no authorization.
  • ehr:backfill:clinic-days counts the days stored at exactly midnight UTC before owner question 40 (b), and before entries 423 and 440 for coverage, flag, caseload and assessment days: a client's date of death, episode, enrollment, attachment, treatment plan, problem, allergy, medication, staff license, coverage, flag, caseload assignment and financial assessment days, and the due day of an open treatment plan review item. With --apply it moves each to the instant the server now stores for that date in the clinic's time zone, with the access entry its own change writes, and an audit entry for a client, a staff profile or a financial assessment. It leaves, naming it, a treatment plan whose review day, or a caseload assignment whose first or last day, would move from one side of the present to the other, to be run again once both instants have passed. A disclosure or a consent carrying such a day is counted and never changed.
  • ehr:backfill:capture-misses counts the closed billable visits with no item on the Visits that captured no charge queue, which before owner question 51 (c) nobody was told of. It asks capture itself about each, inside a transaction it then undoes, and says how many capture would put on that queue, how many it would capture and how many it would put on no queue. With --apply it asks capture again and keeps only the items capture opens, in capture's own words. It raises no charge and changes no visit, and leaves a visit capture would capture for a biller.

Operations: the order the nine scripts above, and the two backfills of 0.2.0 below, are run in, as owner ruling P3-19 sets it, by whoever deploys. Deploy every server to the new code before any script runs, so no server on older code reads what a script writes. Then run them in six steps:

  1. on a database holding EHR data from before 0.2.0, ehr:backfill:revisions, then ehr:backfill:part2, since the later steps read the Part 2 stamps;
  2. ehr:index:charge-encounters, then ehr:index:workqueue-items;
  3. ehr:backfill:file-clients, then ehr:backfill:consent-revocations;
  4. ehr:count:signature-images;
  5. ehr:backfill:authorization-days before ehr:backfill:authorization-alerts, since the alerts read the days;
  6. ehr:backfill:clinic-days, then ehr:backfill:capture-misses.

Run each script that has a dry run without --apply first, and read its printout before running it with --apply. Three steps differ, as the notes say: the two scripts of step 1 and the two of step 3 have no dry run and write when they run, and step 4 is a count only, which writes nothing and takes no --apply.

0.2.0 (Phase 2)link

Clients and the chart:

  • Client records with demographics, contacts, addresses, flags, custom fields and financial details, searchable by name, record number, phone and date of birth. Search terms and client identifiers stay out of the address bar.
  • Saved searches, stored on the server rather than in browser storage, optionally shared with the team. A shared search carries its terms, not its results.
  • Duplicate detection on create and on import, a duplicates panel, and a merge that needs the record number of the record being merged typed back to confirm the pair.
  • The chart: a header carrying name, record number, age, flags and a neutral consent badge, a purpose-of-use selector, and twelve tabs (overview, timeline, documents, treatment plan, problems, medications and allergies, vitals, coverages, consents, scheduling, attachments, access).
  • Episodes of care, program enrollments with transfer, hold and discharge, coverages in priority order, general and 42 CFR Part 2 consents with every required element, and chart attachments.
  • Problems, medications with reconciliation, allergies and vitals, all appearing on one timeline filterable by kind and date range.
  • Break the glass from the chart, with a justification minimum, a countdown banner, an end-access action, and a review queue a reviewer works through. An opener still cannot review their own session unless they are the organization owner.
  • Caseload assignment, a client access report, and the access log entry for every read carrying the purpose of use that was declared.

Scheduling and reminders:

  • A day, week and month calendar with filters by staff, room, program, appointment type and status, and an "only my schedule" switch. The view and the date are in the address bar; the filters are not.
  • Appointment creation with the time zone stated and the entered times read back in words, conflict checking against clinician and room, and a schedule-anyway path where the organization's policy only warns.
  • Recurring series, drag to move, resize to change the length, and an edit or cancel scope prompt that pre-selects nothing, orders the choices narrowest first, and states how many real appointments each choice would touch.
  • Check in, which creates the encounter, check out, no show, cancellation and late cancellation as distinct outcomes.
  • A waitlist with priority order, preferred times, offers and offer expiry. Waitlist counts are computed from the rows the reader may see and are structurally incapable of being subtracted from a visible list.
  • Reminder templates by channel and lead time, with placeholder preview, the organization email channel requirement, text message consent, and STOP and CONFIRM handling on the inbound provider webhook.
  • A reminder for an appointment belonging to a 42 CFR Part 2 client is sent with neutral wording carrying only the date and the time. The template body is replaced, not filtered, and no template can opt out.

Documentation and signatures:

  • A form renderer covering every field type the shared engine implements, with conditional visibility, conditional required rules, calculated values, scoring bands, alerts and service time capture.
  • Autosave against an expected version, a conflict dialogue that shows both values of every differing field before you choose, and a held page that will not navigate away from a document editor holding unsaved work. Nothing is written to browser storage.
  • Signing with typed, drawn and PIN methods, client signatures, a validation gate that names what is missing, the five-attempt PIN lockout, and a late entry banner that is permanent.
  • Co-sign request, co-sign, and send back with a reason, routed by the role keys the form's co-sign rule names. The co-signer list never offers the author.
  • Amendments, addenda and voids, each with a reason that every later reader sees, and a version history showing the chained fingerprints, a comparison between any two versions, and a per-version PDF.
  • Group sessions: attendance, one generated draft per attendee, shared content copied without any reference to one attendee's own record, and each note signed on its own.
  • Treatment plans: problems, goals, objectives and interventions, participants, signing, review cycles with a due date that moves when a review is recorded, revision into a new version, and closure.

Configuration and operations:

  • A visual form builder with drag-ordered sections and fields, a protected health information switch per field, condition and scoring editors, a preview that uses the real renderer, and a JSON tab that round-trips. What the builder offers is derived from the shared engine rather than listed beside it.
  • Client import from CSV: mapping with suggestions, a dry run that names the row and column of every rejection, a commit that creates only the good rows, and an import job with final counts.
  • Work queues for unsigned notes, co-signatures due, treatment plan reviews due, possible duplicates, break-the-glass reviews, signature chain mismatches and failed webhook deliveries, with assignment, completion and dismissal, and counts that update without a reload.
  • An EHR home page showing the queues that have anything on them and the signed-in person's own schedule for today.
  • Three REST routes (GET and POST /api/v1/ehr/appointments, POST /api/v1/ehr/clients/lookup) and the inbound SMS webhook. The REST API returns no Part 2 content and refuses a Part 2 client as absent.
  • A clinician guide covering the day to day work, and UAT suite 26-ehr.md.

Permission changes an administrator should know about:

  • ehr.config.view is held by one role template only, product.ehr.admin. Six configuration reads that clinical work depends on now also accept the permission that describes who needs them: locations, appointment types and reminder templates accept ehr.schedule.view; custom field definitions accept ehr.client.view; form definitions, both the list and a single form, accept ehr.document.view; service codes accept ehr.billing.view or ehr.document.author. ehr.config.view still reaches all of them, and it is still the only permission that reaches the tenant settings, the programs, the staff directory, the workflow definitions and the SMS integration.
  • The EHR entry in the dashboard navigation is gated by EHR product membership, not by ehr.config.view. Before this change only an EHR Admin or the organization owner could reach the product at all.
  • Three narrow reads were added so that a clinical screen never falls back on an administrative one: assignable programs (carrying level of care and active state), co-signers for one document, and the documentation settings that drive the late entry window and whether a signature needs a PIN.

Operations: indexes an operator must have in place:

Two services build indexes independently and they must agree, because they write to the same collections.

  • gql-server builds its own indexes from the Mongoose schemas on connect. A unique or partial index will not build over data that already violates it; the build failure is logged and the index is simply absent afterwards, so check the logs on the first start after upgrading.
  • newinstance-server-rest-api checks a fixed list on every start. For each one it creates the index if the key pattern is absent, accepts it if the key pattern and the behavioral options both match, and otherwise refuses to use it. Three outcomes matter:
    • A unique index the service writes against that is present with different options makes ensureRestIndexes throw, which the bootstrap turns into process.exit(1). The service will not start. This is the one an operator has to get right before a deploy.
    • An index that is present but wrong in any other way, or that could not be built because existing rows violate it, leaves the service running with the routes that depend on it refusing 503, and no Retry-After.
    • An index that could not be checked at all, for example because the database was briefly unreachable, also refuses 503, with Retry-After: 30, and a background recheck promotes it to ready once the check succeeds. Never having been checked counts as unknown, and unknown refuses.

The indexes REST checks, with their exact options:

CollectionKeyOptions
ehr_clients{ orgId: 1, mrn: 1 }unique
ehr_events{ orgId: 1, seq: 1 }unique
ehr_events{ orgId: 1, dedupeKey: 1 }unique, partial on dedupeKey exists
ehr_event_counters{ orgId: 1 }unique
ehr_appointments{ orgId: 1, occurrenceKey: 1 }unique, partial on occurrenceKey exists
ehr_schedule_locks{ orgId: 1, resourceType: 1, resourceId: 1 }unique
ehr_webhook_deliveries{ orgId: 1, subscriptionId: 1, eventId: 1 }unique
ehr_sms_inbound{ toMasked: 1, outcome: 1 }unique, partial on outcome equals unmatched_recipient
ehr_sms_inbound{ orgId: 1, at: -1 }none
service_index_registry{ service: 1, collectionName: 1, indexName: 1 }unique

Which routes stop serving when one is not ready:

RouteDepends on
POST /api/v1/ehr/appointmentsehr_schedule_locks { orgId, resourceType, resourceId }, ehr_events { orgId, seq }, ehr_event_counters { orgId }
POST /api/v1/ehr/clients/lookupehr_clients { orgId, mrn }
POST /api/v1/ehr/webhooks/{id}/testehr_event_counters { orgId }, ehr_events { orgId, seq }, ehr_events { orgId, dedupeKey }, ehr_webhook_deliveries { orgId, subscriptionId, eventId }

Three more indexes are built by gql-server only and are worth checking by hand on an existing database, because each one is unique over data that may already break it: ehr_episodes { orgId, clientId } unique on open episodes only, ehr_treatment_plans { orgId, clientId } unique on active plans only, and ehr_workqueue_items { orgId, queue, refType, refId } unique. If any of these fails to build, duplicate open episodes, duplicate active plans or duplicate queue items become possible and nothing will say so.

Other operations notes:

  • Booking uses a transaction, so the database must be a replica set or a sharded cluster. Without transaction support the REST service still starts, logs why, and refuses booking.
  • Two backfills run once per existing database, before the first Phase 2 deploy serves traffic, both from gql-server and both safe to run more than once because each only touches rows that are not already correct:
    • npm run ehr:backfill:part2 sets the client-level protected flag on every client with a Part 2 enrollment, so a client who was enrolled before this release carries the protection their records already have.
    • npm run ehr:backfill:revisions stamps the revision the new conflict and attestation checks compare against onto treatment plans, documents and break-glass sessions that predate it. Without it an amendment, a void or a break-glass review on an old record has no revision to pin.

0.1.0 (Phase 1)link

Product registration and platform integration:

  • Registered ehr as an umbrella product with ten modules (clients, scheduling, clinical, orders, inpatient, billing, reporting, portal, interop, AI), an EHR entry in the dashboard navigation, and an EHR home page with program, location, and staff counts.
  • Added the Starter, Practice, and Agency billing plans, gating seats and modules.
  • Seeded the full Phase 1 permission vocabulary (40 ehr.* methods), ten role templates (EHR Admin, Clinician, Clinician with co-sign required, Clinical Supervisor, Prescriber, Nurse, Front Desk, Biller, Quality Assurance, Auditor), and nine API-key permission groups.
  • Added the baaSigned flag to the super admin AI providers page, the gate the EHR AI assistant will check before sending PHI to a provider.

Data protection and compliance groundwork:

  • Per-tenant field encryption (AES-256-GCM, tenant data key wrapped by an HKDF-derived master key, versioned for future rotation) for sensitive fields such as tax ID and license numbers.
  • A synchronous, immutable PHI access log, written before every protected read, write, or outbound action returns.
  • Break-the-glass sessions: start, end, and review (an opener cannot review their own session unless they are the organization owner), with configurable access windows and automatic reviewer notifications.
  • A disclosure log for recording who protected health information was released to, and why.
  • A transactional outbox and a worker-driven webhook delivery pipeline with signed payloads, SSRF-checked destination URLs, exponential backoff, dead-lettering after 8 attempts, and automatic subscription pause after 100 dead deliveries (a paused subscription rechecks every 15 minutes so the backlog delivers on resume). Delivery records are retained 30 days and dispatched events 7 days, longer if a delivery still references them. Operations note: both the worker's delivery loop and the REST webhook management routes require the platform SECRET_KEY; without it, delivery does not start and the REST routes return 503.

Core configuration:

  • Tenant settings: display name, time zone, NPI, tax ID, 42 CFR Part 2 controls, documentation rules, break-the-glass policy, retention policy, and AI feature gating.
  • Programs, locations (sites, rooms, virtual), staff profiles with licenses and signature PINs (five failed attempts lock signing, and changing your own PIN, for fifteen minutes), service codes (unique by code and modifier set), appointment types, and custom field definitions.
  • Reference code lookup seeded with place-of-service codes, HCPCS modifiers, and a starter set of behavioral health HCPCS codes.
  • Versioned form and workflow definitions with a draft, publish, retire lifecycle for forms (clone available from any status) and a draft, publish lifecycle for workflows. Includes a seeded PHQ-9 depression screener example in the demo tenant.

Dashboard and API:

  • Settings, Audit, and Webhooks pages in the merchant dashboard, covering everything above plus the PHI access log viewer, break-the-glass review queue, disclosure recording, and full webhook subscription management (create, edit, custom headers, rotate secret, pause and resume, send test, view and redeliver deliveries).
  • A REST API skeleton (/api/v1/ehr) for reference code lookup, webhook subscription management, webhook testing, and event replay.
  • A FHIR R4 capability statement endpoint (GET /fhir/r4/metadata).
  • Dashboard hooks for reference code search, a single staff member, a single form definition, event listing, and setting a signature image are also shipped, but no page in the dashboard calls them yet.

Planned for Phase 3 when 0.1.0 was writtenlink

What Phase 3 built is in 0.3.0 above, and what it does not do is in the billing guide's section 18 and phase-3-acceptance.md. The plan as it was written:

  • Payers and plans, and the eligibility check against them.
  • Fee schedules, contracted rates, and the sliding scale.
  • Authorizations: units granted, units used, and the alert when a client is close to running out.
  • Charge capture from a signed note and from a completed appointment.
  • Claims: build, scrub, submit, and track.
  • Remittances: posting, denials, and the work queue that comes out of them.
  • Client statements and the balances that drive them.