Professional catalogs and profile selections
Doctor owns qualification, speciality, and specialization catalogs. Each doctor has one speciality and an ordered array of specializations. Each specialization has exactly one parent speciality_id. Qualifications represent degrees or certificates. Catalog selections do not establish credential verification or clinical eligibility.
Read choices
Public GET routes return the standard success envelope with a non-null data array, ordered by name and ID:
/api/v1/doctors/qualifications/api/v1/doctors/specialties/api/v1/doctors/specializations?specialityId=<uuid>
The existing specialties collection path supplies choices for the doctor's single speciality. Items contain id, name, description, created_at, and updated_at. Specializations also contain speciality_id.
The optional specialityId query filters specializations in PostgreSQL. Omission lists all children; a valid ID without matches returns []; malformed UUIDs return 422. Unknown query parameters are rejected. The filter belongs only to specialization lists.
Administrator CRUD
Use /api/v1/admin/doctor-qualifications, /api/v1/admin/doctor-specialties, and /api/v1/admin/doctor-specializations with an active platform-admin bearer session. The specialization collection GET accepts the same optional specialityId filter.
| Method | Path suffix | Permission | Success |
|---|---|---|---|
| GET | collection | admin.doctor_catalog.read | 200 |
| GET | /{id} | admin.doctor_catalog.read | 200 |
| POST | collection | admin.doctor_catalog.manage | 201 |
| PUT | /{id} | admin.doctor_catalog.manage | 200 |
| DELETE | /{id} | admin.doctor_catalog.manage | 200 |
POST/PUT accepts name and optional description. Specializations additionally require speciality_id; other catalogs forbid it. PUT replaces editable fields. Names are trimmed and limited to 255 characters, descriptions to 2,000. Names are unique case-insensitively within the parent speciality for specializations and within the whole catalog for the other kinds. Missing records return 404; invalid details return 422; missing parents, duplicates, referenced deletion, or reparenting a selected specialization return 409. Foreign keys enforce the hierarchy during concurrent changes. Catalog name edits appear in current profiles.
Save doctor selections
PUT /api/v1/doctors/profile/professional-details requires an active doctor bearer session. It replaces all selections atomically:
{
"qualification_ids": ["10000000-0000-4000-8000-000000000001"],
"speciality_id": "20000000-0000-4000-8000-000000000001",
"specialization_ids": ["30000000-0000-4000-8000-000000000001"]
}Use actual catalog IDs. All three fields are required; the two arrays accept 0–30 unique canonical UUIDs. Every specialization must belong to the one selected speciality. Empty arrays clear those selections. To change speciality, supply its new child selections in the same request. A missing reference or parent mismatch returns 409 and preserves every previous selection. Malformed selections return 422. The session determines the doctor.
The returned profile contains qualifications, one speciality catalog object, and a specialization catalog array in display order, always including [] when empty. The qualification summary is derived from selected names after saving selections. Legacy qualification text remains available until qualifications are replaced. Auth verification, registration, admin directory/detail, and organization rosters use the same speciality/specialization shape.
The old specialties, specialty_details, and subspecialties profile fields, specialty_ids/subspecialty_ids request fields, scalar profile specialization, and all subspecialty routes have been removed. Registration now requires speciality_id and accepts optional specialization_ids (default []). Invalid hierarchy rolls back account, profile, and optional invitation redemption together. Historical verification and prescription specialization strings remain snapshots; new verification submissions render selected specialization names as their summary.
Existing database upgrade and recovery
Rebuild the API and CLI to include new migrations. Doctor migration 00005 installs doctor_profile_speciality_preflight and the explicit mapping table. Migration 00006 preserves child catalog IDs and valid selections while renaming them to specializations, adds the required single parent, and enforces composite foreign keys linking each child's parent to its doctor's parent.
go run ./tools/cli database migrate --service doctorAn unresolved profile causes 00006 to fail without changing profiles; 00005 remains installed. Inspect the report before retrying:
SELECT doctor_id, license_number, legacy_specialization, legacy_specialties,
selected_speciality_ids, speciality_id, problem
FROM doctor_profile_speciality_preflight
WHERE problem IS NOT NULL;One existing parent selection or an exact unambiguous legacy catalog match is automatically mapped. Multiple parents and unmatched free text require an explicit choice. Insert a reviewed mapping using actual UUIDs:
INSERT INTO doctor_profile_speciality_reconciliation(doctor_id, speciality_id)
VALUES ('10000000-0000-4000-8000-000000000001',
'20000000-0000-4000-8000-000000000001')
ON CONFLICT (doctor_id) DO UPDATE SET speciality_id=EXCLUDED.speciality_id;Child selections under a different parent still block migration. Export original values before explicitly reconciling those selections. No migration splits free text or chooses the first of several parents. Retry the migration after resolving the report. Finalization removes the temporary mapping table/view and keeps original expertise text and parent selections in private legacy_* profile columns for recovery; these are never returned by the API.
Rolling back 00006 restores the old catalog names, current parents, surviving archived parent selections, and original text. It reinstalls the preflight view and explicit current-parent mappings for replay. If current plus archived parents exceed the old 30-parent limit, rollback fails atomically and requires reconciling that recovery data first. Unwind newer migrations before rolling back 00004. Coordinate application/client releases for this contract change; remove private legacy recovery columns only in a separately reviewed cleanup migration.
Seeds
go run ./tools/cli database seed --items doctor-catalogs
go run ./tools/cli database seed --items doctorsCatalog seeds contain six qualifications, seven speciality choices, and Interventional Cardiology under Cardiology. These are product starter terms. Stable IDs, including the original child seed identifier, preserve administrator edits on repeated runs. The development doctor selects General Medicine as the single speciality, no specializations, and MBBS/FCPS qualifications. Repeat account seeds preserve existing professional details and selections.