Skip to content

Clinical input suggestion APIs: implementation plan ​

Status: implemented. See the clinical workflow for setup and current behavior.

The DTO and service-port contract specifies the shared package, operation DTOs, JSON bodies, validation rules, and Go service/repository interfaces.

Confirmed requirements ​

  • Expose two Doctor-owned list/search GET routes for chief complaints and history suggestions.
  • Require an active doctor login using the existing persistent-session, active-account, and doctor-profile checks.
  • Include chief complaint associations in the list response; selection requires no additional request.
  • Search English titles and aliases, including Bengali aliases.
  • Use English display titles. Translated display text and translated association labels are outside this change.
  • Return default suggestions when search is absent or blank, and data: [] when nothing matches.
  • Always return arrays for aliases and associated, and an object for history attributes.
  • Seed all 12 history categories with neutral terms; patient findings are entered by the doctor.
  • Remove the existing history suggestion HTTP API, including its CRUD and detail routes.
  • Start the new history catalog from new seed data. Keep existing history rows privately for recovery; do not automatically classify or expose them through the new API.
  • Keep Doctor read-only and provide full catalog CRUD through Admin.
  • Put shared models, commands/queries, errors and validation in pkg/types/domain, with services and repositories in pkg/types/ports. Add nothing to services/prescription/public for this feature.
  • Keep request/response DTOs and their HTTP/schema tags in each owning service's presentation package, with explicit domain mappings and documentation.
  • Seed the catalogs with stable IDs, preserving subsequent administrator edits, status changes, and deletions on repeat runs.

1. Routes and query contract ​

Use the existing /suggestions/{resource} grouping beneath the Doctor prefix:

http
GET /api/v1/doctors/suggestions/chief-complaints?search=fever&limit=10
GET /api/v1/doctors/suggestions/history?category=medical&search=diabetes&limit=10

These routes follow the existing version/module/resource convention and group the two clinical input catalogs together. Register one canonical route for each operation.

Admin uses the same suggestions grouping:

MethodChief complaintsHistorySuccess
GET/api/v1/admin/suggestions/chief-complaints/api/v1/admin/suggestions/history200, paginated list
GET/api/v1/admin/suggestions/chief-complaints/{id}/api/v1/admin/suggestions/history/{id}200, one record
POST/api/v1/admin/suggestions/chief-complaints/api/v1/admin/suggestions/history201, created record
PATCH/api/v1/admin/suggestions/chief-complaints/{id}/api/v1/admin/suggestions/history/{id}200, updated record
DELETE/api/v1/admin/suggestions/chief-complaints/{id}/api/v1/admin/suggestions/history/{id}200, data: {}

All Admin routes require an active platform-admin session and the new admin.clinical_suggestion.read or admin.clinical_suggestion.manage permission. Writes persist audit history atomically with catalog changes. DELETE soft-deletes a term; Doctor queries include only active, non-deleted terms. Metadata and deletion filters belong only to Admin responses/requests. See the companion contract for every operation's DTO and service port.

ParameterChief complaintsHistoryProposed behavior
searchOptionalOptionalMaximum 255 Unicode characters; trim and normalize whitespace; empty means defaults
limitOptionalOptionalDefault 20; valid range 1–100, matching existing suggestion limits
categoryNot acceptedRequiredExact value from the frontend's 12 supported categories

Reject unknown query parameters. Use search consistently with the current Doctor patient query API. The two Doctor routes need no deprecated q alias, offset, cursor, totals, or pagination metadata: each response is a bounded autocomplete result. Admin lists use a filter-bound cursor and pagination.next_cursor so all catalog entries remain manageable.

History categories:

text
medical, surgical, medication, allergy, family, social,
present-illness, obstetric, pediatric, immunization, adverse-reaction, other

Apply the category filter before search and limiting. A request for category=medical cannot return an allergy item. The Penicillin example belongs to category=allergy.

2. Response contract ​

Reuse pkg/types.SuccessResponse and SuccessList with concrete OpenAPI schemas.

json
{
  "message": "Chief complaints fetched successfully",
  "status_code": 200,
  "data": [
    {
      "id": "<stable UUID>",
      "title": "Fever",
      "aliases": ["জ্বর", "Pyrexia"],
      "associated": ["Chills & rigor", "Headache", "Convulsion"]
    }
  ]
}
json
{
  "message": "History suggestions fetched successfully",
  "status_code": 200,
  "data": [
    {
      "id": "<stable UUID>",
      "category": "allergy",
      "title": "Penicillin",
      "aliases": ["পেনিসিলিন"],
      "attributes": {"allergyKind": "Drug"}
    }
  ]
}

All documented fields are always present. Missing aliases or associations serialize as [], never null. Missing history attributes serialize as {}, never null. Association labels remain ordered strings, matching the current frontend; there is no associated-suggestion detail API.

Use stable UUID strings for IDs. Clients treat them as opaque strings. Seed IDs derive from immutable keys rather than editable titles or positions; Admin-created entries receive server-generated UUIDs. Updates preserve IDs in both cases.

Define a typed attributes object. Initially, its only populated catalog classification is allergyKind, with Drug, Food, Other, or Unspecified, and only for the allergy category. Preserve the frontend's allergyKind spelling. Other categories return {}. Duration, dates, status, dose, frequency, reaction, severity, relation, and amounts remain patient-specific inputs and have no catalog defaults.

Status behavior:

HTTP statusMeaning
200Defaults, matches, or an empty result
401Missing, invalid, expired, or revoked doctor credentials
403Inactive account or authenticated account without a doctor profile
404Admin detail, update, or delete targets a missing record
409Admin mutation duplicates a normalized title or targets an incompatible record state
422Invalid limit, search, category, or unsupported query parameter
503Required authentication capability or catalog storage unavailable
500Unexpected internal failure

Reuse existing Huma error documents and doctor authentication error mapping. Follow existing authenticated Doctor routes' cache policy. These global reference queries require no organization or patient ID, affiliation selection, or subscription-write permission.

3. Search and default ordering ​

Normalize search and stored search values using the same helper: Unicode NFC, whitespace normalization, and Unicode-aware case normalization. Preserve original display text and Bengali vowel/combining marks. Store Bengali spelling variants explicitly as aliases where needed; normalization is not transliteration or spelling correction.

Search titles and individual aliases using parameterized, literal substring comparisons. A search containing %, _, quotes, or backslashes is literal input. Do not search associated, category, or attributes implicitly.

Rank matching records as follows:

  1. Exact normalized title.
  2. Exact normalized alias.
  3. Normalized title prefix.
  4. Normalized alias prefix.
  5. Substring in title or any alias.

Within a rank, use curated display priority, normalized title, and ID. Without search, order by curated priority, normalized title, and ID. Defaults come from the catalog, separately ranked within each history category, and do not depend on patient data or usage tracking.

Return each record once even if several aliases match. Fetch associations and attributes with the records in the same catalog query, without a per-record query loop. Normalize null collections again at the response boundary.

Start with straightforward bounded SQL over the initial catalog. Add indexes for category, uniqueness, and default ordering. Inspect representative query plans and timings with the completed seed before deciding whether a trigram search index is useful; correctness for short Bengali queries must not depend on trigrams.

4. Ownership and implementation boundaries ​

Doctor owns its read HTTP routes and doctor authentication. Admin owns management HTTP routes, admin authentication, and permission orchestration. Prescription owns the clinical catalog implementation, validation, queries, persistence, and transactional audit. Models and validation live in pkg/types/domain; the two service interfaces and repository interfaces live in pkg/types/ports; neither consumer imports services/prescription/public for this feature.

Request flow:

text
Doctor Huma GET handler
  -> Doctor list use case and VerifyAuthUseCase
  -> shared clinical suggestion query port
  -> Prescription list use case
  -> shared clinical repository port and PostgreSQL adapter

Admin Huma CRUD handler
  -> Admin operation use case, authentication and permission check
  -> shared clinical catalog read/write port
  -> Prescription operation use case
  -> PostgreSQL transaction: mutation + audit

Define models, category/status value types, command/query inputs, pages, stable errors and validation in pkg/types/domain. Define AdminClinicalSuggestionService, DoctorClinicalService, and repository/transaction interfaces in pkg/types/ports, importing domain models. Neither package depends on Huma, JSON envelopes, SQL drivers or service packages. Keep PostgreSQL records, adapters, use cases, HTTP DTOs, and authentication behavior local to their implementations.

Expose implementations of the shared ports from the Prescription module and inject them into Doctor/Admin in services/api/cmd/server/main.go. Doctor receives one DoctorClinicalService with both suggestion queries. Admin receives one AdminClinicalSuggestionService with all ten management operations. No consumer receives a repository, transaction handle, or complete Prescription module.

Create one use case per file in each service, following ADR-011. Keep handlers and DTOs specific to their query or management operation. Ensure constructors and registration work with nil database/provider dependencies so offline OpenAPI generation continues to work; actual valid requests fail closed when dependencies are unavailable.

Expected implementation areas:

  • pkg/types/domain/: shared clinical models, value types, queries, commands, errors and validation.
  • pkg/types/ports/: the two clinical services and repository/transaction interfaces, all documented.
  • services/prescription/internal/usecase/: one query or catalog mutation per file, importing the shared domain package.
  • services/prescription/internal/adapters/postgres/: queries and audited transaction implementations of the shared repository ports.
  • services/prescription/module/: shared-port implementation exposure and removal of legacy history handler registration.
  • services/doctor/internal/usecase/: authenticated query orchestration.
  • services/doctor/internal/presentation/http/: the two routes, query validation, DTOs, and error mapping.
  • services/doctor/module/: dependency injection and route registration.
  • services/admin/internal/usecase/: separate list/get/create/patch/delete operations per catalog, with active-admin and permission checks.
  • services/admin/internal/presentation/http/: ten management operations with explicit request/response DTOs and mappers.
  • services/admin/module/: one AdminClinicalSuggestionService dependency.
  • services/authorization/internal/postgres/migrations/: additive read/manage permission migration and role-bootstrap updates where needed.
  • services/api/cmd/server/: composition-root wiring and route/integration tests.

Use the existing pkg/types Go module for the new subpackage; no new Go module or Doctor-to-Prescription module dependency is needed. Existing services' unrelated public contracts stay outside this change. Document this feature's shared-package exception to the previous public-package convention in the service-boundary guide.

5. Database migration and legacy history retirement ​

Add a new Prescription migration; do not edit the existing 00001_prescription_suggestions.sql migration.

Create two new tables:

TableMain columns
rx_chief_complaint_suggestionsid, immutable seed_key, title, aliases text[], associated text[], normalized search values, sort_order
rx_clinical_history_suggestionsid, immutable seed_key, category, title, aliases text[], attributes jsonb, normalized search values, sort_order

Both tables also contain status, created_at, updated_at, deleted_at, and seed-management metadata. seed_key is nullable for Admin-created rows and unique when present. Require valid nonblank titles, valid category/status values, non-null array/object defaults, and unique live normalized titles. Chief complaint titles are unique after normalization among non-deleted rows. History titles are unique after normalization within their category among non-deleted rows: the same condition can legitimately appear in medical and family history. Keep seed keys reserved after deletion to prevent seed resurrection. Validate attribute keys, values, and category compatibility. Keep association and alias order stable.

Admin PATCH preserves omitted fields, replaces supplied arrays/attributes atomically, and permits []/{} to clear them. Reject null for non-nullable fields and reject empty patches. After merging a history patch with stored data, validate category and attributes together. Serialize row mutations and enforce uniqueness in PostgreSQL. Changing title, aliases, associations, attributes, status, category, or order affects subsequent Doctor reads without changing IDs. Soft deletion never cascades into existing clinical documents.

Retain rx_history_suggestions and its existing rows privately for recovery. The new API never queries it, and new seed runs stop writing to it. Avoid guessing categories for existing custom phrases. The new migration's rollback removes only the new catalog structures; it leaves legacy rows intact.

Remove these legacy HTTP operations from registration and OpenAPI:

text
GET, POST          /api/v1/prescriptions/suggestions/history
GET, PATCH, DELETE /api/v1/prescriptions/suggestions/history/{suggestionHistoryId}

Remove unused legacy history CRUD handlers, use cases, repository methods, and DTOs. Keep shared helpers still needed by the other suggestion catalogs. Route tests must prove the old endpoints no longer resolve and that the new Doctor collections expose only GET while management lives in Admin. The clinical /api/v1/prescriptions/patient-history/{patientId} workflow is a separate resource and remains outside this replacement.

Update the old suggestions.csv, parser, summaries, CLI messages, and tests so the existing suggestion seed supports diagnosis, advice, and investigation only. Its three catalogs retain their current behavior. Document the intentional history API replacement and its new authenticated location.

6. Seed catalog and loading ​

The bundled catalog contains 301 distinct chief complaints and 800 categorized history suggestions. These are curation targets: expand with useful terms, aliases, and specialty coverage rather than duplicate spellings or artificial combinations. Report exact reviewed counts in the seed README and command output.

Chief complaint coverage should span general medicine, respiratory, cardiovascular, gastrointestinal, neurological, urinary, musculoskeletal, skin, ENT, eye, reproductive, pediatric, and mental-health presentations. Include the current frontend examples. Add English synonyms, common unambiguous abbreviations, and searchable Bengali aliases. Synonyms belong in aliases rather than duplicate records.

Proposed history distribution:

CategoryTarget entries
medical200
surgical100
medication100
allergy100
family100
social60
present-illness30
obstetric30
pediatric25
immunization25
adverse-reaction20
other10
Total history800

Use neutral subjects for narrative categories, such as onset/progression, menstrual history, birth history, vaccine names, or a medicine name with editable reaction details. Do not seed an assumed negative finding, immunization completion, pregnancy outcome, allergy severity, medicine dose, or other patient-specific fact. Keep adverse reactions distinct from allergy classifications. Associated complaints are optional choices, never selected automatically.

Store readable UTF-8 JSON files under pkg/development/database/seeds/ because nested arrays and attributes are awkward to maintain in the current two-column CSV. Each entry has an immutable key, display values, aliases, optional associations/attributes, and curated order. Embed the files; seed execution requires no download or external service.

Use hand-curated vocabulary, drawing on the existing frontend examples and reusable existing terms. Record provenance, editing decisions, language coverage, and any third-party attribution/license requirements in the seed README. Review Bengali aliases, category placement, and associations as part of content QA; do not invent association lists solely to fill every record.

Add one selectable seed item to the existing CLI and console:

sh
go run ./tools/cli database migrate --service prescription
go run ./tools/cli database seed --items clinical-suggestions

Include it in seed --all. Reuse the existing target checks and confirmation policy. Do not load catalog data automatically during API startup or schema migration.

Validate both files completely before writing, then load them in one transaction. Generate deterministic UUIDs from immutable keys. Repeated loading creates no duplicates or observable changes to existing records. Reuse a matching live catalog identity without changing an Admin-created record's content or ID; reserve its previously empty seed_key as private bookkeeping. A conflicting nonempty key fails atomically rather than silently adopting the record. Preserve seeded records after administrator edits, reclassification, deactivation, or soft deletion; lookup by seed key before matching titles so a renamed or deleted seeded row is not recreated. Revised seed content does not overwrite existing catalog content automatically: apply corrections through Admin or a separately reviewed data migration. Do not delete records omitted from a later file. Serialize concurrent seed runs and report inserted/reused/skipped totals by catalog/category. Failure in either catalog rolls back both.

Before enabling Admin routes, also apply the Authorization permission migration with go run ./tools/cli database migrate --service authorization and assign the appropriate grants using the existing role/permission workflow.

7. Validation and acceptance criteria ​

Add focused tests for behavior that can regress:

  • Doctor access: missing/expired/revoked session, inactive account, non-doctor account, valid doctor, and dependency failure.
  • Admin access: active-admin membership, active account/session, separate read/manage grants, and denied writes without any catalog/audit change.
  • Query contract: defaults, whitespace-only search, default/max limits, invalid limits, required/unknown category, maximum search length, invalid control input, and unknown parameters.
  • Search: English title, English synonym, Bengali alias, canonical Unicode equivalents, case-insensitivity, literal wildcard characters, exact/prefix/substring order, category isolation, and duplicate matches.
  • Responses: stable IDs, deterministic defaults, no-match [], empty arrays/attributes, and associations included without another endpoint.
  • Admin CRUD: pagination/filter binding, create/detail/patch/delete round trips, omitted versus empty/null values, metadata, duplicate conflicts, row concurrency, category/attribute compatibility, and immediate Doctor visibility changes.
  • Persistence: real PostgreSQL query results, category-scoped live-title uniqueness, migration up/down, audit atomicity, soft deletion, and unchanged legacy recovery rows.
  • Seeds: parse/shape validation, duplicate keys and normalized titles, allowed attributes, coverage/counts, repeatability, preservation of administrator edits/IDs/deletions, transaction rollback, concurrent runs, and CLI selection/output.
  • Routing/OpenAPI: two new Doctor GET operations and ten Admin CRUD operations, bearer security, permission documentation, operation-specific DTO schemas, and removal of all old history operations.
  • Boundaries: shared models/ports remain free of HTTP/SQL/service imports; DTO mappings preserve values; no new feature contracts are created in services/prescription/public.
  • Regression: diagnosis/advice/investigation catalog APIs and their reduced legacy seed continue working.

Run targeted unit tests with the repository's race/short conventions for the shared types/domain and types/ports packages, Doctor, Admin, Prescription, Authorization, API, development/database, and CLI. Run relevant integration packages against a dedicated test database, including Doctor/Admin authentication, permissions, audit, and suggestion-catalog coverage. Regenerate OpenAPI with go run ./tools/cli generate openapi and inspect the diff. Expand checks only if changed shared behavior or failures require it.

Update api/openapi.yaml, Doctor/Admin/Prescription service guides, the shared types/domain and types/ports packages guide, the service-boundary guide, the route inventory and success-response reference, seed documentation, database operations documentation, and API examples/collections where applicable.

8. Implementation order and completion ​

  1. Add documented shared domain models/ports, operation DTO designs, additive catalog/permission migrations, and schema tests.
  2. Implement Prescription normalization, queries, CRUD operations, and audited repositories behind the shared ports.
  3. Wire Doctor authentication and the two Huma GET routes, plus Admin authentication/permissions and ten management routes.
  4. Remove the legacy history HTTP surface and unused CRUD code; preserve old rows.
  5. Curate, validate, and wire the two seed files into the existing CLI/console; retire old CSV history loading.
  6. Complete behavioral and database tests, regenerate OpenAPI, and update documentation.
  7. Verify a migrated and seeded test database returns defaults, Bengali matches, category-filtered history, and inline associations through an authenticated doctor session; verify authorized Admin changes appear in those queries and survive seed reruns.

The deliverable is complete when those checks pass and the removed history operations are absent from both runtime routing and generated documentation. Frontend integration and prescription submission/persistence changes are separate work.

Documentation follows the source. Planned contracts are marked explicitly.