Schema Design
How elements are named, scoped, and modeled. Naming conventions, domain namespacing, and the concept/property/vocabulary decision tree.
1. Naming conventions
Casing encodes element type:
| Element type | Convention | Examples |
|---|---|---|
| Concepts | PascalCase | Person, Enrollment, GroupMembership |
| Properties | snake_case | given_name, date_of_birth |
| Vocabulary value codes | snake_case | never_married, bank_transfer |
| Vocabulary identifiers | kebab-case | gender-type, enrollment-status |
Enforced by JSON Schema regex validators. Once a name is published at candidate or above, it cannot be changed.
2. Domain-scoped URIs
Some concepts share a name across domains but carry different semantics ("Enrollment" in social protection vs. education). Domain-specific elements get a domain segment in their URI; universal elements live at the root:
publicschema.org/sp/Enrollment(social protection)publicschema.org/Person(universal)
The test: an element is universal if the same definition carries the same meaning regardless of domain. If not, it belongs in a domain namespace.
Apply the same meaning-based test to properties and vocabularies. A portable primitive shape does not make a property universal: a tax identifier and a vehicle identifier can both be strings while naming different facts. Shared properties such as start_date retain their root URI when used by a sector concept. Existing candidate and normative terms retain their published identities, including older root-property/domain-vocabulary pairs; those are compatibility constraints, not a precedent for assigning new terms by shape alone.
Public names do not need domain abbreviations: use Enrollment, not SPEnrollment. LinkML identifiers must nevertheless be unique within the composite. For example, the authored CrvsPerson represents crvs/Person, a civil-registration snapshot distinct from universal Person; crvs/Parent inherits from that snapshot. See ADR-018.
The build pipeline keys concepts internally by {domain}/{id} (e.g., sp/Enrollment, crvs/Birth) for domain-scoped concepts and by bare id (e.g., Person, Event) for universal ones. This prevents silent overwrites when two domains define concepts with the same short name.
| Code | Domain | Current scope |
|---|---|---|
sp |
Social protection | Benefit programs, participation and public-service delivery relationships |
crvs |
Civil registration | Civil registration of vital events and the associated roles and records |
agri |
Agriculture | Agricultural and fisheries production: holdings, cultivation, and production-specific roles, facilities, inputs and assertions |
land |
Land administration | Spatial and administrative units, tenure and boundary assertions |
environment |
Environment | Environmentally regulated facilities and their activities, and water-use authorizations |
transport |
Transport | Road vehicles, driving entitlements and vessel attributes |
edu |
Education | Education provision, programs, delivery sites and educational facility types |
health |
Health | Healthcare facility discovery and integration with selected FHIR resources; no replacement medical ontology |
tax |
Tax administration | Registration of persons and organizations with tax authorities, by tax type |
elections |
Elections | Voter registration, electoral districts and polling stations |
These labels describe represented slices, not complete sector standards. A domain is a namespace and discovery aid, not a superclass or access-control boundary. Any domain can reuse a shared concept or refer to another domain's concept. Module filenames organize authorship and do not determine public namespaces.
ServicePoint stays shared. Its School and HealthFacility subtypes use edu/School and health/HealthFacility. RegistrationOffice remains at root because its definition also covers identity and refugee registration. WaterPoint retains its existing root identity as a disclosed scope exception; a complete water and sanitation namespace has not been designed. Generic animal and plant identities remain shared where their definitions do not require agricultural production. See domain placement and migration for individual dispositions.
Author explicit class_uri, slot_uri, enum_uri and permissible-value meaning values with consistent annotations.source_domain. The renderer uses a property's authored URI to place its page; adding a new consumer must not move that property. Vocabulary catalog paths remain /vocab/<domain>/<kebab-case-id>. schema/publicschema.yaml supplies domain labels through annotations.domains_json; the site shows domains actually present in each collection and keeps unknown codes visible.
3. URI persistence
Every element gets a stable URI. Once published at candidate or above, a URI will not be removed. Deprecated terms continue to resolve with metadata indicating the replacement. See Versioning and Maturity for the full model.
4. Concept, property, or vocabulary
Use this decision tree to determine what kind of element to create.
Step 1: Does it have its own identity? Does this thing exist independently, get referenced from multiple places, and have its own lifecycle? If yes, it is a concept.
Example: GroupMembership is a concept, not a property on Person or Group. It carries its own data (role, dates), has its own lifecycle, and is referenced from both sides.
Step 2: Is it an attribute of a concept? A fact about a specific concept, with no independent identity? If yes, it is a property. Multiple values (e.g., phone numbers) still make it a property with cardinality many.
Step 3: Is the value drawn from a closed set? If the property accepts one answer from a defined list with stable meanings, the value set is a vocabulary.
Step 4: Reference or inline? If the value has its own identity and properties, reference a concept (concept: Location). If it is a simple scalar, use an inline primitive.
Example: latitude is an inline decimal on Location. It has no independent identity, no sub-properties. It is a number.
| Situation | Element type |
|---|---|
| Own lifecycle, referenced from multiple concepts | Concept |
| Attribute of a concept, no independent identity | Property |
| Value from a closed set of options | Vocabulary |
| Value has its own identity and sub-properties | Property referencing a concept |
| Simple scalar | Inline primitive type |
Actor vs. receiver supertypes
Agent and Party are two abstract supertypes that carry different semantics.
Partyis the receiver side: the persons, organised groups of persons (Household, Family) and organizations that can be identified, enrolled in programs, and receive benefits or services. Beneficiary-side references (beneficiary,recipient,subject,redeemable_by,issued_to) range overParty.Agentis the actor side: the persons, organisations, and software that perform, publish, evaluate, decide, or execute. Actor-side references (performed_by,evaluator,publisher) range overAgent.
Person and Organization belong to both hierarchies: each can both receive services and perform them. Organization covers bodies of any sector, including companies and cooperatives. SoftwareAgent is an Agent only. Two Party-ranged properties do not apply to organizations and say so in their definitions: data_subject, because data protection law protects natural persons, and subject on profiles. See ADR-008 and ADR-028.
Sole proprietorships
Jurisdictions draw the line between a person and their business differently, so the core admits two patterns and an application profile states which one a jurisdiction uses:
- Person pattern. Where the business has no existence separate from the person, record a
Personwith the business identifier (anIdentifierAssignmentfrom the business register), the trading name (aNameUsagewith name usetrading) andindustry. - Organization pattern. Where the register treats the business as a body distinct from the person, record an
Organizationwhoselegal_formis a sole proprietorship, linked to the person by anInstitutionalRole.
A record is a person or an organization, never both: do not define a concept, in the core or in a local extension, that is a subtype of both Person and Organization. FOAF declares the two classes disjoint, and a person can run several successive businesses.
4a. Group-like concepts
Three concepts in PublicSchema describe collections of persons but have distinct semantics. Choosing the right one matters for data quality and interoperability.
Household is a co-residential economic unit. Members share a dwelling and typically share food and resources. The operational definition varies by country and program (combining co-residence, shared budget, shared cooking, and kinship criteria), but the anchor is always physical co-location and shared livelihood. Household is the right concept for registering beneficiary units in social protection programs.
Family is a kinship network. Members are connected by blood, marriage, or adoption, regardless of where they live. A family can span multiple households and geographic areas. Kinship links between members are modelled as Relationship records between Person instances; Family itself carries no dedicated kinship properties at this stage. Family is the right concept when the unit of interest is a relational network rather than a co-residential arrangement.
FamilyRegister is an administrative document, not a group. It is a civil-registration record that tracks a family unit over time as vital events (births, deaths, marriages) occur. It references a Family to expose current membership. FamilyRegister is the right concept for modelling koseki-style, hukou-style, or livret-de-famille-style administrative instruments.
When to use each
| You want to record... | Use |
|---|---|
| A beneficiary unit sharing a dwelling and resources | Household |
| A network of persons connected by blood, marriage, or adoption | Family |
| An administrative civil-registration document tracking a family | FamilyRegister |
Interoperability bridge
Many systems use "family" colloquially to mean the co-residential unit. When exchanging data with such systems, set group_type: family on the Household record. This signals to consumers that the household is being represented as a family for interoperability purposes without misrepresenting the PublicSchema semantics.
5. Temporal context
Almost everything in public service delivery is time-bounded. A status snapshot without a validity period is incomplete. When designing a concept or property, ask: will this value change over time? If yes, model the temporal context explicitly (start/end dates, validity periods).
Date property conventions
Lifecycle concepts use domain-specific named dates that describe the domain event. Relationship and membership concepts use generic start_date / end_date.
| Concept type | Date pattern | Examples |
|---|---|---|
| Lifecycle (Enrollment) | Domain-specific named dates | enrollment_date, exit_date |
| Lifecycle (Entitlement) | Domain-specific period | coverage_period_start, coverage_period_end |
| Lifecycle (Grievance) | Domain-specific event dates | submission_date, resolution_date |
| Single event (PaymentEvent) | Single event date | payment_date |
| Relationship (GroupMembership, Relationship) | Generic dates | start_date, end_date |
| Calendar validity of an assertion (RegistryEntry, Registration) | First and last applicable dates | valid_from, valid_to |
Do not mix both patterns on the same concept. A lifecycle concept should not carry both enrollment_date and start_date.
The two generic pairs are not aliases. start_date names the first day a relationship
is effective and end_date the last day, both included
(ADR-027). valid_from and valid_to
name the first and last applicable calendar dates of an assertion's validity. recorded_at
instead records when the source entered the assertion. Missing dates remain unknown;
an omitted end does not prove perpetual validity.
Use start_date / end_date for relationship and membership concepts, such as
HoldingParcelLink, AnimalResidence, AnimalResponsibility,
AgriculturalServiceRole, IdentifierAssignment, NameUsage, ContactPoint,
AssetPartyRole and AssetAddressAssignment. Registration (including Authorization specializations such as DrivingEntitlement),
RegistryEntry, AgriculturalParcel, Certification and LandTenureAssertion retain
their declared calendar validity.
The relationship date conversion guide describes the
explicit conversion contract for source records that use calendar validity.
An application that compares or converts dates must first know the source's interval
boundaries. Both pairs include their end day, so an inclusive source valid_to: 2026-06-30 becomes end_date: 2026-06-30, and the next period starts on 1 July.
A source whose end is the first day that no longer applies needs one day subtracted.
Do not convert when source precision or boundary semantics are unknown. The farm-work
example follows the same convention.
6. Property independence
A property like start_date is defined once and reused across concepts. When a shared property needs concept-specific value sets (e.g., status on Enrollment vs. Grievance), it specializes via different vocabulary references rather than pretending the differences don't exist.
Cross-concept property reuse
Property independence is not limited to repeated structural fields. Substantive observables can be reused across concepts too. water_source, sanitation_facility, and dwelling_type appear on both SocioEconomicProfile (baseline registration context) and DwellingDamageProfile in a sibling schema (post-shock assessment). In each case the property is declared once and listed in each concept's properties; the pattern also illustrates how PublicSchema properties can be reused by domain-specific profile subtypes vendored in a sibling schema.
The rules that keep this honest:
- One reusable slot per named fact.
water_sourceis declared once underslots:and referenced from both profiles. - Contextual framing lives on the concept, not the property. The property definition names the observable ("the household's primary source of drinking water"). Each concept definition names how that observable is interpreted in that concept (baseline vs. post-shock).
- Reuse must be disclosed in both concepts' narrative definitions. A reader on either page must be able to see that the field also appears elsewhere and why.
- Reuse does not make records type-compatible. A
SocioEconomicProfilerecord and aDwellingDamageProfilerecord are different things even when their property values overlap. Adopters should consult the concept page, not the property list, when serialising into a strongly typed shape. - Split when wording diverges. If the property's own definition needs different text in each context, create two properties.
locationandlocation_of_assessmentare split this way:locationis the concept-agnostic geographic location of the record's subject (the household's site for a Household record, the organisation's primary site for an Organization record);location_of_assessmentis where a post-shock damage assessment was physically carried out, which may differ after displacement.
triggering_hazard_event (on DwellingDamageProfile) and triggering_vital_event (on CivilStatusAnnotation) follow the same split. Both were originally unified as one triggering_event whose type was widened to concept:Event, but the expected subtype carries meaning for validators and practitioners, so each consumer now declares its own typed reference. See ADR-007 for the full argument.
7. Age applicability
Some Person-scoped properties are only meaningful for specific age groups. The Washington Group Short Set and Extended Set apply to adults; the Child Functioning Module applies to children ages 2-4 and 5-17. WHO growth standards apply to under-5s. Rather than encoding these rules in definition prose alone (which machines cannot parse), properties carry an optional age_applicability array of controlled tags.
| Tag | Numeric range | Source of the band |
|---|---|---|
infant_0_1 |
0-23 months | General infancy (covers MICS infant modules, early WHO growth) |
child_2_4 |
2-4 years (24-59 months) | CFM 2-4 variant; WHO Child Growth Standards |
child_5_17 |
5-17 years | CFM 5-17 variant; also CRC definition of "child" |
adolescent |
10-19 years | WHO definition (deliberately crosscutting with child_5_17 and adult) |
adult |
18+ years | WG-SS / WG-ES |
Topical relevance, not eligibility
age_applicability answers "which age groups does this property concern?" It is not a filter primitive for eligibility. Age-based filtering is the consumer's job, computed from date_of_birth. Under this framing, overlap between tags is a feature, not a bug: a property about adolescent reproductive health carries both child_5_17 and adolescent because the topic genuinely concerns both the under-18 bracket and the WHO 10-19 bracket.
A consumer asking "is this field relevant for a 15-year-old?" evaluates the child's age against all of the property's bands and asks whether any match. A consumer asking "is this topic adolescence-specific?" checks for the adolescent tag specifically.
Population rules
- Only populate on properties that attach to
Person. Age-applicability is meaningless on concepts without an age. - Not required. Absence means the property applies broadly to any age.
- Validator enforces bibliography-implied coverage: properties cited by
washington-group-ssorwashington-group-esmust includeadult; properties cited bywashington-group-cfmmust include at least one of the child bands (child_2_4orchild_5_17). Properties may narrow CFM coverage where the definition text explains which variant they map to.
8. External equivalents vs. serialisation bindings
The external_equivalents field on properties was originally intended for equivalents in other ontologies (SEMIC Core Vocabularies, DCI Core): a property like given_name maps exactly to http://www.w3.org/ns/person#firstName. The match is semantic: both describe the same concept in an alternate ontology.
The same field is also used for serialisation bindings such as FHIR R4 Observation with LOINC codes. These are not equivalents in the semic/dci sense; they are instructions for how to serialise this property into a specific interop format. The distinction matters when reading a property detail page: a SEMIC row says "this concept exists in another ontology"; a FHIR/LOINC row says "when you serialise this data into FHIR, use this code."
Convention:
- Per-item LOINC codes belong on the property (each WG item has its own LOINC code).
- Whole-vocabulary LOINC answer-list references belong on the vocabulary (
standard.uri). Example:pregnancy-statuscarries one LOINC answer-list URI for the whole value set.
9. Sensitivity annotations
Some properties reveal sensitive circumstances regardless of whether they identify a specific person. program_ref reveals enrollment in a specific program (which may target HIV, disability, or poverty). grievance_type reveals that someone filed a complaint.
| Level | When to use | What it signals |
|---|---|---|
standard |
Default. No special handling beyond normal data protection. | Can be omitted (assumed if absent). |
sensitive |
Reveals circumstances (health, poverty, victimhood) in most contexts. | Requires justification to collect or disclose. |
restricted |
Should not appear in credentials at routine service points. | Requires a Data Protection Impact Assessment. |
This is a practitioner warning, not a compliance label. Whether a property constitutes personal data depends on the record it appears in, not the property itself. See Selective Disclosure for credential-level classification.
10. Display escape hatches
Some properties carry schema-level semantics that are intentionally decoupled from jurisdiction-specific display requirements. The certificate_label property on Parent is an example: the data model stores role-nature (biological, gestational, legal, adoptive) on parental_role, decoupled from gender, but some jurisdictions are required by law to print gendered or positional labels ("mother", "father", "parent 1", "parent 2") on the civil certificate. Rather than forcing the schema to carry gendered codes, certificate_label provides a free-text field for the label as it must appear on the printed document. The underlying role-nature remains machine-readable and gender-neutral; the display-layer requirement is satisfied without contaminating the data model.
See a problem on this page? Report it on GitHub.
Help improve this page
Highlight any text on this page to leave an annotation. Join our review group to get started.
If you have a GitHub account, click the feedback icon next to any section heading to report a specific issue.