Specialties Feature
Medical specialty categories (Cardiology, Physical Therapy, Dermatology) to organize your team.
NOT BUILT — reconciled 2026-08-02
There is no specialties table, no Go domain, no routes, and zero specialties.* permission rows in any migration. This page is a design spec, not a description of running code. Every "Implementation Files" path below is aspirational.
Settled 2026-08-02 — specialties are PER-ORG. specialties.organization_id UUID NOT NULL. The open "per-org or global?" question that used to sit in features.md F1.1 is closed: per-org matches leo, data-model.md Area 2, and this page. Global would have enabled cross-org anonymised analytics and a platform-seeded starter catalog; that is not worth diverging from the live system the data migrates out of.
Ships in migration 000040 — the first migration of the F1–F6 port. See platform-completion.md and leo-port-map.md §3 → F1.
What this enables
Team organization: Categorize your specialists by medical discipline—helps you manage who can do what.
Patient filtering: Patients can browse by specialty ("I want a Cardiologist") to find the right specialist.
Multi-specialty clinics: Run a clinic with Cardiology, PT, and Dermatology all in the same platform, each with their own team.
How it works
- Admin creates specialties: "Cardiology", "Physical Therapy", "Dermatology"
- Admin assigns specialists: Dr. Smith → Cardiology, Dr. Jones → Physical Therapy
- Patients filter by specialty: "Show me available Cardiologists"
- System filters: Shows only specialists tagged with Cardiology
Technical Reference
Overview
Specialties are medical specialty categories (e.g., Cardiology, Dermatology, Orthopedics) used to organize specialists and appointment templates within an organization.
Key Concepts
- Organization-scoped: Each organization maintains its own specialty catalog.
organization_id NOT NULL+ RLS, like every tenant table. - Specialist association: Specialists can belong to multiple specialties (M:M relationship via
specialist_specialtiesjunction table — which carries its own denormalisedorganization_id NOT NULLand its own RLS policies) - Offering filtering: An Offering references a single specialty via
offerings.specialty_id(ON DELETE RESTRICT). "Appointment template" is retired leo vocabulary — see the naming note below. - Simple CRUD: Basic create/read/update/delete operations with admin-only mutations
Database Schema
See architecture/data-model.md → specialties for the canonical table definition (UUID PKs, RLS policies, indexes).
Key fields:
idUUID — primary keyorganization_idUUID NOT NULL — organization foreign key (settled per-org 2026-08-02)titleTEXT — display name (e.g., "Cardiology")slug(text) - URL-safe identifier (e.g., "cardiology")created_at,updated_at- Standard timestamps
Constraints and indexes the migration must carry:
UNIQUE (slug, organization_id)— slugs are unique within a clinic, not globallyGIN (immutable_unaccent(title) gin_trgm_ops)— Romanian typeahead withoutunaccentbreaks UX ("Stefan" must match "Ștefan").immutable_unaccent+pg_trgmalready ship in000001.- RLS SELECT for staff via
organization_memberships, plus a patient-side SELECT policy viacurrent_human_patient_profile_ids()joined topatients.organization_id— without it the portal's specialty filter returns zero rows for patient sessions.
Deletion
Specialties are configuration, not medical records, so delete is a hard delete — but it is pre-checked and returns 409 with in-use counts when specialists or offerings still reference the row, and it emits an audit entry either way. (Contrast specialists, which is soft-delete-only.)
Permissions
specialties.manage for mutations; membership-read for the list. No specialty permission row exists in any migration today — this feature seeds its own, along with the system role-template grants, in the same PR as the schema.
Naming
speciality (leo's spelling), appointment_template and franchise are retired vocabulary. The platform spells it specialty, the catalog entity is an Offering, and the tenant is an organization. See glossary.md, which wins every naming dispute.
API Reference
See api.md for complete endpoint documentation.
Endpoints:
GET /v1/specialties- List all specialties in current organizationPOST /v1/specialties- Create specialty (admin only)GET /v1/specialties/{id}- Get specialty with related specialistsPUT /v1/specialties/{id}- Update specialty (admin only)DELETE /v1/specialties/{id}- Delete specialty (admin only)
Related Features
- Specialists (../specialists/) - Specialists link to specialties via
specialist_specialties - Offerings (../services/) - An offering references a single specialty via
offerings.specialty_id
Implementation Files (planned — none of this exists yet)
Backend implementation (Go), following the platform's domain layout (/new-domain scaffolds it):
services/api/internal/core/domain/specialties/{model,repository,service,handler,errors}.go— all SQL stays inrepository.go, reached throughConnFromContext(ctx)so RLS applies- Route registration in
services/api/internal/core/server/routes.go, gated byRequirePermissionandRequireURLOrgMatchesScope("id")(P47)
Database migration:
000040—specialties+specialists+specialist_specialtiesin one migration, with table DDL, indexes, RLS policies, permission seeding, system role-template grants, and data-classification.md entries for every new column.make checkfails the build if the classification rows are missing.
The earlier claim on this page that specialties shipped across migrations
000001–000003was wrong in every particular. Those are000001_init,000002_tenancy_rbacand000003_org_settings; the string "specialt" does not appear in any migration in the repository.