@prefix sh:    <http://www.w3.org/ns/shacl#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:   <http://www.w3.org/2001/XMLSchema#> .
@prefix owl:   <http://www.w3.org/2002/07/owl#> .
@prefix top:   <https://top.scientix.ai/onto/clinical/v1#> .
@prefix topc:  <https://top.scientix.ai/onto/commons/v1#> .
@prefix fhir:  <http://hl7.org/fhir/> .
@prefix usdm:  <https://www.cdisc.org/standards/usdm/v3#> .
@prefix cdash: <https://www.cdisc.org/standards/cdashig/v2-1#> .
@prefix ngsi:  <https://uri.etsi.org/ngsi-ld/> .
@prefix prov:  <http://www.w3.org/ns/prov#> .

# TOP SHACL shapes · source version: 0.7.0-strawman
# Generated by build_shacl.py · do not edit by hand
# Validates NGSI-LD entity instances against the reference graph's structural constraints

# === Top-level classes ===

top:Sponsor rdfs:subClassOf prov:Agent .

top:SponsorShape a sh:NodeShape ;
    sh:targetClass top:Sponsor ;
    sh:name "Sponsor" ;
    sh:description "A Sponsor is an entity responsible for some aspect of a clinical trial: initiation, regulatory accountability, financing, or operational conduct. A study has at least one sponsor of record, who carries regulatory responsibility, and may have additional Sponsor entities for operational delegation (a CRO acting as proxy), financial backing (an academic medical center on an investigator-initiated trial), or co-sponsorship. The Sponsor entity is what the site, the study team, and the regulator interact with day to day. Corporate identity and parent/child structure live on the linked Organization." ;
    sh:property [
        sh:path top:sponsorId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "sponsorId" ;
    ] ;
    sh:property [
        sh:path top:sponsorName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name." ;
        sh:name "sponsorName" ;
    ] ;
    sh:property [
        sh:path top:legalName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Registered legal name." ;
        sh:name "legalName" ;
    ] ;
    sh:property [
        sh:path top:sponsorType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PHARMACEUTICAL" "BIOTECH" "ACADEMIC" "GOVERNMENT" "INVESTIGATOR_SPONSOR" "CRO_AS_SPONSOR" "OTHER" ) ;
        sh:description "Sponsor type taxonomy. CRO_AS_SPONSOR covers CROs acting as legal sponsor. INVESTIGATOR_SPONSOR covers academic single-PI sponsorship." ;
        sh:name "sponsorType" ;
    ] ;
    sh:property [
        sh:path top:duns ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Dun & Bradstreet DUNS number." ;
        sh:name "duns" ;
    ] ;
    sh:property [
        sh:path top:orcid ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ORCID identifier when sponsor is an individual investigator-sponsor." ;
        sh:name "orcid" ;
    ] ;
    sh:property [
        sh:path top:address ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Headquarters address (line1, line2, city, region, postalCode)." ;
        sh:name "address" ;
    ] ;
    sh:property [
        sh:path top:country ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3 country code of registration." ;
        sh:name "country" ;
    ] ;
    sh:property [
        sh:path top:phone ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Primary phone." ;
        sh:name "phone" ;
    ] ;
    sh:property [
        sh:path top:email ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Primary email." ;
        sh:name "email" ;
    ] ;
    sh:property [
        sh:path top:website ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Sponsor website." ;
        sh:name "website" ;
    ] ;
    sh:property [
        sh:path top:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "INACTIVE" "MERGED" "ACQUIRED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path top:isSponsorOfRecord ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "True if this Sponsor entity holds regulatory accountability per 21 CFR 312 (or equivalent in other jurisdictions). Exactly one Sponsor entity per (Study × RegulatoryAuthorityScope) carries this flag set true; SHACL constraint enforces the rule per jurisdiction." ;
        sh:name "isSponsorOfRecord" ;
    ] ;
    sh:property [
        sh:path top:hasRegulatoryResponsibility ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "True if this Sponsor entity carries regulatory accountability for the Study. Often the same as isSponsorOfRecord but separated for the rare case where regulatory submission is delegated without transfer of legal sponsor-of-record status." ;
        sh:name "hasRegulatoryResponsibility" ;
    ] ;
    sh:property [
        sh:path top:hasFinancialResponsibility ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "True if this Sponsor entity provides funding for the Study. Distinct from sponsor-of-record: an academic medical center may be the financial sponsor while a PI is the legal sponsor of an IIT." ;
        sh:name "hasFinancialResponsibility" ;
    ] ;
    sh:property [
        sh:path top:hasOperationalResponsibility ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "True if this Sponsor entity manages day-to-day operational conduct of the Study. Most common case where a CRO acts as proxy for the sponsor of record." ;
        sh:name "hasOperationalResponsibility" ;
    ] ;
    sh:property [
        sh:path top:isInitiator ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "True if this Sponsor entity initiated the Study (the entity that triggered the trial). Often the same as the financial sponsor for IITs; often the same as sponsor of record for industry-sponsored trials." ;
        sh:name "isInitiator" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Datetime from which this Sponsor relationship is operationally valid. Empty for the initial Sponsor record on a Study. Populated when sponsor transfers occur (M&A, divestiture, asset sale). Uses NGSI-LD's validFrom temporal property semantics." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Datetime through which this Sponsor relationship was operationally valid. Empty for currently-active Sponsor records. Populated when this Sponsor record is superseded by a new one (M&A transfer, sponsor handoff). Acquiring entities in M&A often do not move all studies at once; the temporal bounds are how this gets modeled honestly." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:primaryTherapeuticArea ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "The therapeutic area this Sponsor entity's role on this Study is focused on (oncology, cardiology, infectious disease, rare disease, etc.). Per-Study lens: the Sponsor entity's TA focus for this specific Study, which may be a single area even when the parent Organization's portfolio spans many. The corporate TA spread lives on Organization.primaryTherapeuticAreas. Optional because not every Study has a single dominant TA (basket trials, platform trials)." ;
        sh:name "primaryTherapeuticArea" ;
    ] ;
    sh:property [
        sh:path top:belongsToOrganization ;
        sh:class topc:Organization ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization (corporate / legal entity) this Sponsor entity is backed by. USDM models Sponsor as a type on Organization; TOP keeps Sponsor as an operator-grade top-level and uses this relationship to anchor it to the corporate truth. J&J Innovative Medicine (Sponsor) → belongsToOrganization → Organization{name: 'J&J Innovative Medicine', parentOrganization: J&J}." ;
        sh:name "belongsToOrganization" ;
    ] ;
    sh:property [
        sh:path top:actsOnBehalfOf ;
        sh:class top:Sponsor ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Self-ref: this Sponsor entity acts as proxy for another Sponsor entity (typically a CRO operational sponsor pointing to the sponsor of record). Empty when this Sponsor is itself the principal. The IQVIA-Sponsor entity that operationally runs Pfizer's Study X has actsOnBehalfOf → Pfizer-Sponsor entity. Distinct from parentSponsor: actsOnBehalfOf is operational delegation, parentSponsor is structural lineage." ;
        sh:name "actsOnBehalfOf" ;
    ] ;
    sh:property [
        sh:path top:parentSponsor ;
        sh:class top:Sponsor ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Self-ref: this Sponsor entity has a structural parent Sponsor entity. Use only when the per-Org-per-Study lineage is meaningfully different from what Organization.parentOrganization expresses. Three operational cases: (1) M&A successor lineage, where Sponsor B supersedes Sponsor A on the same Study after corporate transfer, B.parentSponsor → A captures the lineage handoff alongside validFrom/validUntil. (2) Lead/co-sponsor in co-development, where the co-sponsor entity points at the lead-sponsor entity. (3) EU CTR Article 74 legal representatives, where a non-EU principal Sponsor designates an EU Sponsor entity as their legal rep, with the EU rep's parentSponsor → principal. Distinct from belongsToOrganization (corporate identity) and actsOnBehalfOf (operational proxy)." ;
        sh:name "parentSponsor" ;
    ] ;
    sh:property [
        sh:path top:regulatoryAuthorityScope ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Regulatory authorities this Sponsor entity is the sponsor of record under. Used for multi-jurisdictional studies where different legal entities act as sponsor of record in different jurisdictions (Pfizer Inc under FDA, Pfizer Ireland under EMA, on the same trial). Empty for single-jurisdiction studies; the SHACL 'exactly one sponsor of record per Study' constraint relaxes to per-jurisdiction." ;
        rdfs:comment "TARGET-MISSING: RegulatoryAuthority is a flagged-missing object in OOUX v0.2 (#60)." ;
        sh:name "regulatoryAuthorityScope" ;
    ] ;
    sh:property [
        sh:path top:runs ;
        sh:class top:Study ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this Sponsor entity is scoped to. Each Sponsor entity is per-Organization-per-Study. A company that sponsors many studies has many Sponsor entities, one per study, all linked through belongsToOrganization to the same Organization. The enterprise-level 'all studies Pfizer sponsors' view is served from Organization.playsSponsorRole, not from a single Sponsor entity." ;
        sh:name "runs" ;
    ] ;
    sh:property [
        sh:path top:employs ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Employees and contractors. 0..N at the per-Study Sponsor scope: a Sponsor record may be created before staffing assignments are linked. Enterprise-level employee counts roll up via Organization." ;
        sh:name "employs" ;
    ] ;
    sh:property [
        sh:path top:engages ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sites the sponsor has contracted on this Study. 0..N because Sponsor records exist before site selection completes." ;
        sh:name "engages" ;
    ] ;
    sh:property [
        sh:path top:contractsWith ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Contracted CROs." ;
        rdfs:comment "TARGET-MISSING: CRO is a flagged-missing object in OOUX v0.2; needs its own catalog entry." ;
        sh:name "contractsWith" ;
    ] ;
    sh:property [
        sh:path top:holds ;
        sh:class top:Contract ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "All sponsor-side contracts on this Study. 0..N because contracts get signed over time, not at Sponsor-record creation." ;
        sh:name "holds" ;
    ] ;
    sh:property [
        sh:path top:authors ;
        sh:class top:Budget ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Budgets the sponsor maintains for this Study. 0..N because budgets may be authored downstream." ;
        sh:name "authors" ;
    ] ;
    sh:property [
        sh:path top:publishesDocument ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor-controlled documents on this Study (TMF and beyond). 0..N because TMF accumulates over the Study lifecycle. Split from the polysemous 'publishes' verb in v0.1.2 so each predicate carries one target type, preserving clean projection to USDM and FHIR." ;
        sh:name "publishesDocument" ;
    ] ;
    sh:property [
        sh:path top:publishesPublication ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Resulting publications (peer-reviewed, conference, preprint). 0..N because most Studies have not produced publications yet at any given moment. Split from the polysemous 'publishes' verb in v0.1.2 so each predicate carries one target type, preserving clean projection to USDM and FHIR." ;
        rdfs:comment "TARGET-MISSING: Publication is not in OOUX v0.2 catalog; treat as Document subtype until specified." ;
        sh:name "publishesPublication" ;
    ] ;
    sh:property [
        sh:path top:files ;
        sh:class top:RegulatorySubmission ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Submissions filed by the sponsor on this Study. 0..N because submissions accumulate; a brand-new Sponsor record has zero filings yet." ;
        sh:name "files" ;
    ] ;
    sh:property [
        sh:path top:commissions ;
        sh:class top:Audit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor-commissioned audits. 0..N: small IITs and academic-sponsored studies may never commission a sponsor-level audit." ;
        sh:name "commissions" ;
    ] ;
    sh:property [
        sh:path top:operatesTrainingProgram ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor training programs on this Study. 0..N at per-Study Sponsor scope. Split from the polysemous 'operates' verb in v0.1.2 so each predicate carries one target type, preserving clean projection to USDM and FHIR." ;
        rdfs:comment "TARGET-MISSING: TrainingProgram is not in OOUX v0.2 catalog; TrainingRecord (#77) covers individual completions; TrainingProgram needs its own entry." ;
        sh:name "operatesTrainingProgram" ;
    ] ;
    sh:property [
        sh:path top:maintains ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Standard operating procedures attached to this Study. 0..N: SOPs are typically maintained at the Organization level and inherited." ;
        rdfs:comment "TARGET-MISSING: SOP is not in OOUX v0.2 catalog; treat as Document subtype until specified." ;
        sh:name "maintains" ;
    ] ;
    sh:property [
        sh:path top:operatesSystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor-owned systems on this Study (EDC, CTMS, eTMF, IRT, safety database). 0..N because system attachment happens during Study startup, not at Sponsor creation. Split from the polysemous 'operates' verb in v0.1.2 so each predicate carries one target type, preserving clean projection to USDM and FHIR." ;
        sh:name "operatesSystem" ;
    ] ;
    sh:property [
        sh:path top:produces ;
        sh:class top:Report ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Reports authored by the sponsor on this Study. 0..N because reports accumulate." ;
        sh:name "produces" ;
    ] ;
    sh:property [
        sh:path top:plans ;
        sh:class top:Milestone ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Program-level milestones. 0..N because milestones get planned downstream of Sponsor record creation." ;
        sh:name "plans" ;
    ] ;
    sh:property [
        sh:path top:conducts ;
        sh:class top:RiskAssessment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Risk assessments owned by the sponsor on this Study. 0..N because risk assessment is a workflow that produces records over time." ;
        sh:name "conducts" ;
    ] ;
    sh:property [
        sh:path top:executes ;
        sh:class top:CAPA ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor-driven CAPAs. 0..N: most Sponsor records have zero CAPAs at any given snapshot." ;
        sh:name "executes" ;
    ] ;
    sh:property [
        sh:path top:interfacesWith ;
        sh:class top:OversightBody ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "IRB, EC, DSMB, IDMC engagement. 0..N because oversight engagement gets established during startup, not at Sponsor creation." ;
        sh:name "interfacesWith" ;
    ] ;
    sh:property [
        sh:path top:signs ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Data transfer agreements. 0..N because DTAs are signed downstream of Sponsor record creation." ;
        rdfs:comment "TARGET-MISSING: DataTransferAgreement is a Contract subtype not separately enumerated in OOUX v0.2." ;
        sh:name "signs" ;
    ] ;
    sh:property [
        sh:path top:supplies ;
        sh:class top:InvestigationalProduct ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "IP the sponsor supplies on this Study. 0..N because IP supply chain attaches downstream, and observational studies may not involve IP at all." ;
        sh:name "supplies" ;
    ] ;
    sh:property [
        sh:path top:organizes ;
        sh:class top:Tag ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "User-defined organizational tags." ;
        sh:name "organizes" ;
    ] .

top:actsOnBehalfOf rdfs:subPropertyOf prov:actedOnBehalfOf .
top:parentSponsor rdfs:subPropertyOf prov:wasDerivedFrom .

top:Study rdfs:subClassOf prov:Entity .

top:StudyShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:name "Study" ;
    sh:description "A Study is a research project intended to evaluate the effects of an intervention (interventional), to characterize a population or natural history (observational), or to provide expanded access to an investigational product. The Study entity is the central organizing concept of the clinical-trials reference graph: every other top-level relates to Study somehow. Study is reached upstream from Sponsor (via Sponsor.runs, 1..1) and from StudySite (via StudySite.forStudy, 1..1). Study itself owns the definition-side chain (Protocol, Arms, Schedule of Assessments, Endpoints, Inclusion/Exclusion Criteria) as sub-objects. Operational facts (per-Site participation, per-Participant data, per-Visit-occurrence events) are reached through StudySite, Participant, and Visit-occurrence — not as direct edges on Study, per the direct-vs-traversed discipline established in the Site spec." ;
    sh:property [
        sh:path top:studyId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "studyId" ;
    ] ;
    sh:property [
        sh:path top:sponsorProtocolId ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Sponsor's internal protocol identifier (e.g., 'ONCO-423-001'). Distinct from registry IDs (NCT, EudraCT)." ;
        sh:name "sponsorProtocolId" ;
    ] ;
    sh:property [
        sh:path top:studyTitle ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Official long title of the study (e.g., 'A Phase 3 Randomized Double-Blind Placebo-Controlled Study of Drug X in Patients with Advanced Non-Small Cell Lung Cancer'). Optional at strawman; required when Study is registered with ClinicalTrials.gov / EU CTIS." ;
        sh:name "studyTitle" ;
    ] ;
    sh:property [
        sh:path top:studyShortTitle ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly short title used in dashboards and conversation." ;
        sh:name "studyShortTitle" ;
    ] ;
    sh:property [
        sh:path top:studyAcronym ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Single-token acronym (e.g., 'ONCO-423')." ;
        sh:name "studyAcronym" ;
    ] ;
    sh:property [
        sh:path top:studyDescription ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Narrative description of the study's purpose and design." ;
        sh:name "studyDescription" ;
    ] ;
    sh:property [
        sh:path top:studyType ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "INTERVENTIONAL" "OBSERVATIONAL" "EXPANDED_ACCESS" "PATIENT_REGISTRY" ) ;
        sh:description "Study category. Optional at strawman to accommodate PLANNED-state Studies. Aligned with ClinicalTrials.gov StudyType and USDM StudyDesign.studyType." ;
        sh:name "studyType" ;
    ] ;
    sh:property [
        sh:path top:studyPhase ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "PHASE_0" "PHASE_1" "PHASE_1_2" "PHASE_2" "PHASE_2_3" "PHASE_3" "PHASE_4" "NOT_APPLICABLE" ) ;
        sh:description "Trial phase per ICH E8 / ClinicalTrials.gov / USDM. NOT_APPLICABLE for observational and registry studies." ;
        sh:name "studyPhase" ;
    ] ;
    sh:property [
        sh:path top:interventionModel ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "SINGLE_GROUP" "PARALLEL" "CROSSOVER" "FACTORIAL" "SEQUENTIAL" ) ;
        sh:description "ClinicalTrials.gov interventional model assignment." ;
        sh:name "interventionModel" ;
    ] ;
    sh:property [
        sh:path top:allocation ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "RANDOMIZED" "NON_RANDOMIZED" "NOT_APPLICABLE" ) ;
        sh:description "Allocation model per ClinicalTrials.gov / ICH E8." ;
        sh:name "allocation" ;
    ] ;
    sh:property [
        sh:path top:masking ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "OPEN_LABEL" "SINGLE_BLIND" "DOUBLE_BLIND" "TRIPLE_BLIND" "QUADRUPLE_BLIND" ) ;
        sh:description "Blinding/masking level. Aligned with ClinicalTrials.gov Masking and USDM." ;
        sh:name "masking" ;
    ] ;
    sh:property [
        sh:path top:primaryPurpose ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "TREATMENT" "PREVENTION" "DIAGNOSTIC" "SUPPORTIVE_CARE" "SCREENING" "HEALTH_SERVICES_RESEARCH" "BASIC_SCIENCE" "DEVICE_FEASIBILITY" "OTHER" ) ;
        sh:description "ClinicalTrials.gov primary purpose classification." ;
        sh:name "primaryPurpose" ;
    ] ;
    sh:property [
        sh:path top:studyStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PLANNED" "RECRUITING" "ENROLLING_BY_INVITATION" "ACTIVE_NOT_RECRUITING" "COMPLETED" "SUSPENDED" "TERMINATED" "WITHDRAWN" "UNKNOWN_STATUS" ) ;
        sh:description "Lifecycle state. Aligned with ClinicalTrials.gov OverallStatus and FHIR ResearchStudy.status. Distinct from per-StudySite status (which lives on StudySite.studySiteStatus)." ;
        sh:name "studyStatus" ;
    ] ;
    sh:property [
        sh:path top:conditionStudied ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Primary condition or disease being studied (e.g., 'Advanced Non-Small Cell Lung Cancer'). Free text or SNOMED-coded; comma-separated for multiple conditions." ;
        sh:name "conditionStudied" ;
    ] ;
    sh:property [
        sh:path top:therapeuticArea ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Therapeutic area classification (e.g., ONCOLOGY, CARDIOLOGY, NEUROLOGY, IMMUNOLOGY). Per-Study TA; corporate-portfolio TA spread lives on the linked Sponsor's Organization." ;
        sh:name "therapeuticArea" ;
    ] ;
    sh:property [
        sh:path top:targetEnrollment ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Target enrollment across all sites for the entire study. Aggregate; per-StudySite enrollment targets live on StudySite.enrollmentTarget." ;
        sh:name "targetEnrollment" ;
    ] ;
    sh:property [
        sh:path top:actualEnrollment ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Current actual enrollment across all sites. Aggregate; computable as SUM over StudySite.actualEnrollment but stored for view-speed." ;
        sh:name "actualEnrollment" ;
    ] ;
    sh:property [
        sh:path top:enrollmentType ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "ANTICIPATED" "ACTUAL" ) ;
        sh:description "Whether targetEnrollment is anticipated or actual. Aligned with ClinicalTrials.gov EnrollmentType." ;
        sh:name "enrollmentType" ;
    ] ;
    sh:property [
        sh:path top:minAge ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Minimum age for participant eligibility (e.g., '18 Years', '6 Months'). String to preserve unit." ;
        sh:name "minAge" ;
    ] ;
    sh:property [
        sh:path top:maxAge ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Maximum age for participant eligibility." ;
        sh:name "maxAge" ;
    ] ;
    sh:property [
        sh:path top:sexEligibility ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "ALL" "MALE" "FEMALE" ) ;
        sh:description "Sex eligibility per ClinicalTrials.gov." ;
        sh:name "sexEligibility" ;
    ] ;
    sh:property [
        sh:path top:acceptsHealthyVolunteers ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Whether the study accepts healthy volunteers." ;
        sh:name "acceptsHealthyVolunteers" ;
    ] ;
    sh:property [
        sh:path top:plannedStartDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Planned date of first patient first visit." ;
        sh:name "plannedStartDate" ;
    ] ;
    sh:property [
        sh:path top:actualStartDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Actual date of first patient first visit (FPI). Empty until FPI is achieved." ;
        sh:name "actualStartDate" ;
    ] ;
    sh:property [
        sh:path top:primaryCompletionDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date of last primary outcome measure assessment per ClinicalTrials.gov. Distinct from actualCompletionDate (last visit, all outcomes)." ;
        sh:name "primaryCompletionDate" ;
    ] ;
    sh:property [
        sh:path top:plannedCompletionDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "plannedCompletionDate" ;
    ] ;
    sh:property [
        sh:path top:actualCompletionDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date of last patient last visit (LPLV). Empty until LPLV is achieved." ;
        sh:name "actualCompletionDate" ;
    ] ;
    sh:property [
        sh:path top:responsibleParty ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "SPONSOR" "INVESTIGATOR_SPONSOR" "SPONSOR_INVESTIGATOR" "OTHER" ) ;
        sh:description "Who is responsible for the study per ClinicalTrials.gov ResponsibleParty.type. INVESTIGATOR_SPONSOR for IITs where the PI is the sponsor of record; SPONSOR_INVESTIGATOR for industry-sponsored with sub-investigator named." ;
        sh:name "responsibleParty" ;
    ] ;
    sh:property [
        sh:path top:studyVersion ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Current version identifier for the study definition (e.g., '3.2.1'). Mirrors USDM StudyVersion.versionIdentifier; richer multi-version semantics (separate StudyVersion sub-object) lifts in v0.4 if operationally needed. Amendments are tracked via the hasAmendment relationship." ;
        sh:name "studyVersion" ;
    ] ;
    sh:property [
        sh:path top:clinicalTrialsGovId ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ClinicalTrials.gov NCT identifier (e.g., 'NCT99999423'). Optional: IITs and early-phase trials may not have NCT IDs at time of Study creation; populated when ClinicalTrials.gov registration completes." ;
        sh:name "clinicalTrialsGovId" ;
    ] ;
    sh:property [
        sh:path top:eudraCT ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "EudraCT (EU Clinical Trials Register) identifier (e.g., '2026-001234-56'). For studies submitted under the EU Clinical Trials Regulation; replaced by EU CTIS identifier for new submissions." ;
        sh:name "eudraCT" ;
    ] ;
    sh:property [
        sh:path top:euCtisId ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "EU Clinical Trials Information System (CTIS) identifier; the EU CTR Article 80 register identifier replacing EudraCT for new submissions." ;
        sh:name "euCtisId" ;
    ] ;
    sh:property [
        sh:path top:isrctn ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISRCTN registry identifier (UK)." ;
        sh:name "isrctn" ;
    ] ;
    sh:property [
        sh:path top:nciId ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NCI Clinical Trials ID (oncology studies)." ;
        sh:name "nciId" ;
    ] ;
    sh:property [
        sh:path top:otherRegistryIds ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated additional registry identifiers (ANZCTR, ChiCTR, JPRN, etc.). Free-form for registries not enumerated above." ;
        sh:name "otherRegistryIds" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated user-defined tags." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:hasProtocol ;
        sh:class top:Protocol ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The current effective Protocol for this Study. Sub-object. Protocol amendments are tracked via the hasAmendment relationship; the hasProtocol points at the current effective version." ;
        sh:name "hasProtocol" ;
    ] ;
    sh:property [
        sh:path top:hasArm ;
        sh:class top:Arm ;
        sh:minCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Study arms (treatment groups). Sub-object. Single-arm studies have one Arm entity. Multi-arm studies (parallel, crossover, factorial) have multiple. Aligned with USDM StudyDesign.arms." ;
        sh:name "hasArm" ;
    ] ;
    sh:property [
        sh:path top:hasSchedule ;
        sh:class top:ScheduleOfAssessments ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Schedule(s) of Assessments. Sub-object. 0..N because PLANNED-state Studies may not yet have an SOA; SHACL invariant enforces presence once Study is ACTIVE. 0..N also handles the case where SOA versions across protocol amendments are tracked separately. Aligned with USDM StudyDesign.scheduleTimelines." ;
        sh:name "hasSchedule" ;
    ] ;
    sh:property [
        sh:path top:hasEndpoint ;
        sh:class top:Endpoint ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Study endpoints (primary, secondary, exploratory, safety). Sub-object. 0..N because PLANNED-state Studies may not yet have endpoints defined; SHACL invariant enforces presence once Study is ACTIVE. Aligned with USDM StudyDesign.objectives.endpoints." ;
        sh:name "hasEndpoint" ;
    ] ;
    sh:property [
        sh:path top:hasInclusionCriterion ;
        sh:class top:InclusionCriterion ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Inclusion criteria for participant eligibility. Sub-object. 0..N because PLANNED-state Studies may not yet have criteria; SHACL invariant enforces presence once Study is RECRUITING. Aligned with USDM StudyDesign.eligibilityCriteria (subset of inclusion type)." ;
        sh:name "hasInclusionCriterion" ;
    ] ;
    sh:property [
        sh:path top:hasExclusionCriterion ;
        sh:class top:ExclusionCriterion ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Exclusion criteria for participant eligibility. Sub-object. 0..N because rare cases (some registries) may have no formal exclusion criteria. Aligned with USDM StudyDesign.eligibilityCriteria (subset of exclusion type)." ;
        sh:name "hasExclusionCriterion" ;
    ] ;
    sh:property [
        sh:path top:hasAmendment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Protocol amendments tracked over the Study lifecycle. Each Amendment captures the change history; the current effective Protocol is reached via hasProtocol." ;
        rdfs:comment "TARGET-MISSING: Amendment is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasAmendment" ;
    ] ;
    sh:property [
        sh:path top:hasMilestone ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Study-level milestones (FPI, LPI, DBL, CSR, IND submission, etc.). Site-level milestones live on StudySite.activationDate / deactivationDate." ;
        rdfs:comment "TARGET-MISSING: Milestone is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasMilestone" ;
    ] ;
    sh:property [
        sh:path top:suppliesInvestigationalProduct ;
        sh:class top:InvestigationalProduct ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "IPs supplied for this Study. 0..N because observational studies may have none. Mirrors Sponsor.supplies (the per-Sponsor IP list)." ;
        sh:name "suppliesInvestigationalProduct" ;
    ] ;
    sh:property [
        sh:path top:hasOversightBody ;
        sh:class top:OversightBody ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Study-level oversight bodies (DSMB, IDMC). Per-Site IRB/EC of record lives on StudySite.hasIRB." ;
        sh:name "hasOversightBody" ;
    ] ;
    sh:property [
        sh:path top:hasStudyStartupPackage ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study Startup Package containing essential documents and templates distributed to sites during initiation." ;
        rdfs:comment "TARGET-MISSING: StudyStartupPackage is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasStudyStartupPackage" ;
    ] ;
    sh:property [
        sh:path top:usesSystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study Systems (the EDC, CTMS, IRT, eTMF, safety database used for this Study). Sites and Sponsors using these Systems reach them via the System's three-axis relationships (operatedBy, usedBy, oversightHeldBy)." ;
        sh:name "usesSystem" ;
    ] ;
    sh:property [
        sh:path top:hasStatisticalAnalysisPlan ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Statistical Analysis Plan (SAP) document. Document.documentType=SOP or a future SAP-specific subtype." ;
        sh:name "hasStatisticalAnalysisPlan" ;
    ] ;
    sh:property [
        sh:path top:hasStudyTeam ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Study team members (Sponsor PM, Biostatistician, Medical Monitor, Pharmacovigilance Lead, etc.). Orthogonal to per-StudySite delegation (which is on StudySite.delegatesAuthorityTo). The same Person can be on multiple Studies' teams." ;
        sh:name "hasStudyTeam" ;
    ] ;
    sh:property [
        sh:path top:hasTag ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "User-defined tags applied to this Study." ;
        rdfs:comment "TARGET-MISSING: Tag is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasTag" ;
    ] ;
    sh:property [
        sh:path top:hasRiskAssessment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Risk assessments owned at the Study level (per ICH E8(R1) quality-by-design). Per-StudySite risk-tier is captured on StudySite.oversightTier." ;
        rdfs:comment "TARGET-MISSING: RiskAssessment is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasRiskAssessment" ;
    ] .

top:Protocol rdfs:subClassOf prov:Entity .

top:ProtocolShape a sh:NodeShape ;
    sh:targetClass top:Protocol ;
    sh:name "Protocol" ;
    sh:description "The Protocol is the formal document defining the study's design, methodology, statistical plan, and conduct procedures. Sub-object of Study. Protocol amendments are tracked via Study.hasAmendment; this Protocol points at the current effective version." ;
    sh:property [
        sh:path top:protocolId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "protocolId" ;
    ] ;
    sh:property [
        sh:path top:protocolNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Sponsor's protocol identifier (often duplicates Study.sponsorProtocolId; kept distinct because the protocol-as-document has its own identity separate from the Study-as-trial)." ;
        sh:name "protocolNumber" ;
    ] ;
    sh:property [
        sh:path top:protocolTitle ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Protocol document title (often duplicates Study.studyTitle for consistency, but kept as separate field for cases where the document title differs)." ;
        sh:name "protocolTitle" ;
    ] ;
    sh:property [
        sh:path top:protocolVersion ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Semver-style version (e.g., '3.0.0', '3.2.1'). Each amendment increments." ;
        sh:name "protocolVersion" ;
    ] ;
    sh:property [
        sh:path top:protocolStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "DRAFT" "IN_REVIEW" "APPROVED" "EFFECTIVE" "AMENDED" "SUPERSEDED" "RETIRED" ) ;
        sh:description "Lifecycle state. EFFECTIVE means currently in force; AMENDED means a successor version exists; SUPERSEDED means replaced; RETIRED means Study closed and Protocol archived." ;
        sh:name "protocolStatus" ;
    ] ;
    sh:property [
        sh:path top:approvalDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date of regulatory or IRB approval of this Protocol version." ;
        sh:name "approvalDate" ;
    ] ;
    sh:property [
        sh:path top:effectiveDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date this Protocol version became effective at sites." ;
        sh:name "effectiveDate" ;
    ] ;
    sh:property [
        sh:path top:protocolDocumentRef ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "URI of the actual Protocol PDF (typically in eTMF). Distinct from the JSON-LD/RDF representation." ;
        sh:name "protocolDocumentRef" ;
    ] ;
    sh:property [
        sh:path top:ddfDocumentRef ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "URI of the USDM Digital Definition Format JSON document (machine-readable protocol). Aligned with USDM v3 DDF deliverable." ;
        sh:name "ddfDocumentRef" ;
    ] ;
    sh:property [
        sh:path top:studyDesignRationale ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Narrative rationale for the chosen study design. Aligned with USDM StudyDesign.rationale." ;
        sh:name "studyDesignRationale" ;
    ] .

top:Arm rdfs:subClassOf prov:Entity .

top:ArmShape a sh:NodeShape ;
    sh:targetClass top:Arm ;
    sh:name "Arm" ;
    sh:description "An Arm is a group of Participants who receive a specific intervention (or no intervention) per the protocol. Sub-object of Study. Single-arm studies have one Arm; multi-arm studies (parallel, crossover, factorial) have multiple. Each Participant is assigned to exactly one Arm at randomization (or is unassigned in pre-randomization phases)." ;
    sh:property [
        sh:path top:armId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "armId" ;
    ] ;
    sh:property [
        sh:path top:armName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name (e.g., 'Drug X 200mg', 'Standard of Care', 'Placebo')." ;
        sh:name "armName" ;
    ] ;
    sh:property [
        sh:path top:armShortName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Abbreviated label for charts and dashboards." ;
        sh:name "armShortName" ;
    ] ;
    sh:property [
        sh:path top:armType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "EXPERIMENTAL" "ACTIVE_COMPARATOR" "PLACEBO_COMPARATOR" "NO_INTERVENTION" "SHAM_COMPARATOR" "OTHER" ) ;
        sh:description "Arm type per ClinicalTrials.gov ArmType." ;
        sh:name "armType" ;
    ] ;
    sh:property [
        sh:path top:armDescription ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Narrative description of what participants in this arm receive." ;
        sh:name "armDescription" ;
    ] ;
    sh:property [
        sh:path top:armSize ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Planned number of Participants for this arm." ;
        sh:name "armSize" ;
    ] ;
    sh:property [
        sh:path top:interventionDescription ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "One-line summary of the intervention (e.g., 'Drug X 200mg PO QD for 12 weeks')." ;
        sh:name "interventionDescription" ;
    ] ;
    sh:property [
        sh:path top:isPlaceboArm ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "isPlaceboArm" ;
    ] ;
    sh:property [
        sh:path top:isControlArm ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if this arm serves as the control (placebo, active comparator, or no-intervention)." ;
        sh:name "isControlArm" ;
    ] .

top:ScheduleOfAssessments rdfs:subClassOf prov:Entity .

top:ScheduleOfAssessmentsShape a sh:NodeShape ;
    sh:targetClass top:ScheduleOfAssessments ;
    sh:name "ScheduleOfAssessments" ;
    sh:description "The Schedule of Assessments (SOA) defines the visit-by-procedure matrix for the study: which procedures, assessments, samples, and data collections happen at which protocol-defined visit. Sub-object of Study. The SOA anchors the Visit-template chain: each row is a procedure or assessment; each column is a Visit-template. The cell at (procedure, visit) tells the operator whether that procedure is performed at that visit." ;
    sh:property [
        sh:path top:soaId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "soaId" ;
    ] ;
    sh:property [
        sh:path top:soaName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name (e.g., 'ONCO-423 Schedule of Assessments v3.0')." ;
        sh:name "soaName" ;
    ] ;
    sh:property [
        sh:path top:soaVersion ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Version identifier; tracks alongside Protocol version through amendments." ;
        sh:name "soaVersion" ;
    ] ;
    sh:property [
        sh:path top:soaStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "DRAFT" "APPROVED" "EFFECTIVE" "SUPERSEDED" ) ;
        sh:description "Lifecycle state. EFFECTIVE means currently in force at activated sites." ;
        sh:name "soaStatus" ;
    ] ;
    sh:property [
        sh:path top:approvalDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "approvalDate" ;
    ] ;
    sh:property [
        sh:path top:effectiveDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "effectiveDate" ;
    ] ;
    sh:property [
        sh:path top:soaDocumentRef ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "URI of the SOA artifact (typically a table embedded in the Protocol PDF or a separate Excel/PDF file)." ;
        sh:name "soaDocumentRef" ;
    ] ;
    sh:property [
        sh:path top:description ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Narrative description; useful when the SOA covers complex multi-arm or crossover designs." ;
        sh:name "description" ;
    ] .

top:Endpoint rdfs:subClassOf prov:Entity .

top:EndpointShape a sh:NodeShape ;
    sh:targetClass top:Endpoint ;
    sh:name "Endpoint" ;
    sh:description "An Endpoint is a measurable outcome used to evaluate the study's intervention or natural history. Sub-object of Study. Endpoints are categorized as PRIMARY (the main outcome the study is powered to detect), SECONDARY (additional pre-specified outcomes), EXPLORATORY (hypothesis-generating), or SAFETY (adverse event-driven). Each endpoint has a measurement type, a time frame, and an associated statistical approach." ;
    sh:property [
        sh:path top:endpointId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "endpointId" ;
    ] ;
    sh:property [
        sh:path top:endpointName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Short name for dashboards (e.g., 'Overall Survival', 'PFS at 12 months', 'C-max')." ;
        sh:name "endpointName" ;
    ] ;
    sh:property [
        sh:path top:endpointDescription ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Full text definition of the endpoint, including how it is measured and at what time points." ;
        sh:name "endpointDescription" ;
    ] ;
    sh:property [
        sh:path top:endpointType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PRIMARY" "SECONDARY" "EXPLORATORY" "SAFETY" ) ;
        sh:description "Endpoint category." ;
        sh:name "endpointType" ;
    ] ;
    sh:property [
        sh:path top:endpointMeasurementType ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "BINARY" "CONTINUOUS" "ORDINAL" "TIME_TO_EVENT" "COUNT" "RATE" "OTHER" ) ;
        sh:description "Statistical type of the measurement." ;
        sh:name "endpointMeasurementType" ;
    ] ;
    sh:property [
        sh:path top:measurementUnit ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Unit of measurement (e.g., 'months', 'mg/dL', '%')." ;
        sh:name "measurementUnit" ;
    ] ;
    sh:property [
        sh:path top:timeFrame ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Time window over which the endpoint is assessed (e.g., 'Up to 24 weeks', 'From randomization to death from any cause')." ;
        sh:name "timeFrame" ;
    ] ;
    sh:property [
        sh:path top:statisticalApproach ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Statistical method used to analyze this endpoint (e.g., 'Cox proportional hazards model', 'Kaplan-Meier survival analysis', 'Mixed-effects model for repeated measures')." ;
        sh:name "statisticalApproach" ;
    ] .

top:InclusionCriterion rdfs:subClassOf prov:Entity .

top:InclusionCriterionShape a sh:NodeShape ;
    sh:targetClass top:InclusionCriterion ;
    sh:name "InclusionCriterion" ;
    sh:description "An InclusionCriterion is a specific characteristic that potential Participants must have to be eligible for the Study. Sub-object of Study. Eligibility is determined per-Participant during screening; failed inclusion criteria result in screen failure." ;
    sh:property [
        sh:path top:criterionId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "criterionId" ;
    ] ;
    sh:property [
        sh:path top:criterionNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display number/code (e.g., 'I1', 'I2', '1.a'). Operator-readable identifier within the protocol." ;
        sh:name "criterionNumber" ;
    ] ;
    sh:property [
        sh:path top:criterionText ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Full text of the criterion as written in the Protocol." ;
        sh:name "criterionText" ;
    ] ;
    sh:property [
        sh:path top:criterionCategory ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "AGE" "SEX" "DIAGNOSIS" "DISEASE_STAGE" "LAB_VALUES" "MEDICAL_HISTORY" "PRIOR_TREATMENT" "MEDICATIONS" "PERFORMANCE_STATUS" "GENETIC" "OTHER" ) ;
        sh:description "Categorization for SFQ-style filtering and feasibility analysis." ;
        sh:name "criterionCategory" ;
    ] ;
    sh:property [
        sh:path top:waiverAllowed ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Whether deviations from this criterion are allowed via medical monitor approval (most criteria do NOT allow waivers)." ;
        sh:name "waiverAllowed" ;
    ] ;
    sh:property [
        sh:path top:waiverConditions ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Conditions under which a waiver may be granted (if waiverAllowed=true)." ;
        sh:name "waiverConditions" ;
    ] .

top:ExclusionCriterion rdfs:subClassOf prov:Entity .

top:ExclusionCriterionShape a sh:NodeShape ;
    sh:targetClass top:ExclusionCriterion ;
    sh:name "ExclusionCriterion" ;
    sh:description "An ExclusionCriterion is a specific characteristic that disqualifies potential Participants from the Study. Sub-object of Study. A Participant who meets ANY exclusion criterion is ineligible." ;
    sh:property [
        sh:path top:criterionId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "criterionId" ;
    ] ;
    sh:property [
        sh:path top:criterionNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display number/code (e.g., 'E1', 'E2', '2.b')." ;
        sh:name "criterionNumber" ;
    ] ;
    sh:property [
        sh:path top:criterionText ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Full text of the criterion." ;
        sh:name "criterionText" ;
    ] ;
    sh:property [
        sh:path top:criterionCategory ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "AGE" "SEX" "DIAGNOSIS" "DISEASE_STAGE" "LAB_VALUES" "MEDICAL_HISTORY" "PRIOR_TREATMENT" "MEDICATIONS" "PERFORMANCE_STATUS" "GENETIC" "PREGNANCY" "OTHER" ) ;
        sh:description "Categorization. PREGNANCY appears in exclusion category list because pregnancy/lactation is a near-universal exclusion in interventional studies but rarely an inclusion criterion." ;
        sh:name "criterionCategory" ;
    ] ;
    sh:property [
        sh:path top:waiverAllowed ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Whether deviations from this criterion are allowed via medical monitor approval." ;
        sh:name "waiverAllowed" ;
    ] ;
    sh:property [
        sh:path top:waiverConditions ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "waiverConditions" ;
    ] .

top:VisitDefinition rdfs:subClassOf prov:Entity .

top:VisitDefinitionShape a sh:NodeShape ;
    sh:targetClass top:VisitDefinition ;
    sh:name "VisitDefinition" ;
    sh:description "VisitDefinition captures what the protocol says happens at a given visit (template-side). It carries the visit window, expected duration, day-relative-to-anchor scheduling, and references to ActivityTemplates that should be performed. Distinct from Visit (the operational occurrence). USDM-ingest-derivable from StudyDesign.encounters[]; the operational-side Visit is created by EDC/scheduling/eVisit systems at runtime." ;
    sh:property [
        sh:path top:visitDefinitionId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier. Mints from USDM Encounter.id under the urn:top:study:{usdm_study_id}/visit-definition:{usdm_encounter_id} URI policy." ;
        sh:name "visitDefinitionId" ;
    ] ;
    sh:property [
        sh:path top:visitNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display number/code (e.g., 'V1', 'Visit 4', 'Day 28'). Operator-readable identifier within the protocol." ;
        sh:name "visitNumber" ;
    ] ;
    sh:property [
        sh:path top:visitName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly name (e.g., 'Screening Visit', 'Baseline / Day 1', 'End of Treatment')." ;
        sh:name "visitName" ;
    ] ;
    sh:property [
        sh:path top:visitDay ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Days from study anchor (typically randomization or first dose). Negative for screening (Day -7), 0 for baseline, positive for follow-up. Maps to SDTM VISITDY." ;
        sh:name "visitDay" ;
    ] ;
    sh:property [
        sh:path top:visitWindowMinDays ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Minimum days from visitDay the visit can occur and still be in-window. Negative offset (e.g., -3 means 'up to 3 days early')." ;
        sh:name "visitWindowMinDays" ;
    ] ;
    sh:property [
        sh:path top:visitWindowMaxDays ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Maximum days from visitDay the visit can occur and still be in-window. Positive offset (e.g., +3 means 'up to 3 days late')." ;
        sh:name "visitWindowMaxDays" ;
    ] ;
    sh:property [
        sh:path top:expectedDurationMinutes ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Expected duration of the visit in minutes (operational scheduling input)." ;
        sh:name "expectedDurationMinutes" ;
    ] ;
    sh:property [
        sh:path top:description ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Narrative description. Useful when the visit's purpose isn't obvious from name + day." ;
        sh:name "description" ;
    ] ;
    sh:property [
        sh:path top:visitDefinitionStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "DRAFT" "EFFECTIVE" "SUPERSEDED" ) ;
        sh:description "Lifecycle of the template. EFFECTIVE means currently in force; SUPERSEDED if a protocol amendment introduced a new VisitDefinition. NGSI-LD temporal property." ;
        sh:name "visitDefinitionStatus" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated user-defined tags." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:appearsInSchedule ;
        sh:class top:ScheduleOfAssessments ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Schedule(s) of Assessments this VisitDefinition appears in. 0..N because protocol amendments can reference the same VisitDefinition across multiple SOA versions." ;
        sh:name "appearsInSchedule" ;
    ] ;
    sh:property [
        sh:path top:hasExpectedActivityTemplate ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "ActivityTemplates expected at this visit per the SOA. Flagged-missing for now: ActivityTemplate as a separate entity defers to v0.6+; v0.5 carries this as a forward reference for future lift." ;
        rdfs:comment "TARGET-MISSING: ActivityTemplate is a v0.6+ entity; for v0.5 the SOA grid is referenced indirectly via this relationship. Visit-occurrences carry runtime Activity sub-objects directly." ;
        sh:name "hasExpectedActivityTemplate" ;
    ] .

top:Site rdfs:subClassOf prov:Agent .

top:SiteShape a sh:NodeShape ;
    sh:targetClass top:Site ;
    sh:name "Site" ;
    sh:description "A Site is the location or set of locations where trial-related activities are conducted and/or coordinated under the investigator's/institution's oversight (ICH E6(R3) Annex 1 Section 2). Site captures the operational reality and stable feasibility profile of a research site. Per-Study facts (siteNumber, enrollmentTarget, IRB approval status, oversight tier, per-Study staff delegation, per-Study Systems and Contracts) live on the StudySite horizontal, which captures Site's role on a specific Study. Site itself is study-independent: a Site exists with its staff, equipment, IRB-of-record default, qualifications, and infrastructure regardless of which Studies are active." ;
    sh:property [
        sh:path top:siteId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "siteId" ;
    ] ;
    sh:property [
        sh:path top:siteName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name." ;
        sh:name "siteName" ;
    ] ;
    sh:property [
        sh:path top:siteAlias ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operating name when distinct from legal Site name." ;
        sh:name "siteAlias" ;
    ] ;
    sh:property [
        sh:path top:siteType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "HOSPITAL" "CLINIC" "ACADEMIC_MEDICAL_CENTER" "RESEARCH_CENTER" "COMMUNITY_PRACTICE" "DECENTRALIZED_HUB" "VIRTUAL" ) ;
        sh:description "Site type taxonomy. DECENTRALIZED_HUB and VIRTUAL cover R3 Annex 2 non-traditional modalities." ;
        sh:name "siteType" ;
    ] ;
    sh:property [
        sh:path top:address ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Primary address. For DECENTRALIZED_HUB sites this may be 0..N when the hub serves multiple geographic locations; the strawman emits as a single object and the broker layer normalizes to multi-valued when needed." ;
        sh:name "address" ;
    ] ;
    sh:property [
        sh:path top:city ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "city" ;
    ] ;
    sh:property [
        sh:path top:state ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "state" ;
    ] ;
    sh:property [
        sh:path top:country ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3 country code." ;
        sh:name "country" ;
    ] ;
    sh:property [
        sh:path top:postalCode ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "postalCode" ;
    ] ;
    sh:property [
        sh:path top:phone ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "phone" ;
    ] ;
    sh:property [
        sh:path top:email ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "email" ;
    ] ;
    sh:property [
        sh:path top:website ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "website" ;
    ] ;
    sh:property [
        sh:path top:gcpQualified ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site holds a current GCP qualification at the institution level (R3 Section 2.1). Per-Study GCP currency for individual staff lives on Person.gcpTrainingStatus." ;
        sh:name "gcpQualified" ;
    ] ;
    sh:property [
        sh:path top:facilitiesAdequate ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "General facilities adequacy for clinical research (R3 Section 2.2). Per-Study/per-protocol facilities adequacy lives on StudySite.staffingAdequate and is protocol-dependent." ;
        sh:name "facilitiesAdequate" ;
    ] ;
    sh:property [
        sh:path top:therapeuticAreasExperienced ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of therapeutic areas the Site has experience in (oncology, cardiology, neurology, immunology, infectious disease, rare disease, etc.). Drives feasibility-questionnaire matching." ;
        sh:name "therapeuticAreasExperienced" ;
    ] ;
    sh:property [
        sh:path top:indicationExperience ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Array of structured records: {indication, totalTrials, totalPatientsEnrolled}. Captures past indication-specific experience for SFQ matching." ;
        sh:name "indicationExperience" ;
    ] ;
    sh:property [
        sh:path top:patientPopulationAccess ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Array of {populationDescription, estimatedReach, sourceType}. SFQ feasibility input." ;
        sh:name "patientPopulationAccess" ;
    ] ;
    sh:property [
        sh:path top:patientReferralSources ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of recruitment sources the Site uses (HOSPITAL_OWN, EMR_DATABASE, ADVERTISING, REFERRAL_NETWORK, REGISTRY, PATIENT_ADVOCACY, OTHER). Stored as comma-separated string for the strawman; native NGSI-LD multi-value once the broker layer is in place." ;
        sh:name "patientReferralSources" ;
    ] ;
    sh:property [
        sh:path top:totalTrialsConducted ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Lifetime count of trials conducted at this Site." ;
        sh:name "totalTrialsConducted" ;
    ] ;
    sh:property [
        sh:path top:trialsByPhase ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Array of {phase, count} for past trial experience by phase." ;
        sh:name "trialsByPhase" ;
    ] ;
    sh:property [
        sh:path top:averageEnrollmentRate ;
        sh:datatype xsd:decimal ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Average patients enrolled per month across past trials. SFQ benchmark." ;
        sh:name "averageEnrollmentRate" ;
    ] ;
    sh:property [
        sh:path top:firstPatientInToActivationDays ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Average days from contract execution to first patient enrolled." ;
        sh:name "firstPatientInToActivationDays" ;
    ] ;
    sh:property [
        sh:path top:queryResponseTimeHours ;
        sh:datatype xsd:decimal ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Average hours to respond to data queries; data-quality SFQ signal." ;
        sh:name "queryResponseTimeHours" ;
    ] ;
    sh:property [
        sh:path top:sponsorRetentionRate ;
        sh:datatype xsd:decimal ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Percentage of past sponsors that have run multiple trials at this Site (0.0-1.0)." ;
        sh:name "sponsorRetentionRate" ;
    ] ;
    sh:property [
        sh:path top:freezerCapacity ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Array of {tempRange (e.g. '-80C', '-20C', '4C'), volume, units}. Storage feasibility for biological samples." ;
        sh:name "freezerCapacity" ;
    ] ;
    sh:property [
        sh:path top:imagingCapabilities ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of on-site imaging modalities (XRAY, CT, MRI, ULTRASOUND, PET, SPECT, ECHO, NUCLEAR_MEDICINE, MAMMOGRAPHY). Stored as comma-separated string for the strawman; native NGSI-LD multi-value once the broker layer is in place." ;
        sh:name "imagingCapabilities" ;
    ] ;
    sh:property [
        sh:path top:labCapabilities ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of lab certifications/types available (CLIA, CAP, CENTRAL, POINT_OF_CARE, SPECIALTY). Stored as comma-separated string for the strawman; native NGSI-LD multi-value once the broker layer is in place." ;
        sh:name "labCapabilities" ;
    ] ;
    sh:property [
        sh:path top:pharmacyOnSite ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site has a pharmacy on-site capable of IP handling." ;
        sh:name "pharmacyOnSite" ;
    ] ;
    sh:property [
        sh:path top:dctCapable ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site supports decentralized trial activities (remote visits, home health, telehealth)." ;
        sh:name "dctCapable" ;
    ] ;
    sh:property [
        sh:path top:econsentCapable ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site supports electronic informed consent." ;
        sh:name "econsentCapable" ;
    ] ;
    sh:property [
        sh:path top:eproCapable ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site supports electronic patient-reported outcomes." ;
        sh:name "eproCapable" ;
    ] ;
    sh:property [
        sh:path top:edcVendorExperience ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of EDC vendors the Site has experience with (Medidata Rave, Veeva CDMS, Castor, REDCap, etc.). SFQ technology-readiness signal." ;
        sh:name "edcVendorExperience" ;
    ] ;
    sh:property [
        sh:path top:irbType ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "CENTRAL" "LOCAL" "HYBRID" ) ;
        sh:description "Default IRB type the Site uses; per-Study IRB-of-record assignment lives on StudySite.hasIRB." ;
        sh:name "irbType" ;
    ] ;
    sh:property [
        sh:path top:typicalIrbApprovalDays ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Average days from IRB submission to approval. SFQ timeline signal." ;
        sh:name "typicalIrbApprovalDays" ;
    ] ;
    sh:property [
        sh:path top:regulatoryAuthorityExperience ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of regulatory authorities the Site has filed under (FDA, EMA, PMDA, NMPA, MHRA, HSA, ANVISA, etc.)." ;
        sh:name "regulatoryAuthorityExperience" ;
    ] ;
    sh:property [
        sh:path top:sopsAvailable ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Site maintains its own SOP library." ;
        sh:name "sopsAvailable" ;
    ] ;
    sh:property [
        sh:path top:sopLibrarySize ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Count of distinct SOPs in the Site's library." ;
        sh:name "sopLibrarySize" ;
    ] ;
    sh:property [
        sh:path top:insuranceCarrier ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Name of the insurance carrier providing professional liability / clinical-trial insurance for the Site." ;
        sh:name "insuranceCarrier" ;
    ] ;
    sh:property [
        sh:path top:insuranceCoverageAmount ;
        sh:datatype xsd:decimal ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "insuranceCoverageAmount" ;
    ] ;
    sh:property [
        sh:path top:insuranceCurrency ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 4217 currency code." ;
        sh:name "insuranceCurrency" ;
    ] ;
    sh:property [
        sh:path top:insuranceExpiry ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "insuranceExpiry" ;
    ] ;
    sh:property [
        sh:path top:subInvestigatorCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Aggregate count of available sub-investigators. The actual Person roster lives on hasStaff." ;
        sh:name "subInvestigatorCount" ;
    ] ;
    sh:property [
        sh:path top:coordinatorCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Aggregate count of available study coordinators." ;
        sh:name "coordinatorCount" ;
    ] ;
    sh:property [
        sh:path top:regulatoryCoordinatorCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "regulatoryCoordinatorCount" ;
    ] ;
    sh:property [
        sh:path top:studyNurseCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "studyNurseCount" ;
    ] ;
    sh:property [
        sh:path top:labTechCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "labTechCount" ;
    ] ;
    sh:property [
        sh:path top:languagesSupported ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated ISO 639-1 codes; languages staff can support for participant interactions." ;
        sh:name "languagesSupported" ;
    ] ;
    sh:property [
        sh:path top:gcpTrainingCurrencyRate ;
        sh:datatype xsd:decimal ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Percentage of staff with current GCP training (0.0-1.0). SFQ signal; per-Person training currency lives on Person." ;
        sh:name "gcpTrainingCurrencyRate" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated array of user-defined tags (per OOUX Tag pattern)." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:belongsToOrganization ;
        sh:class topc:Organization ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The legal Organization that owns or operates this Site (e.g., MSKCC's Organization for the MSKCC main-campus Site). USDM Organization.managedSites is the inverse." ;
        sh:name "belongsToOrganization" ;
    ] ;
    sh:property [
        sh:path top:partOfSiteNetwork ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "An Organization with organizationType=SITE_NETWORK or SMO that this Site is part of. Elevate Research is an Organization; each of its locations is a Site with partOfSiteNetwork pointing to Elevate Research." ;
        sh:name "partOfSiteNetwork" ;
    ] ;
    sh:property [
        sh:path top:parentSite ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Self-reference for satellite-of-main-site structure. A satellite clinic Site references the main hospital Site as its parent. Use when the relationship is operationally a parent-child pair, not just network membership." ;
        sh:name "parentSite" ;
    ] ;
    sh:property [
        sh:path top:hasStaff ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Persons employed/affiliated at this Site, study-independent. The roster of people who CAN be delegated to per-Study; actual delegation per-Study lives on StudySite.delegatesAuthorityTo." ;
        sh:name "hasStaff" ;
    ] ;
    sh:property [
        sh:path top:headOfClinicalResearch ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Administrative/institutional contact for the Site's clinical-research operations. NOT the per-Study Principal Investigator (which lives on StudySite.hasPrincipalInvestigator)." ;
        sh:name "headOfClinicalResearch" ;
    ] ;
    sh:property [
        sh:path top:hostsEquipment ;
        sh:class topc:Equipment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-bound equipment. Equipment.equipmentBinding=SITE_BOUND points back here." ;
        sh:name "hostsEquipment" ;
    ] ;
    sh:property [
        sh:path top:hasStorageLocation ;
        sh:class topc:StorageLocation ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-bound storage locations (freezer slots, refrigerator shelves, secure cabinet drawers, ambient pharmacy shelves, offsite vaults). The Equipment/StorageLocation split: a freezer is Equipment that provides multiple StorageLocations." ;
        sh:name "hasStorageLocation" ;
    ] ;
    sh:property [
        sh:path top:hasInstitutionalDocument ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-level essential records that are study-independent: institutional licenses, CLIA certifications, lab accreditations, GCP training records (Site-level), institutional SOPs. Per-Study essential records live on StudySite.hasEssentialRecord." ;
        sh:name "hasInstitutionalDocument" ;
    ] ;
    sh:property [
        sh:path top:hasInstitutionalCredential ;
        sh:class topc:Credential ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-level credentials (CLIA, JCAHO, ISO 9001, lab accreditations, etc.)." ;
        sh:name "hasInstitutionalCredential" ;
    ] ;
    sh:property [
        sh:path top:usesSystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-operational Systems (the Site's EMR, the Site's source-document repository, the Site's local pharmacy management system) that exist regardless of any specific Study. Per-Study System usage (Sponsor-provisioned EDC, per-Study IRT, etc.) lives on StudySite.usesSystem." ;
        sh:name "usesSystem" ;
    ] .

top:Participant rdfs:subClassOf prov:Entity .

top:ParticipantShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:name "Participant" ;
    sh:description "A Participant is a real human enrolled in a Study at a StudySite. Per FIRST-PRINCIPLES (operator-grounded foundation, ephemeral projections), Participant is the trial-conduct-realm role-entity for the underlying human; the same human is a Patient in their care realm, a Claimant in their claims realm, an RWE-Patient in research-warehouse projections — those are projection targets, not TOP entities. The operational hierarchy: Site -> StudySite -> Study -> Protocol -> SOA -> Visit -> Activity, with Participant anchored at StudySite (1..1; multi-Study participation is modeled as separate Participant entities). Lifecycle events (Consent, Screening, Enrollment, Withdrawal) are sub-objects under Participant — the SDTM DS-record pattern; operators see workflow events, the projection adapters emit DS rows. participantStatus is a NGSI-LD temporal property (validFrom/validUntil) so trajectory is queryable for downstream digital-twin synthesis (key enabler for twin-for-enrollment in v0.5+)." ;
    sh:property [
        sh:path top:participantId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier. Operators type the parts (screeningNumber + randomizationNumber); USUBJID = STUDYID-SITEID-SUBJID concatenation is a SDTM-projection-time derivation, not a TOP attribute." ;
        sh:name "participantId" ;
    ] ;
    sh:property [
        sh:path top:screeningNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Site-assigned screening identifier (operator-facing). Maps to SDTM SUBJID. CDASH SUBJID. Operator types this when registering a new Participant after consent." ;
        sh:name "screeningNumber" ;
    ] ;
    sh:property [
        sh:path top:randomizationNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Sponsor-assigned identifier issued at randomization. Often used for IP kit assignment. Empty until randomization. SDTM has no canonical column; carried in SUPPDM or kit-level records." ;
        sh:name "randomizationNumber" ;
    ] ;
    sh:property [
        sh:path top:randomizationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date of randomization. Empty until randomization. Required when participantStatus=RANDOMIZED (SHACL hard violation)." ;
        sh:name "randomizationDate" ;
    ] ;
    sh:property [
        sh:path top:firstName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operator-grounded; populated with real value for site-coordinator UX or empty/de-identified for research-warehouse projections per deployment-side identification policy." ;
        sh:name "firstName" ;
    ] ;
    sh:property [
        sh:path top:middleName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "middleName" ;
    ] ;
    sh:property [
        sh:path top:lastName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operator-grounded; same de-identification posture as firstName." ;
        sh:name "lastName" ;
    ] ;
    sh:property [
        sh:path top:dateOfBirth ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Real DOB, or year-only approximation, or shifted date depending on deployment. Maps to SDTM BRTHDTC (ISO 8601). FHIR Patient.birthDate." ;
        sh:name "dateOfBirth" ;
    ] ;
    sh:property [
        sh:path top:sex ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "MALE" "FEMALE" "INTERSEX" "UNKNOWN" "NOT_REPORTED" ) ;
        sh:description "Biological sex per ICH E5 / FDA guidance. Operator-meaningful enum; SDTM projection emits the terser M/F/U/UNDIFFERENTIATED codes (INTERSEX -> UNDIFFERENTIATED per CDISC controlled term). Gender identity is a separate concern when needed." ;
        sh:name "sex" ;
    ] ;
    sh:property [
        sh:path top:race ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated for multiracial. Maps to SDTM RACE (CDISC controlled term); SDTM uses MULTIPLE + SUPPDM RACEMUL for multiracial." ;
        sh:name "race" ;
    ] ;
    sh:property [
        sh:path top:ethnicity ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "HISPANIC_OR_LATINO" "NOT_HISPANIC_OR_LATINO" "NOT_REPORTED" "UNKNOWN" ) ;
        sh:description "CDISC controlled term." ;
        sh:name "ethnicity" ;
    ] ;
    sh:property [
        sh:path top:primaryLanguage ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 639-1 code (en, es, fr, zh, etc.). Operator-grounded for site-coordinator UX (preferred language for visit interactions). Not in SDTM DM core." ;
        sh:name "primaryLanguage" ;
    ] ;
    sh:property [
        sh:path top:country ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3 country code. For multi-region trials." ;
        sh:name "country" ;
    ] ;
    sh:property [
        sh:path top:participantStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "SCREENING" "SCREEN_FAILED" "CONSENTED" "ENROLLED" "RANDOMIZED" "ON_TREATMENT" "IN_FOLLOW_UP" "COMPLETED" "WITHDRAWN" "DISCONTINUED" "LOST_TO_FOLLOW_UP" ) ;
        sh:description "Operationally-distinct lifecycle states (11). Operator-meaningful; standards projections live below the line. NGSI-LD temporal property (validFrom/validUntil bracketing each value) so trajectory is queryable downstream (Decision 10 twin-queryability discipline)." ;
        sh:name "participantStatus" ;
    ] ;
    sh:property [
        sh:path top:enrollmentDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date of enrollment (single canonical Sponsor-assigned moment). Distinct from randomizationDate (later) and consent date (on InformedConsent sub-object). Maps to SDTM RFSTDTC." ;
        sh:name "enrollmentDate" ;
    ] ;
    sh:property [
        sh:path top:completionDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the Participant completed protocol participation (LPLV-equivalent for this Participant). SOFT SHACL: should be populated when participantStatus=COMPLETED." ;
        sh:name "completionDate" ;
    ] ;
    sh:property [
        sh:path top:withdrawalDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Most recent withdrawal event date (canonical query convenience; full history lives on WithdrawalRecord sub-objects)." ;
        sh:name "withdrawalDate" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated user-defined tags (per OOUX Tag pattern)." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD-native temporal property: datetime from which this Participant record is operationally valid. Pattern parallels Sponsor and StudySite." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD-native temporal property: datetime through which this Participant record was operationally valid. Empty for currently-active Participants." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:forStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySite where this Participant is enrolled. Per the operational hierarchy correction (Site -> StudySite -> Study), Participant anchors at StudySite, not Site directly. The OOUX Participant->1 Site direct edge is a correction surfaced during the Site spec lift; tracked." ;
        sh:name "forStudySite" ;
    ] ;
    sh:property [
        sh:path top:forStudy ;
        sh:class top:Study ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this Participant is enrolled in. Reachable via forStudySite.forStudy traversal but kept as a direct edge for query convenience and SHACL invariant simplicity (Participant.assignedToArm.forStudy == Participant.forStudy)." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path top:assignedToArm ;
        sh:class top:Arm ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study Arm the Participant is assigned to. 0 in pre-randomization phases; 1..1 once randomized. Hard SHACL: when participantStatus=RANDOMIZED, assignedToArm must be populated AND the Arm must belong to the same Study as Participant.forStudy (cross-entity consistency)." ;
        sh:name "assignedToArm" ;
    ] ;
    sh:property [
        sh:path top:hasInformedConsent ;
        sh:class top:InformedConsent ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Informed consent event records. 0..N to support re-consent (after protocol amendment, ICF version change, lapse-and-re-establish). Each InformedConsent sub-object is one consent event with its own date, consenter, witness, document reference, lifecycle status." ;
        sh:name "hasInformedConsent" ;
    ] ;
    sh:property [
        sh:path top:hasScreeningRecord ;
        sh:class top:ScreeningRecord ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Screening event records. 0..N to support re-screening attempts. Hard SHACL: when participantStatus=SCREEN_FAILED, at least one ScreeningRecord with screeningOutcome=SCREEN_FAILED must exist." ;
        sh:name "hasScreeningRecord" ;
    ] ;
    sh:property [
        sh:path top:hasEnrollmentRecord ;
        sh:class top:EnrollmentRecord ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Enrollment event records. Typically 1 per Participant; multi-enrollment supported for re-enrollment scenarios." ;
        sh:name "hasEnrollmentRecord" ;
    ] ;
    sh:property [
        sh:path top:hasWithdrawalRecord ;
        sh:class top:WithdrawalRecord ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Withdrawal event records. Hard SHACL: when participantStatus=WITHDRAWN, at least one WithdrawalRecord must exist with withdrawalCategory=CONSENT_WITHDRAWN." ;
        sh:name "hasWithdrawalRecord" ;
    ] ;
    sh:property [
        sh:path top:convertedFromRecruit ;
        sh:class top:Recruit ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Recruit entity that converted to this Participant when consent was obtained. Empty when Participant was registered without going through a tracked recruitment funnel (direct site walk-in). The InformedConsent sub-object's existence IS the conversion event; this relationship is the operational-funnel-side trace." ;
        sh:name "convertedFromRecruit" ;
    ] ;
    sh:property [
        sh:path top:hasVisit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Visit-occurrences for this Participant. Inverse of Visit.atParticipant (when Visit lifts)." ;
        rdfs:comment "TARGET-MISSING: Visit is a top-level in OOUX (one of the locked 8) but not yet in TOP source; lifts post-Participant per planning sequencing." ;
        sh:name "hasVisit" ;
    ] ;
    sh:property [
        sh:path top:hasAdverseEvent ;
        sh:class top:Event ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Adverse-event records linked to this Participant. The relationship is named for AE/SAE category Events (the operator vocabulary); the broader Event entity also covers Deviations, Discrepancies, Safety Signals, etc., reachable via Event.forParticipant inverse." ;
        sh:name "hasAdverseEvent" ;
    ] ;
    sh:property [
        sh:path top:hasTag ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "User-defined tags applied to this Participant." ;
        rdfs:comment "TARGET-MISSING: Tag is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasTag" ;
    ] .

top:convertedFromRecruit rdfs:subPropertyOf prov:wasDerivedFrom .

top:InformedConsent rdfs:subClassOf prov:Activity .

top:InformedConsentShape a sh:NodeShape ;
    sh:targetClass top:InformedConsent ;
    sh:name "InformedConsent" ;
    sh:description "An InformedConsent sub-object captures one consent event for a Participant: the moment the participant (or LAR) signed an ICF acknowledging the study's purpose, procedures, risks, and benefits. ICH E6(R3) Section 2.4 GCP-required. Re-consent (after amendment, ICF version change, or lapse) creates additional InformedConsent records under the same Participant — full event history. Maps to FHIR Consent resource (one per record) and SDTM DS records with DSDECOD=INFORMED CONSENT OBTAINED / WITHDRAWAL OF CONSENT." ;
    sh:property [
        sh:path top:consentId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "consentId" ;
    ] ;
    sh:property [
        sh:path top:consentVersion ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "ICF document version (e.g., 'v3.2'). Tracks which Protocol/ICF revision the consent was given against." ;
        sh:name "consentVersion" ;
    ] ;
    sh:property [
        sh:path top:consentDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Date consent was obtained. Maps to SDTM DSSTDTC for the corresponding DS record." ;
        sh:name "consentDate" ;
    ] ;
    sh:property [
        sh:path top:consentTime ;
        sh:datatype xsd:time ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Time of day consent was obtained, if recorded (eConsent typically captures this; paper consent often does not)." ;
        sh:name "consentTime" ;
    ] ;
    sh:property [
        sh:path top:consentMethod ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PAPER" "ELECTRONIC" "ELECTRONIC_REMOTE" ) ;
        sh:description "Method of consent capture. ELECTRONIC_REMOTE supports DCT/decentralized-trial workflows (telehealth consent)." ;
        sh:name "consentMethod" ;
    ] ;
    sh:property [
        sh:path top:consenterRelationship ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "SELF" "LEGALLY_AUTHORIZED_REPRESENTATIVE" "PARENT_GUARDIAN" "OTHER" ) ;
        sh:description "Who is consenting on the Participant's behalf. SELF for adult competent participants; LAR for incapacitated adults; PARENT_GUARDIAN for pediatric." ;
        sh:name "consenterRelationship" ;
    ] ;
    sh:property [
        sh:path top:language ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 639-1 code of the language the ICF was presented in. Maps to FHIR Consent.language." ;
        sh:name "language" ;
    ] ;
    sh:property [
        sh:path top:translatorUsed ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Whether a translator/interpreter was used. FHIR represents this as Consent.actor with role=interpreter." ;
        sh:name "translatorUsed" ;
    ] ;
    sh:property [
        sh:path top:consentStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "OBTAINED" "WITHDRAWN" "EXPIRED" "RECONSENTED" ) ;
        sh:description "Lifecycle of this specific consent record. OBTAINED on creation; WITHDRAWN if subject withdraws; EXPIRED if ICF expires; RECONSENTED if a new InformedConsent supersedes this one (the new record points back via reconsentReason)." ;
        sh:name "consentStatus" ;
    ] ;
    sh:property [
        sh:path top:reconsentRequired ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if this consent record was created because a reconsent was required (e.g., after a protocol amendment changed risk profile)." ;
        sh:name "reconsentRequired" ;
    ] ;
    sh:property [
        sh:path top:reconsentReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Reason for reconsent (e.g., 'Protocol Amendment v3.2 added a new procedure'). Free text or coded." ;
        sh:name "reconsentReason" ;
    ] ;
    sh:property [
        sh:path top:withdrawalDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date this specific consent was withdrawn (if applicable). Distinct from Participant.withdrawalDate (which is the most-recent across all WithdrawalRecords)." ;
        sh:name "withdrawalDate" ;
    ] ;
    sh:property [
        sh:path top:consentingPerson ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person who consented (often the Participant themselves; or the LAR / parent / guardian). 0..1 because the Participant entity itself is the most common case and TOP doesn't always model the human-as-Person entity." ;
        sh:name "consentingPerson" ;
    ] ;
    sh:property [
        sh:path top:personObtainingConsent ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The site staff member (Person, drawn from forStudySite.delegatesAuthorityTo) who obtained the consent. ICH E6(R3) Section 2.4 GCP-required attribution." ;
        sh:name "personObtainingConsent" ;
    ] ;
    sh:property [
        sh:path top:consentWitness ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Witness to the consent process, where required by jurisdictional regulation or sponsor SOP." ;
        sh:name "consentWitness" ;
    ] ;
    sh:property [
        sh:path top:consentDocument ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The signed ICF document in the eTMF/ISF." ;
        rdfs:comment "TARGET-MISSING: Document is a horizontal in TOP source; relationship valid." ;
        sh:name "consentDocument" ;
    ] .

top:consentingPerson rdfs:subPropertyOf prov:wasAttributedTo .
top:personObtainingConsent rdfs:subPropertyOf prov:wasAssociatedWith .
top:consentWitness rdfs:subPropertyOf prov:wasAssociatedWith .

top:ScreeningRecord rdfs:subClassOf prov:Activity .

top:ScreeningRecordShape a sh:NodeShape ;
    sh:targetClass top:ScreeningRecord ;
    sh:name "ScreeningRecord" ;
    sh:description "A ScreeningRecord captures one screening attempt for a Participant against the Study's inclusion/exclusion criteria. ICH E6(R3) Section 2.5 GCP-anchored. Re-screening (after a screen failure, per protocol re-screening rules) creates additional ScreeningRecords. Maps to FHIR ResearchSubject.progress entries with subjectState=screening / off-study and SDTM DS records with DSDECOD=SCREEN FAILURE (when failed) plus SDTM IE records (Inclusion/Exclusion Not Met) for failed criteria." ;
    sh:property [
        sh:path top:screeningRecordId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "screeningRecordId" ;
    ] ;
    sh:property [
        sh:path top:screeningStartDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Date screening began (often consent date or shortly after)." ;
        sh:name "screeningStartDate" ;
    ] ;
    sh:property [
        sh:path top:screeningEndDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date screening concluded (with outcome). Empty for in-progress screening." ;
        sh:name "screeningEndDate" ;
    ] ;
    sh:property [
        sh:path top:screeningOutcome ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "IN_PROGRESS" "ENROLLED" "SCREEN_FAILED" "RE_SCREENING_PENDING" "WITHDREW_BEFORE_DECISION" ) ;
        sh:description "Final outcome of this screening attempt. IN_PROGRESS while open; one of the terminal states once concluded." ;
        sh:name "screeningOutcome" ;
    ] ;
    sh:property [
        sh:path top:screenFailReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Verbatim reason for screen failure (free text or coded). Maps to SDTM DSTERM (verbatim) and DSDECOD." ;
        sh:name "screenFailReason" ;
    ] ;
    sh:property [
        sh:path top:failedInclusionCriteria ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated list of inclusion criterion numbers (e.g., 'I1, I3') the Participant failed. SDTM IE domain is the canonical projection (one IE record per failed criterion)." ;
        sh:name "failedInclusionCriteria" ;
    ] ;
    sh:property [
        sh:path top:failedExclusionCriteria ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated list of exclusion criterion numbers (e.g., 'E2, E5') the Participant met. SDTM IE domain is the canonical projection." ;
        sh:name "failedExclusionCriteria" ;
    ] ;
    sh:property [
        sh:path top:reScreeningEligibility ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Whether the Participant is eligible to be re-screened (per protocol re-screening rules; some screen failures are permanent disqualifications)." ;
        sh:name "reScreeningEligibility" ;
    ] ;
    sh:property [
        sh:path top:screenedBy ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The site staff member (PI or sub-investigator, drawn from forStudySite.delegatesAuthorityTo) who conducted/signed-off-on the screening." ;
        sh:name "screenedBy" ;
    ] .

top:screenedBy rdfs:subPropertyOf prov:wasAssociatedWith .

top:EnrollmentRecord rdfs:subClassOf prov:Activity .

top:EnrollmentRecordShape a sh:NodeShape ;
    sh:targetClass top:EnrollmentRecord ;
    sh:name "EnrollmentRecord" ;
    sh:description "An EnrollmentRecord captures the moment a Participant was enrolled into the Study (eligibility confirmed; assigned to the trial). Distinct from RANDOMIZED (which may follow). Typically 1 per Participant; multi-enrollment supported for re-enrollment scenarios where a previously withdrawn Participant rejoins (rare; some long-running registries). Maps to FHIR ResearchSubject.progress (milestone=Enrolled) and SDTM DS DSDECOD=ENROLLED." ;
    sh:property [
        sh:path top:enrollmentRecordId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "enrollmentRecordId" ;
    ] ;
    sh:property [
        sh:path top:enrollmentDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Date of enrollment. Maps to SDTM RFSTDTC and DS DSSTDTC for DSDECOD=ENROLLED." ;
        sh:name "enrollmentDate" ;
    ] ;
    sh:property [
        sh:path top:enrollmentMethod ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "ON_SITE" "REMOTE" "HYBRID" ) ;
        sh:description "Method of enrollment. ON_SITE is traditional; REMOTE supports DCT (decentralized trial) workflows; HYBRID for partial-DCT designs." ;
        sh:name "enrollmentMethod" ;
    ] ;
    sh:property [
        sh:path top:enrollmentNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Sponsor-assigned enrollment number (often sequential, distinct from screeningNumber and randomizationNumber)." ;
        sh:name "enrollmentNumber" ;
    ] ;
    sh:property [
        sh:path top:protocolVersionAtEnrollment ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Protocol version in force at time of enrollment. References the USDM StudyVersion identifier in force; useful for audit when amendments cascade." ;
        sh:name "protocolVersionAtEnrollment" ;
    ] ;
    sh:property [
        sh:path top:eligibilityConfirmedBy ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Principal Investigator (Person, drawn from forStudySite.hasPrincipalInvestigator typically) who confirmed eligibility per ICH E6(R3) Section 2.4 GCP-required attribution." ;
        sh:name "eligibilityConfirmedBy" ;
    ] .

top:eligibilityConfirmedBy rdfs:subPropertyOf prov:wasAssociatedWith .

top:WithdrawalRecord rdfs:subClassOf prov:Activity .

top:WithdrawalRecordShape a sh:NodeShape ;
    sh:targetClass top:WithdrawalRecord ;
    sh:name "WithdrawalRecord" ;
    sh:description "A WithdrawalRecord captures the moment a Participant left the Study (subject-initiated withdrawal of consent, investigator-initiated discontinuation, lost-to-follow-up, etc.). Each event is a separate record so the full lifecycle is auditable. Maps to FHIR ResearchSubject.progress (milestone=Withdrawn) and SDTM DS records with DSDECOD per CDISC controlled term." ;
    sh:property [
        sh:path top:withdrawalRecordId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "withdrawalRecordId" ;
    ] ;
    sh:property [
        sh:path top:withdrawalDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Date of withdrawal/discontinuation. Maps to SDTM DSSTDTC." ;
        sh:name "withdrawalDate" ;
    ] ;
    sh:property [
        sh:path top:withdrawalCategory ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "CONSENT_WITHDRAWN" "INVESTIGATOR_DECISION" "ADVERSE_EVENT" "LOST_TO_FOLLOW_UP" "PROTOCOL_DEVIATION" "NON_COMPLIANCE" "DEATH" "OTHER" ) ;
        sh:description "Category of the withdrawal event. Maps to SDTM DSDECOD: CONSENT_WITHDRAWN -> WITHDRAWAL OF CONSENT; INVESTIGATOR_DECISION -> DISCONTINUED; etc." ;
        sh:name "withdrawalCategory" ;
    ] ;
    sh:property [
        sh:path top:withdrawalReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Verbatim reason for withdrawal (free text or coded). Maps to SDTM DSTERM." ;
        sh:name "withdrawalReason" ;
    ] ;
    sh:property [
        sh:path top:continueDataCollection ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if the Participant withdrew from active treatment but data collection (follow-up visits) continues per protocol. FHIR represents as continued ResearchSubject without period.end despite withdrawn status." ;
        sh:name "continueDataCollection" ;
    ] ;
    sh:property [
        sh:path top:withdrawnFromAllProcedures ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if the Participant withdrew consent for all study procedures including data use (full withdrawal). When true, deployment-side data-handling policies apply (data deletion, retention exception, etc.)." ;
        sh:name "withdrawnFromAllProcedures" ;
    ] ;
    sh:property [
        sh:path top:notes ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Free-text notes (often used by site coordinators for context that doesn't fit the controlled fields)." ;
        sh:name "notes" ;
    ] ;
    sh:property [
        sh:path top:documentedBy ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The site staff member (Person) who documented the withdrawal event. Required by GCP audit; relationship optional in TOP source because some legacy data may lack the attribution." ;
        sh:name "documentedBy" ;
    ] .

top:documentedBy rdfs:subPropertyOf prov:wasAssociatedWith .

top:Recruit rdfs:subClassOf prov:Entity .

top:RecruitShape a sh:NodeShape ;
    sh:targetClass top:Recruit ;
    sh:name "Recruit" ;
    sh:description "A Recruit is a person who has responded to or been identified by a recruitment funnel for a Study but has NOT yet given informed consent. The recruitment realm has its own operator (recruitment coordinator / outreach lead) and its own workflow (advertising response, pre-screening calls, eligibility pre-check, scheduling, contact attempts). Per FIRST-PRINCIPLES, Recruit is a separate top-level entity rather than a pre-consent state on Participant: the recruiter's workflow is operationally distinct from the coordinator's, the data shape is lighter (no consent, often just contact info + light eligibility pre-check), and PHI handling differs (recruiter does not need full demographics or medical history). When a Recruit consents, they convert to a Participant via Participant.convertedFromRecruit. Recruit data persists as recruitment-funnel analytics (conversion rates per source) regardless of whether conversion occurred." ;
    sh:property [
        sh:path top:recruitId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "recruitId" ;
    ] ;
    sh:property [
        sh:path top:firstName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operator-grounded; populated when recruitment coordinator has captured contact info. Identification-status-agnostic posture (same as Participant)." ;
        sh:name "firstName" ;
    ] ;
    sh:property [
        sh:path top:lastName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastName" ;
    ] ;
    sh:property [
        sh:path top:phone ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Phone number for recruiter contact. Often the only identifier captured at first response (radio ad call-in, web-form submission)." ;
        sh:name "phone" ;
    ] ;
    sh:property [
        sh:path top:email ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "email" ;
    ] ;
    sh:property [
        sh:path top:preferredContactMethod ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "PHONE" "EMAIL" "TEXT" "PORTAL" "OTHER" ) ;
        sh:description "Recruit's stated preferred contact method." ;
        sh:name "preferredContactMethod" ;
    ] ;
    sh:property [
        sh:path top:recruitStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "RESPONDED" "CONTACTED" "PRE_SCREENING_SCHEDULED" "PRE_SCREENED" "QUALIFIED_PENDING_CONSENT" "CONVERTED_TO_PARTICIPANT" "DECLINED" "NO_SHOW" "NOT_QUALIFIED" "DROPPED" ) ;
        sh:description "Recruitment-funnel state. CONVERTED_TO_PARTICIPANT terminates the Recruit lifecycle (Participant record now exists). DECLINED / NO_SHOW / NOT_QUALIFIED / DROPPED are terminal failure states. NGSI-LD temporal property (validFrom/validUntil) for trajectory queryability (parallel to Participant.participantStatus)." ;
        sh:name "recruitStatus" ;
    ] ;
    sh:property [
        sh:path top:recruitmentSource ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "SITE_DATABASE" "EMR_OUTREACH" "ADVERTISING_DIGITAL" "ADVERTISING_PRINT" "ADVERTISING_RADIO" "REFERRAL_PROVIDER" "REFERRAL_PARTICIPANT" "REGISTRY" "PATIENT_ADVOCACY" "WALK_IN" "OTHER" ) ;
        sh:description "How the Recruit entered the funnel. Drives recruitment-channel ROI analysis." ;
        sh:name "recruitmentSource" ;
    ] ;
    sh:property [
        sh:path top:recruitmentSourceDetails ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Free-text detail (e.g., specific ad campaign ID, specific registry name, referring physician)." ;
        sh:name "recruitmentSourceDetails" ;
    ] ;
    sh:property [
        sh:path top:responseDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Date the Recruit first entered the funnel (responded to ad / referred / identified by EMR mining)." ;
        sh:name "responseDate" ;
    ] ;
    sh:property [
        sh:path top:preScreeningOutcome ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "QUALIFIED" "NOT_QUALIFIED" "DEFERRED" "PENDING" ) ;
        sh:description "Outcome of the recruiter's pre-screening assessment (NOT formal protocol screening; that happens post-consent on the Participant via ScreeningRecord). PENDING while in progress; QUALIFIED proceeds to consent scheduling." ;
        sh:name "preScreeningOutcome" ;
    ] ;
    sh:property [
        sh:path top:preScreeningNotes ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Free-text notes from pre-screening conversation (high-level eligibility flags, concerns, motivations). Limited PHI by design — full medical history not captured here." ;
        sh:name "preScreeningNotes" ;
    ] ;
    sh:property [
        sh:path top:contactAttempts ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Count of contact attempts made by the recruiter (calls, emails, texts). Operational metric; drives recruiter follow-up cadence." ;
        sh:name "contactAttempts" ;
    ] ;
    sh:property [
        sh:path top:lastContactDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Most recent contact attempt date (successful or not)." ;
        sh:name "lastContactDate" ;
    ] ;
    sh:property [
        sh:path top:consentScheduled ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if a consent appointment is scheduled. Bridges to Participant creation." ;
        sh:name "consentScheduled" ;
    ] ;
    sh:property [
        sh:path top:consentAppointmentDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "consentAppointmentDate" ;
    ] ;
    sh:property [
        sh:path top:country ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3. Useful for multi-country recruitment funnel analytics." ;
        sh:name "country" ;
    ] ;
    sh:property [
        sh:path top:primaryLanguage ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 639-1. Drives recruiter language-matching." ;
        sh:name "primaryLanguage" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated user-defined tags." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property for recruit-record validity." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:forStudy ;
        sh:class top:Study ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this Recruit was identified for. Recruits are scoped per-Study (a person responding to two trial ads is two Recruits, even if same human; cross-Study identity resolution is federation, not foundation)." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path top:forStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySite the Recruit was funneled to (often known by recruitment-source channel). 0..1 because some Recruits enter the funnel before site assignment is finalized (multi-site studies with central-recruitment-then-site-routing)." ;
        sh:name "forStudySite" ;
    ] ;
    sh:property [
        sh:path top:recruitedBy ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The recruitment coordinator (Person) handling this Recruit. From forStudySite.delegatesAuthorityTo with role=recruitment-coordinator (when delegation is per-Study) or from a sponsor-side recruitment team." ;
        sh:name "recruitedBy" ;
    ] ;
    sh:property [
        sh:path top:convertedToParticipant ;
        sh:class top:Participant ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Participant entity that was created when this Recruit consented. Empty until conversion. Inverse of Participant.convertedFromRecruit." ;
        sh:name "convertedToParticipant" ;
    ] .

top:recruitedBy rdfs:subPropertyOf prov:wasAssociatedWith .

top:Visit rdfs:subClassOf prov:Activity .

top:VisitShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:name "Visit" ;
    sh:description "A Visit is an actual visit-occurrence: a Participant attended at a StudySite on a specific date. Distinct from VisitDefinition (the protocol-template). Visits carry actual datetimes, status, mode (in-person / telehealth / remote / async), Activities performed, Tasks captured, and any Observations the operator noted. Per FIRST-PRINCIPLES universal-foundation posture, the Visit > Activity > Task hierarchy handles ANY assessment without modeling specifics — a DICOM imaging Activity and a blood-draw Activity are the same TOP shape. The OOUX Visit->Site direct edge is corrected: Visit.forStudySite -> StudySite (1..1)." ;
    sh:property [
        sh:path top:visitId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "visitId" ;
    ] ;
    sh:property [
        sh:path top:visitNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Numeric ordering per protocol (matches VisitDefinition.visitNumber when definedBy is populated). Maps to SDTM VISITNUM." ;
        sh:name "visitNumber" ;
    ] ;
    sh:property [
        sh:path top:visitName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly name. For UNSCHEDULED visits, free text describing the reason. Maps to SDTM VISIT." ;
        sh:name "visitName" ;
    ] ;
    sh:property [
        sh:path top:visitMode ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "IN_PERSON_CLINIC" "IN_PERSON_HOME" "REMOTE_VIDEO" "REMOTE_PHONE" "REMOTE_ASYNC" "HYBRID" ) ;
        sh:description "Visit modality per ICH E6(R3) Annex 2 DCT framing. IN_PERSON_CLINIC for traditional clinic visits; IN_PERSON_HOME for home health; REMOTE_VIDEO for telehealth; REMOTE_PHONE for phone visits; REMOTE_ASYNC for ePRO/eDiary capture without live interaction; HYBRID for mixed-modality visits. FHIR Encounter.virtualService + Encounter.location are the projection targets." ;
        sh:name "visitMode" ;
    ] ;
    sh:property [
        sh:path top:visitStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "SCHEDULED" "IN_PROGRESS" "COMPLETED" "PARTIALLY_COMPLETED" "MISSED" "RESCHEDULED" "CANCELLED" "OUT_OF_WINDOW" "UNSCHEDULED" ) ;
        sh:description "Visit lifecycle state. NGSI-LD temporal property (validFrom/validUntil per value) for trajectory queryability. SCHEDULED for booked-but-not-started; IN_PROGRESS during active visit; COMPLETED for per-protocol completion; PARTIALLY_COMPLETED when some Activities not performed (rescheduled separately); MISSED for didn't-happen-no-makeup; OUT_OF_WINDOW when actual datetime falls outside visitDefinition.visitWindow; UNSCHEDULED for ad-hoc (urgent care, AE workup, sponsor-requested). FHIR Encounter.status is the projection target." ;
        sh:name "visitStatus" ;
    ] ;
    sh:property [
        sh:path top:plannedStartDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Planned visit date per SOA / scheduling system. What the operator/coordinator booked." ;
        sh:name "plannedStartDate" ;
    ] ;
    sh:property [
        sh:path top:plannedEndDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Planned end date (typically same day; multi-day visits possible for inpatient procedures or PK time-courses)." ;
        sh:name "plannedEndDate" ;
    ] ;
    sh:property [
        sh:path top:actualStartDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Actual visit start date. Empty for SCHEDULED visits not yet started. Maps to SDTM SVSTDTC." ;
        sh:name "actualStartDate" ;
    ] ;
    sh:property [
        sh:path top:actualStartTime ;
        sh:datatype xsd:time ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Actual start time-of-day where captured. Operational completeness." ;
        sh:name "actualStartTime" ;
    ] ;
    sh:property [
        sh:path top:actualEndDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Actual visit end date. Maps to SDTM SVENDTC." ;
        sh:name "actualEndDate" ;
    ] ;
    sh:property [
        sh:path top:actualEndTime ;
        sh:datatype xsd:time ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Actual end time-of-day where captured." ;
        sh:name "actualEndTime" ;
    ] ;
    sh:property [
        sh:path top:visitDay ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Days from study anchor (typically randomization or first dose). Computed from actualStartDate when definedBy is populated. Maps to SDTM VISITDY." ;
        sh:name "visitDay" ;
    ] ;
    sh:property [
        sh:path top:protocolDeviationCode ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Sponsor-defined deviation code when visitStatus=OUT_OF_WINDOW or other deviation. Free text or coded; aligns with sponsor deviation taxonomy." ;
        sh:name "protocolDeviationCode" ;
    ] ;
    sh:property [
        sh:path top:unscheduledReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Reason for an unscheduled visit (urgent care, AE workup, sponsor-requested follow-up). Required when visitStatus=UNSCHEDULED. Maps to SDTM SVUPDES." ;
        sh:name "unscheduledReason" ;
    ] ;
    sh:property [
        sh:path top:tags ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated user-defined tags." ;
        sh:name "tags" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this Visit record became operationally valid." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this Visit record was superseded or closed." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:forParticipant ;
        sh:class top:Participant ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Participant whose visit this is. PROV: this Visit Activity is wasAttributedTo this Participant. Maps to FHIR Encounter.subject (via ResearchSubject.subject linkage)." ;
        sh:name "forParticipant" ;
    ] ;
    sh:property [
        sh:path top:forStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySite hosting this visit (operational owner regardless of physical location — telehealth visits still have a StudySite of record). OOUX Visit->Site corrected to Visit->StudySite per the operational hierarchy. Maps to FHIR Encounter.serviceProvider + Encounter.location." ;
        sh:name "forStudySite" ;
    ] ;
    sh:property [
        sh:path top:definedBy ;
        sh:class top:VisitDefinition ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The VisitDefinition (protocol template) this Visit instantiates. 0..1 because UNSCHEDULED visits don't have a corresponding template. PROV: wasInformedBy semantics — the template informed the actual visit's expected scope." ;
        sh:name "definedBy" ;
    ] ;
    sh:property [
        sh:path top:hasActivity ;
        sh:class top:Activity ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Activities performed at this Visit (universal sub-objects: vitals, MRI, ePRO, biopsy, IP-admin, etc.). Per FIRST-PRINCIPLES universal-foundation posture, Activity is the same TOP shape regardless of activity type." ;
        sh:name "hasActivity" ;
    ] ;
    sh:property [
        sh:path top:hasTask ;
        sh:class top:Task ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Tasks (leaf data-capture units) performed at this Visit. Each Task has belongsToActivity referencing its parent Activity within the same Visit. Polymorphic taskValue handles all data shapes (numeric, text, coded, URI references, structured)." ;
        sh:name "hasTask" ;
    ] ;
    sh:property [
        sh:path top:hasVisitObservation ;
        sh:class top:VisitObservation ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Operator-narrative observations captured during the visit that don't fit a structured Activity slot (per OOUX Path C reportability handoff). May escalate to Event when relevant." ;
        sh:name "hasVisitObservation" ;
    ] ;
    sh:property [
        sh:path top:partOf ;
        sh:class top:Visit ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Parent Visit this Visit is part of (multi-part visits, follow-up linkage). Maps to FHIR Encounter.partOf and OMOP preceding_visit_occurrence_id (linked-list)." ;
        sh:name "partOf" ;
    ] ;
    sh:property [
        sh:path top:hasAdverseEvent ;
        sh:class top:Event ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Adverse events identified at or attributed to this Visit. Inverse of Event.duringVisit. As with Participant.hasAdverseEvent, the relationship label is operator-vocabulary; Event with category in {DEVIATION, DISCREPANCY, OTHER_CLINICAL_EVENT} arising during a Visit reach back through Event.duringVisit." ;
        sh:name "hasAdverseEvent" ;
    ] ;
    sh:property [
        sh:path top:hasTag ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "User-defined tags." ;
        rdfs:comment "TARGET-MISSING: Tag flagged-missing as before." ;
        sh:name "hasTag" ;
    ] .

top:definedBy rdfs:subPropertyOf prov:wasInformedBy .

top:Activity rdfs:subClassOf prov:Activity .

top:ActivityShape a sh:NodeShape ;
    sh:targetClass top:Activity ;
    sh:name "Activity" ;
    sh:description "Activity is the universal work unit at a Visit — Vitals, Blood Draw, MRI, ePRO session, IP administration, ECG, biopsy, sequencing draw, ADAS-Cog session, anything. Per FIRST-PRINCIPLES universal-foundation posture, the SAME entity shape handles all activity types; specialization is content (biomedicalConceptCode + activityType for filter/sort + linked Equipment + governing Document), never entity shape. activityStatus is a NGSI-LD temporal property. PROV: each Activity is a prov:Activity with wasAssociatedWith Person agents, used Equipment, governed-by Documents." ;
    sh:property [
        sh:path top:activityId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "activityId" ;
    ] ;
    sh:property [
        sh:path top:activityName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly name (e.g., 'Vital Signs', 'MRI Brain', 'ECOG Performance Status', 'Drug X Administration', 'EQ-5D-5L')." ;
        sh:name "activityName" ;
    ] ;
    sh:property [
        sh:path top:activityType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PROCEDURE" "ASSESSMENT" "IP_ADMINISTRATION" "SAMPLE_COLLECTION" "QUESTIONNAIRE" "IMAGING" "OTHER" ) ;
        sh:description "Coarse categorization for operator sort/filter, NOT for specialization. Per FIRST-PRINCIPLES universal-foundation posture, the entity shape is the same for all types — activityType is a hint, not a discriminator that changes structure." ;
        sh:name "activityType" ;
    ] ;
    sh:property [
        sh:path top:activityStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PERFORMED" "NOT_PERFORMED" "PARTIALLY_PERFORMED" "RESCHEDULED" ) ;
        sh:description "Lifecycle of this Activity. NGSI-LD temporal property." ;
        sh:name "activityStatus" ;
    ] ;
    sh:property [
        sh:path top:notPerformedReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "When activityStatus=NOT_PERFORMED, the reason (subject refused, equipment failure, scheduling conflict, etc.). Required by SHACL when status indicates non-performance." ;
        sh:name "notPerformedReason" ;
    ] ;
    sh:property [
        sh:path top:biomedicalConceptCode ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NCIt C-code (or LOINC where applicable) identifying what the Activity IS. References COSMoS BC catalog. The discriminator that operators (and downstream systems) use to know 'this is a Vital Signs activity' vs 'this is an MRI Brain activity' — content, not shape." ;
        sh:name "biomedicalConceptCode" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: operational start time of the Activity within the Visit." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: operational end time." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:performedBy ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Person(s) who performed the Activity. PROV: prov:wasAssociatedWith. Drawn from forStudySite.delegatesAuthorityTo for delegated tasks." ;
        sh:name "performedBy" ;
    ] ;
    sh:property [
        sh:path top:usedEquipment ;
        sh:class topc:Equipment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Equipment used to perform the Activity (centrifuge, MRI scanner, infusion pump, ECG machine, etc.). PROV: prov:used. The Equipment's calibration/credential chain is queryable from here." ;
        sh:name "usedEquipment" ;
    ] ;
    sh:property [
        sh:path top:governedBy ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Document(s) that govern how this Activity should be performed (protocol section, SOP, lab manual). Document carries the section anchor. Per the chain-of-custody mock-up: 'Per shipping SOP-2026-CL-019' is a governedBy reference." ;
        sh:name "governedBy" ;
    ] .

top:performedBy rdfs:subPropertyOf prov:wasAssociatedWith .
top:usedEquipment rdfs:subPropertyOf prov:used .

top:Task rdfs:subClassOf prov:Activity .

top:TaskShape a sh:NodeShape ;
    sh:targetClass top:Task ;
    sh:name "Task" ;
    sh:description "Task is the universal leaf data-capture unit at a Visit (one Task per measurement / response / dose / observation). Per FIRST-PRINCIPLES universal-foundation posture, Task handles ANY captured data via polymorphic taskValue (NUMERIC / TEXT / CODED / URI_REFERENCE / STRUCTURED / DATE / IMAGE_REFERENCE). A BP measurement, an MRI Study Instance UID reference, a PHQ-9 item response, a drug dose record, a tumor lesion measurement — all the same TOP entity shape. The specialization lives in content. Each Task carries observedAt as Property metadata on taskValue (NOT a separate flat performedDateTime attribute) per NGSI-LD temporal conventions. Maps to FHIR Observation (one Observation per Task) and per-domain SDTM rows (VS/LB/EX/etc.)." ;
    sh:property [
        sh:path top:taskId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "taskId" ;
    ] ;
    sh:property [
        sh:path top:taskName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly name (e.g., 'Systolic BP', 'MRI Study Instance UID', 'PHQ-9 Item 1', 'Drug X dose')." ;
        sh:name "taskName" ;
    ] ;
    sh:property [
        sh:path top:taskValueType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "NUMERIC" "TEXT" "CODED" "URI_REFERENCE" "STRUCTURED" "DATE" "IMAGE_REFERENCE" ) ;
        sh:description "Discriminates the shape of taskValue. NUMERIC for numeric measurements; TEXT for free text; CODED for enum/concept-code values; URI_REFERENCE for URIs to external artifacts (DICOM PACS, S3, lab LIS); STRUCTURED for nested object values (lesion measurements with components, BP with sys/dia/MAP); DATE for captured dates; IMAGE_REFERENCE for radiology/pathology image URIs." ;
        sh:name "taskValueType" ;
    ] ;
    sh:property [
        sh:path top:taskValue ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "The captured value, polymorphic per taskValueType. NUMERIC values held as string for SHACL flexibility; deployment-side typing applies at projection time. NGSI-LD observedAt Property metadata captures when the value was measured/observed (NOT a separate performedDateTime attribute)." ;
        sh:name "taskValue" ;
    ] ;
    sh:property [
        sh:path top:taskUnit ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "UCUM unit where applicable (e.g., '[mmHg]', 'cm', 'mg', 'beats/min'). Empty for taskValueType=TEXT/CODED/URI_REFERENCE/etc. where unit doesn't apply." ;
        sh:name "taskUnit" ;
    ] ;
    sh:property [
        sh:path top:biomedicalConceptCode ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NCIt or LOINC C-code identifying what the Task IS (the BC catalog lookup). e.g., NCIt:C25298 for Systolic BP, LOINC:44261-6 for PHQ-9 Q1, NCIt:C16809 for MRI Brain." ;
        sh:name "biomedicalConceptCode" ;
    ] ;
    sh:property [
        sh:path top:taskStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "COMPLETED" "NOT_PERFORMED" "PARTIALLY_COMPLETED" ) ;
        sh:description "Lifecycle of the Task. NGSI-LD temporal property." ;
        sh:name "taskStatus" ;
    ] ;
    sh:property [
        sh:path top:notPerformedReason ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "When taskStatus=NOT_PERFORMED. Required by SHACL." ;
        sh:name "notPerformedReason" ;
    ] ;
    sh:property [
        sh:path top:observedAt ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "When the value was observed/measured. Canonical NGSI-LD Property metadata for measurement timestamps. Maps to FHIR Observation.effectiveDateTime and SDTM per-domain DTC fields." ;
        sh:name "observedAt" ;
    ] ;
    sh:property [
        sh:path top:belongsToActivity ;
        sh:class top:Activity ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The parent Activity within the same Visit. Tasks group under Activities for operator sort/filter and projection-adapter routing (e.g., all VS-domain Tasks in one Vitals Activity)." ;
        sh:name "belongsToActivity" ;
    ] ;
    sh:property [
        sh:path top:performedBy ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person who captured this Task. PROV: prov:wasAssociatedWith." ;
        sh:name "performedBy" ;
    ] .

top:performedBy rdfs:subPropertyOf prov:wasAssociatedWith .

top:VisitObservation rdfs:subClassOf prov:Activity .

top:VisitObservationShape a sh:NodeShape ;
    sh:targetClass top:VisitObservation ;
    sh:name "VisitObservation" ;
    sh:description "VisitObservation captures something the operator notices during a visit that doesn't fit a structured Activity / Task slot — observational notes, unexpected events, patient-reported context. Per OOUX Path C, may escalate to a categorized Event (when Event lifts) via escalatedTo. Distinct from Task (which is structured data capture). PROV: each VisitObservation is a prov:Activity that wasInformedBy by the parent Visit; if escalated, the resulting Event was wasInformedBy this VisitObservation." ;
    sh:property [
        sh:path top:observationId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "observationId" ;
    ] ;
    sh:property [
        sh:path top:observationText ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "The operator's narrative observation." ;
        sh:name "observationText" ;
    ] ;
    sh:property [
        sh:path top:observationCategory ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PROTOCOL_DEVIATION" "SAFETY_SIGNAL" "DATA_DISCREPANCY" "OPERATIONAL_NOTE" "OTHER" ) ;
        sh:description "Coarse categorization. SAFETY_SIGNAL escalates to AE workflow; PROTOCOL_DEVIATION goes to deviation tracking; DATA_DISCREPANCY into query workflow; OPERATIONAL_NOTE for routine notes; OTHER for catch-all." ;
        sh:name "observationCategory" ;
    ] ;
    sh:property [
        sh:path top:observationDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "When the observation was recorded." ;
        sh:name "observationDate" ;
    ] ;
    sh:property [
        sh:path top:observationTime ;
        sh:datatype xsd:time ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Time-of-day the observation was recorded." ;
        sh:name "observationTime" ;
    ] ;
    sh:property [
        sh:path top:escalated ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "True if this observation has been escalated to a categorized Event. SHACL soft warning: escalated=true should have escalatedTo populated." ;
        sh:name "escalated" ;
    ] ;
    sh:property [
        sh:path top:escalationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the escalation occurred." ;
        sh:name "escalationDate" ;
    ] ;
    sh:property [
        sh:path top:recordedBy ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person who recorded the observation (typically a CRA, coordinator, or PI). PROV: prov:wasAssociatedWith." ;
        sh:name "recordedBy" ;
    ] ;
    sh:property [
        sh:path top:escalatedTo ;
        sh:class top:Event ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "When escalated, the categorized Event that resulted from this observation. PROV: the Event was wasInformedBy this observation. Inverse of Event.derivedFrom (operator-meaningful escalation chain)." ;
        sh:name "escalatedTo" ;
    ] .

top:recordedBy rdfs:subPropertyOf prov:wasAssociatedWith .
top:escalatedTo rdfs:subPropertyOf prov:wasInformedBy .

top:OversightBody rdfs:subClassOf prov:Agent .

top:OversightBodyShape a sh:NodeShape ;
    sh:targetClass top:OversightBody ;
    sh:name "OversightBody" ;
    sh:description "An OversightBody is a committee that holds review authority over a Study or StudySite — IRB (Institutional Review Board) for site-level ethics review, EC (Ethics Committee) for the EU equivalent, DSMB / IDMC (Data Safety Monitoring Board / Independent Data Monitoring Committee) for sponsor-level interim safety review, Steering Committee for cross-site governance, CEC (Clinical Events Committee) for endpoint adjudication. Per FIRST-PRINCIPLES universal-foundation posture, OversightBody is a foundation primitive (every regulated trial has oversight bodies regardless of therapeutic area). The oversight body holds authority; the review activities happen through ReviewDecision sub-objects." ;
    sh:property [
        sh:path top:oversightBodyId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "oversightBodyId" ;
    ] ;
    sh:property [
        sh:path top:oversightBodyName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name (e.g., 'Memorial Sloan Kettering Cancer Center IRB', 'ONCO-423 Independent Data Monitoring Committee')." ;
        sh:name "oversightBodyName" ;
    ] ;
    sh:property [
        sh:path top:oversightBodyType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "IRB" "EC" "DSMB" "IDMC" "STEERING_COMMITTEE" "CEC" "OTHER" ) ;
        sh:description "Type of oversight body. IRB for US institutional review boards; EC for European ethics committees; DSMB / IDMC are interchangeable terms for sponsor-level interim safety review boards; STEERING_COMMITTEE for cross-site governance; CEC for clinical events committees doing endpoint adjudication; OTHER for catch-all." ;
        sh:name "oversightBodyType" ;
    ] ;
    sh:property [
        sh:path top:scope ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "LOCAL" "CENTRAL" "SPONSOR" "REGULATORY" "OTHER" ) ;
        sh:description "Scope of authority. LOCAL for site-specific (typical IRB at a single institution); CENTRAL for multi-site coverage (a single IRB serving multiple sites); SPONSOR for sponsor-level (DSMB/IDMC); REGULATORY for regulator-affiliated; OTHER for catch-all." ;
        sh:name "scope" ;
    ] ;
    sh:property [
        sh:path top:registrationNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Registration identifier (e.g., FDA OHRP IRB registration number IRB00000123, FWA00001234, EU EC reference)." ;
        sh:name "registrationNumber" ;
    ] ;
    sh:property [
        sh:path top:registrationAuthority ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "The authority that issued the registration (e.g., 'FDA OHRP', 'EU CTIS', 'NHS HRA')." ;
        sh:name "registrationAuthority" ;
    ] ;
    sh:property [
        sh:path top:registrationCountry ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3 country code where the body is registered." ;
        sh:name "registrationCountry" ;
    ] ;
    sh:property [
        sh:path top:address ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Mailing address (line1, city, region, postalCode, country)." ;
        sh:name "address" ;
    ] ;
    sh:property [
        sh:path top:phone ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "phone" ;
    ] ;
    sh:property [
        sh:path top:email ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "email" ;
    ] ;
    sh:property [
        sh:path top:website ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "website" ;
    ] ;
    sh:property [
        sh:path top:operatingProcedure ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "URI to the body's published operating procedure / charter / SOP. References a Document horizontal entity for full lineage when uploaded." ;
        sh:name "operatingProcedure" ;
    ] ;
    sh:property [
        sh:path top:establishedDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the body was established." ;
        sh:name "establishedDate" ;
    ] ;
    sh:property [
        sh:path top:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "SUSPENDED" "DISBANDED" ) ;
        sh:description "Lifecycle state. NGSI-LD temporal property — bodies can be suspended (regulatory hold) or disbanded after study completion." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this OversightBody record became operationally valid for the trial." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this OversightBody record was superseded or closed." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:belongsToOrganization ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization that hosts / sponsors / convened the oversight body. 0..1 because some bodies (especially DSMBs) are convened ad-hoc without a hosting Organization. PROV: the Organization is a higher-order Agent." ;
        sh:name "belongsToOrganization" ;
    ] ;
    sh:property [
        sh:path top:hasMember ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Committee members. Each Person has expertise relevant to the body's mandate (clinical, statistical, ethical, patient advocacy). PROV: each member is a prov:Agent acting on behalf of the OversightBody." ;
        sh:name "hasMember" ;
    ] ;
    sh:property [
        sh:path top:chair ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The committee chair. 0..1 because some bodies operate with co-chairs or rotating chairs." ;
        sh:name "chair" ;
    ] ;
    sh:property [
        sh:path top:reviewsStudy ;
        sh:class top:Study ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Studies this body has review authority over. 0..N: an IRB reviews many studies; a DSMB typically reviews one." ;
        sh:name "reviewsStudy" ;
    ] ;
    sh:property [
        sh:path top:reviewsStudySite ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "StudySites this body has review authority over. Site-level IRBs typically review per-StudySite; central IRBs review multiple StudySites under one Study." ;
        rdfs:comment "TARGET-MISSING: StudySite is a horizontal but the relationship target resolves correctly; no flag needed in v0.5.1." ;
        sh:name "reviewsStudySite" ;
    ] .

top:hasMember rdfs:subPropertyOf prov:actedOnBehalfOf .

top:InvestigationalProduct rdfs:subClassOf prov:Entity .

top:InvestigationalProductShape a sh:NodeShape ;
    sh:targetClass top:InvestigationalProduct ;
    sh:name "InvestigationalProduct" ;
    sh:description "An InvestigationalProduct (IP) is the substance, biologic, medical device, or combination being studied in a Study. Per FIRST-PRINCIPLES universal-foundation posture, IP is a single entity covering all product types — drugs, biologics, devices, diagnostics, combination products — discriminated by investigationalProductType enum. Specialization is content (productCode, dosageForm, mechanismOfAction, regulatorySource); not entity shape. Per ICH E6(R3) Section 2.7 and 21 CFR 312.6, IPs require lot tracking, blinded kit packaging (when applicable), and dispensation accountability. TOP models these via two sub-objects: Lot (per-batch manufacturing identity) and Kit (per-blinded-packaging-unit). Dispensation events live as Visit Activities (activityType=IP_ADMINISTRATION) per the universal Visit > Activity > Task foundation; no separate Dispensation entity needed." ;
    sh:property [
        sh:path top:investigationalProductId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "investigationalProductId" ;
    ] ;
    sh:property [
        sh:path top:investigationalProductName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Operator-friendly name (e.g., 'Drug X', 'BioCar-T-001', 'NeuroMonitor v2')." ;
        sh:name "investigationalProductName" ;
    ] ;
    sh:property [
        sh:path top:genericName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Generic / non-proprietary name (when applicable)." ;
        sh:name "genericName" ;
    ] ;
    sh:property [
        sh:path top:brandName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Brand / proprietary name (when commercially available, e.g., for active comparators or reformulations)." ;
        sh:name "brandName" ;
    ] ;
    sh:property [
        sh:path top:investigationalProductType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "DRUG" "BIOLOGIC" "DEVICE" "DIAGNOSTIC" "COMBINATION_PRODUCT" "OTHER" ) ;
        sh:description "Coarse type discriminator. DRUG for small-molecule pharmaceuticals; BIOLOGIC for biologics including monoclonal antibodies, vaccines, cell/gene therapies; DEVICE for medical devices; DIAGNOSTIC for in vitro diagnostics; COMBINATION_PRODUCT for drug-device or biologic-device combinations; OTHER for catch-all. Per FIRST-PRINCIPLES universal-foundation posture, this is a category discriminator for sort/filter, NOT a shape discriminator — the entity is the same shape regardless of type." ;
        sh:name "investigationalProductType" ;
    ] ;
    sh:property [
        sh:path top:mechanismOfAction ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Brief description of how the product works (e.g., 'PD-1 checkpoint inhibitor', 'CAR-T cell therapy targeting CD19')." ;
        sh:name "mechanismOfAction" ;
    ] ;
    sh:property [
        sh:path top:dosageForm ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Dosage form (TABLET / CAPSULE / SOLUTION / SUSPENSION / INJECTION / INFUSION / TOPICAL / IMPLANT / DEVICE — free text accepts NCIt or FHIR Medication.form codes)." ;
        sh:name "dosageForm" ;
    ] ;
    sh:property [
        sh:path top:routeOfAdministration ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Primary route of administration (ORAL / IV / IM / SC / TOPICAL / INTRATHECAL / etc.)." ;
        sh:name "routeOfAdministration" ;
    ] ;
    sh:property [
        sh:path top:strength ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Strength / concentration (e.g., '200 mg', '50 mg/mL', '10^7 cells/dose')." ;
        sh:name "strength" ;
    ] ;
    sh:property [
        sh:path top:isBlinded ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Whether this IP is dispensed under blinding (true requires Kit sub-objects with treatmentAssignment for IRT-managed kit assignment; false means open-label)." ;
        sh:name "isBlinded" ;
    ] ;
    sh:property [
        sh:path top:regulatoryStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "INVESTIGATIONAL" "APPROVED_OFF_LABEL" "EXPANDED_ACCESS" "MARKETED_COMPARATOR" "OTHER" ) ;
        sh:description "Regulatory status of the product. INVESTIGATIONAL for true IND/IDE products; APPROVED_OFF_LABEL when an approved drug is investigated for a new indication; EXPANDED_ACCESS for compassionate-use frameworks; MARKETED_COMPARATOR for active comparator arms using approved drugs; OTHER for catch-all." ;
        sh:name "regulatoryStatus" ;
    ] ;
    sh:property [
        sh:path top:regulatorySource ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Regulatory authority and identifier (e.g., 'FDA IND #123456', 'EMA EudraCT 2024-001234-56', 'PMDA Notification #...')." ;
        sh:name "regulatorySource" ;
    ] ;
    sh:property [
        sh:path top:biomedicalConceptCode ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NCIt C-code for the substance / device. References COSMoS BC catalog or external substance registries (UNII, RxNorm)." ;
        sh:name "biomedicalConceptCode" ;
    ] ;
    sh:property [
        sh:path top:investigationalProductStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PLANNED" "IN_DEVELOPMENT" "IN_USE" "DISCONTINUED" "WITHDRAWN" ) ;
        sh:description "Lifecycle of the IP across all studies that use it. PLANNED for pre-IND; IN_DEVELOPMENT for pre-first-in-human; IN_USE for actively-administered; DISCONTINUED for development paused; WITHDRAWN for safety-driven removal. NGSI-LD temporal property." ;
        sh:name "investigationalProductStatus" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this IP record became operationally valid." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:suppliedBySponsor ;
        sh:class top:Sponsor ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Sponsor that supplies / manufactures / imports this IP. Per 21 CFR 312.6 the IP supplier is regulatorily accountable for product labeling, lot tracking, and dispensation oversight." ;
        sh:name "suppliedBySponsor" ;
    ] ;
    sh:property [
        sh:path top:forStudy ;
        sh:class top:Study ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Studies that use this IP. 0..N because some IPs are used across multiple studies (platform trials, basket trials, expanded-access programs)." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path top:hasLot ;
        sh:class top:Lot ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Manufacturing lots / batches of this IP. Each Lot has its own manufacturing date, expiration, release status, and accountability chain." ;
        sh:name "hasLot" ;
    ] ;
    sh:property [
        sh:path top:hasKit ;
        sh:class top:Kit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Blinded packaging units (kits) prepared from this IP. Required when isBlinded=true (SHACL invariant). Each Kit ties to a Lot and is assigned to a Participant via IRT." ;
        sh:name "hasKit" ;
    ] ;
    sh:property [
        sh:path top:governedByDocument ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Documents governing this IP — Investigator's Brochure, IP handling instructions, product label, Certificate of Analysis (lot-specific), IP destruction record. Per ICH E6(R3) Section 2.7." ;
        sh:name "governedByDocument" ;
    ] .

top:Lot rdfs:subClassOf prov:Entity .

top:LotShape a sh:NodeShape ;
    sh:targetClass top:Lot ;
    sh:name "Lot" ;
    sh:description "A Lot represents a single manufacturing batch of an InvestigationalProduct. Per ICH E6(R3) Section 2.7, IP must be tracked at lot level for accountability, expiry management, and recall response. The Lot carries manufacturing date, release date, expiration date, retest date (when applicable), manufacturer / facility identifiers, and lot status. Dispensation events at Visits reference the Lot used (via Visit's STRUCTURED taskValue or via Kit assignment for blinded studies)." ;
    sh:property [
        sh:path top:lotId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "lotId" ;
    ] ;
    sh:property [
        sh:path top:lotNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Sponsor-assigned lot identifier (e.g., 'DRX-2026-0117'). Operator-grounded — what gets printed on the IP label and tracked in pharmacy." ;
        sh:name "lotNumber" ;
    ] ;
    sh:property [
        sh:path top:manufacturedDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the lot was manufactured." ;
        sh:name "manufacturedDate" ;
    ] ;
    sh:property [
        sh:path top:releaseDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the lot was released for clinical use (post-QC / post-Certificate-of-Analysis)." ;
        sh:name "releaseDate" ;
    ] ;
    sh:property [
        sh:path top:expirationDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Expiration date. Hard SHACL: lots cannot be administered after expiration; lotStatus=EXPIRED requires this date <= today." ;
        sh:name "expirationDate" ;
    ] ;
    sh:property [
        sh:path top:retestDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date by which the lot must be re-tested (for stability-extension lots). Optional — only applies when stability data supports retest extension." ;
        sh:name "retestDate" ;
    ] ;
    sh:property [
        sh:path top:manufacturer ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Name of the manufacturer (when distinct from suppliedBySponsor — e.g., contract manufacturer)." ;
        sh:name "manufacturer" ;
    ] ;
    sh:property [
        sh:path top:manufacturingFacility ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Manufacturing facility identifier or address." ;
        sh:name "manufacturingFacility" ;
    ] ;
    sh:property [
        sh:path top:quantityProduced ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Number of units produced in this lot (kits, vials, tablets — unit depends on dosageForm)." ;
        sh:name "quantityProduced" ;
    ] ;
    sh:property [
        sh:path top:lotStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "MANUFACTURING" "RELEASED" "IN_DISTRIBUTION" "DISTRIBUTED" "EXPIRED" "RECALLED" "DESTROYED" ) ;
        sh:description "Lot lifecycle. NGSI-LD temporal property. MANUFACTURING during production; RELEASED after QC sign-off; IN_DISTRIBUTION during shipment; DISTRIBUTED when arrived at sites; EXPIRED past expiration date; RECALLED for safety / quality recall; DESTROYED post-reconciliation." ;
        sh:name "lotStatus" ;
    ] ;
    sh:property [
        sh:path top:governedByDocument ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Documents governing this lot — Certificate of Analysis (lot-specific), lot release record, lot destruction record." ;
        sh:name "governedByDocument" ;
    ] .

top:Kit rdfs:subClassOf prov:Entity .

top:KitShape a sh:NodeShape ;
    sh:targetClass top:Kit ;
    sh:name "Kit" ;
    sh:description "A Kit is a single blinded packaging unit prepared from a Lot, assigned to a Participant via IRT (Interactive Response Technology). Required for blinded studies where treatment assignment is masked at the dispensation level. Each Kit carries the kit number (printed on the label), its source Lot, the treatment assignment code (the IRT-issued randomization code; the unblinding key is held separately by the IRT vendor), and lifecycle status (PACKAGED → IN_INVENTORY → DISPENSED → RETURNED → RECONCILED)." ;
    sh:property [
        sh:path top:kitId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "kitId" ;
    ] ;
    sh:property [
        sh:path top:kitNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Sponsor / IRT-assigned kit identifier (e.g., 'KIT-00042'). Operator-grounded — what gets scanned at dispensation." ;
        sh:name "kitNumber" ;
    ] ;
    sh:property [
        sh:path top:packagedDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the kit was packaged from its Lot." ;
        sh:name "packagedDate" ;
    ] ;
    sh:property [
        sh:path top:treatmentAssignment ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "IRT-issued treatment-assignment code. For blinded studies, this is a coded label (e.g., 'A', 'B', 'PLACEBO_GROUP_2') whose unblinding key is held separately by the IRT vendor; the foundation carries the code without the unblinding map." ;
        sh:name "treatmentAssignment" ;
    ] ;
    sh:property [
        sh:path top:assignmentDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the kit was assigned to a Participant (typically the randomization or dispensation date)." ;
        sh:name "assignmentDate" ;
    ] ;
    sh:property [
        sh:path top:kitStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PACKAGED" "IN_INVENTORY" "ASSIGNED" "DISPENSED" "RETURNED" "RECONCILED" "DESTROYED" ) ;
        sh:description "Kit lifecycle. NGSI-LD temporal property. PACKAGED at packaging facility; IN_INVENTORY at site pharmacy; ASSIGNED when IRT issues to Participant (pre-dispensation); DISPENSED when handed to Participant; RETURNED when unused returned to pharmacy; RECONCILED at end-of-trial reconciliation; DESTROYED per IP destruction record." ;
        sh:name "kitStatus" ;
    ] ;
    sh:property [
        sh:path top:belongsToLot ;
        sh:class top:Lot ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Lot this Kit was packaged from. Each Kit traces back to exactly one Lot for accountability." ;
        sh:name "belongsToLot" ;
    ] ;
    sh:property [
        sh:path top:assignedToParticipant ;
        sh:class top:Participant ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Participant the Kit is assigned to (post-IRT-assignment). 0..1 because Kits in IN_INVENTORY haven't been assigned yet. SHACL: kitStatus=ASSIGNED or DISPENSED requires this to be populated." ;
        sh:name "assignedToParticipant" ;
    ] .

top:Event rdfs:subClassOf prov:Activity .

top:EventShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:name "Event" ;
    sh:description "An Event is a single container holding occurrences related to a Study lifecycle: Adverse Event (AE), Serious Adverse Event (SAE), Protocol Deviation, Data Discrepancy, Safety Signal, Safety Report, Other Clinical Event. Per FIRST-PRINCIPLES universal-foundation posture, Event is one entity for all categories — discriminated by eventCategory enum rather than split into specialized classes. Per ICH E2A / E2B(R3) and 21 CFR 312.32, regulatory reportability is a first-class attribute with submission timeline tracking. Per ICH E6(R3) Section 6, protocol deviations carry category and impact. Other Clinical Event carries derivedFrom back to a VisitObservation when a routine observation escalates to a tracked Event." ;
    sh:property [
        sh:path top:eventId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "eventId" ;
    ] ;
    sh:property [
        sh:path top:eventCategory ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ADVERSE_EVENT" "SERIOUS_ADVERSE_EVENT" "DEVIATION" "DISCREPANCY" "SAFETY_SIGNAL" "SAFETY_REPORT" "OTHER_CLINICAL_EVENT" ) ;
        sh:description "Discriminator across the Event categories. ADVERSE_EVENT for any untoward medical occurrence; SERIOUS_ADVERSE_EVENT for SAEs (death, life-threatening, hospitalization, persistent disability, congenital anomaly, medically important — ICH E2A definition); DEVIATION for protocol deviations (ICH E6 Section 6); DISCREPANCY for data discrepancies surfaced during cleaning; SAFETY_SIGNAL for aggregate safety signals (cross-participant trends); SAFETY_REPORT for E2B(R3) Individual Case Safety Reports submitted to regulators; OTHER_CLINICAL_EVENT for catch-all escalations (e.g., an unusual VisitObservation that doesn't fit AE but warrants tracking)." ;
        sh:name "eventCategory" ;
    ] ;
    sh:property [
        sh:path top:eventType ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Free-form workflow-domain term for the specific occurrence (e.g., 'Grade 3 nausea', 'Missed visit window', 'Lab discrepancy: alkaline phosphatase'). Not a controlled vocabulary at this layer; controlled vocabulary lives in meddraPreferredTerm (for AE/SAE) or protocolDeviationCategory (for DEVIATION)." ;
        sh:name "eventType" ;
    ] ;
    sh:property [
        sh:path top:eventDate ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "When the event occurred (or, for ongoing events, when it began). NGSI-LD temporal property — events have a real-world timeline distinct from when they were reported. PROV: this is the prov:atTime of the prov:Activity." ;
        sh:name "eventDate" ;
    ] ;
    sh:property [
        sh:path top:detectedDate ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "When the event was detected (which may be later than when it occurred — e.g., a Grade 1 AE noticed retroactively at the next visit, or a discrepancy surfaced during data cleaning weeks after data entry)." ;
        sh:name "detectedDate" ;
    ] ;
    sh:property [
        sh:path top:reportedDate ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "When the event was formally reported into the trial system. For SAEs, this anchors the 24-hour / 7-day / 15-day reporting clocks." ;
        sh:name "reportedDate" ;
    ] ;
    sh:property [
        sh:path top:description ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Operator-grounded narrative description. For AE/SAE this is the verbatim term; for DEVIATION the deviation narrative; for DISCREPANCY the specific data quality issue." ;
        sh:name "description" ;
    ] ;
    sh:property [
        sh:path top:severity ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "MILD" "MODERATE" "SEVERE" "LIFE_THREATENING" "FATAL" "LOW" "MEDIUM" "HIGH" "CRITICAL" ) ;
        sh:description "Severity grading. MILD/MODERATE/SEVERE/LIFE_THREATENING/FATAL for AE/SAE per CTCAE/ICH E2A; LOW/MEDIUM/HIGH/CRITICAL for non-clinical event categories (DEVIATION, DISCREPANCY). Mutually-exclusive scales kept in one enum for foundation simplicity; controlled-vocabulary projection at the adapter layer enforces the right scale per category." ;
        sh:name "severity" ;
    ] ;
    sh:property [
        sh:path top:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "REPORTED" "UNDER_REVIEW" "ASSESSED" "RESOLVED" "ONGOING" "CLOSED" ) ;
        sh:description "Lifecycle state. NGSI-LD temporal property — Events transition REPORTED → UNDER_REVIEW → ASSESSED → (RESOLVED|ONGOING) → CLOSED with full audit trail." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path top:detectionSource ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "VISIT_OBSERVATION" "SELF_REPORT" "EXTERNAL_REPORT" "MONITORING" "ROUTINE_LAB" "DATA_CLEANING" "AGGREGATE_REVIEW" "OTHER" ) ;
        sh:description "How the Event was detected. VISIT_OBSERVATION pairs with derivedFrom (the routine observation that escalated); SELF_REPORT is participant-initiated; EXTERNAL_REPORT covers ER visits, hospitalizations reported by family/caregiver; MONITORING surfaces during CRA review; ROUTINE_LAB for lab-flagged values; DATA_CLEANING for query-driven discrepancies; AGGREGATE_REVIEW for cross-participant safety signals." ;
        sh:name "detectionSource" ;
    ] ;
    sh:property [
        sh:path top:identifier ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Workflow-issued identifier (e.g., 'AE-2026-0014', 'DEV-MSKCC-008'). Distinct from eventId (URI); identifier is the human-meaningful reference operators use in narratives and CAPAs." ;
        sh:name "identifier" ;
    ] ;
    sh:property [
        sh:path top:causality ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "UNRELATED" "UNLIKELY" "POSSIBLY" "PROBABLY" "DEFINITELY" "NOT_ASSESSABLE" ) ;
        sh:description "Investigator's causality assessment relative to the InvestigationalProduct. Required field for AE/SAE per ICH E2A; not applicable to DEVIATION or DISCREPANCY. The action taken with IP (actionTakenWithIp) often correlates with causality but is recorded separately." ;
        sh:name "causality" ;
    ] ;
    sh:property [
        sh:path top:expectedness ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "EXPECTED" "UNEXPECTED" ) ;
        sh:description "Whether the event is expected per the Investigator's Brochure (IB). UNEXPECTED + SERIOUS + IP-related = SUSAR (Suspected Unexpected Serious Adverse Reaction) — the highest-priority regulatory reporting category per ICH E2A." ;
        sh:name "expectedness" ;
    ] ;
    sh:property [
        sh:path top:regulatoryReportable ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Whether this event triggers a regulatory submission (e.g., to FDA via E2B, to EMA via EudraVigilance). SHACL invariant: SERIOUS_ADVERSE_EVENT must have regulatoryReportable=true." ;
        sh:name "regulatoryReportable" ;
    ] ;
    sh:property [
        sh:path top:regulatoryReportDeadline ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Deadline for regulatory submission. SUSARs: 7 days for fatal/life-threatening, 15 days for other; non-SUSAR SAEs: 15 days; periodic safety reports vary. SHACL soft warning when this is in the past and regulatoryReportSubmitted is still false (overdue)." ;
        sh:name "regulatoryReportDeadline" ;
    ] ;
    sh:property [
        sh:path top:regulatoryReportSubmitted ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Whether the regulatory report has been submitted. NGSI-LD temporal property — flips from false to true once submission lands. SHACL invariant: when true, regulatoryReportSubmittedDate must be populated." ;
        sh:name "regulatoryReportSubmitted" ;
    ] ;
    sh:property [
        sh:path top:regulatoryReportSubmittedDate ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date/time the regulatory report was submitted. Anchors compliance audit trails — comparing this to eventDate validates timeline compliance." ;
        sh:name "regulatoryReportSubmittedDate" ;
    ] ;
    sh:property [
        sh:path top:meddraPreferredTerm ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "MedDRA Preferred Term (PT) for AE/SAE coding. Controlled vocabulary; CDISC-pipeline manifest carries the active MedDRA version. Required for AE/SAE that submit to E2B." ;
        sh:name "meddraPreferredTerm" ;
    ] ;
    sh:property [
        sh:path top:meddraSystemOrganClass ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "MedDRA System Organ Class (SOC). Highest level in the MedDRA hierarchy; derived from PT but stored separately for query convenience and aggregate reporting." ;
        sh:name "meddraSystemOrganClass" ;
    ] ;
    sh:property [
        sh:path top:ctcaeGrade ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "GRADE_1" "GRADE_2" "GRADE_3" "GRADE_4" "GRADE_5" ) ;
        sh:description "CTCAE grading per CTCAE 5.0 (NCI Common Terminology Criteria for Adverse Events). 1=Mild, 2=Moderate, 3=Severe, 4=Life-threatening, 5=Death. SHACL invariant: ctcaeGrade only valid when eventCategory IN {ADVERSE_EVENT, SERIOUS_ADVERSE_EVENT}." ;
        sh:name "ctcaeGrade" ;
    ] ;
    sh:property [
        sh:path top:actionTakenWithIp ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "NONE" "DOSE_REDUCED" "DOSE_INTERRUPTED" "DOSE_DISCONTINUED" "NEW_TREATMENT_INITIATED" "NOT_APPLICABLE" "UNKNOWN" ) ;
        sh:description "Action taken with the InvestigationalProduct in response to the event. Per ICH E2A required AE field. NOT_APPLICABLE for DEVIATION / DISCREPANCY events." ;
        sh:name "actionTakenWithIp" ;
    ] ;
    sh:property [
        sh:path top:outcome ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "RECOVERED" "RECOVERING" "NOT_RECOVERED" "RECOVERED_WITH_SEQUELAE" "FATAL" "UNKNOWN" ) ;
        sh:description "Clinical outcome of the event. Required for AE/SAE per E2A; not applicable to DEVIATION." ;
        sh:name "outcome" ;
    ] ;
    sh:property [
        sh:path top:protocolDeviationCategory ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "INCLUSION_VIOLATION" "EXCLUSION_VIOLATION" "INFORMED_CONSENT" "MISSED_VISIT" "MISSED_PROCEDURE" "OUT_OF_WINDOW_VISIT" "IP_NONCOMPLIANCE" "RANDOMIZATION_ERROR" "BLINDING_BREAK" "OTHER" ) ;
        sh:description "Categorization of protocol deviations per ICH E6(R3) Section 6. SHACL invariant: protocolDeviationCategory only valid when eventCategory=DEVIATION." ;
        sh:name "protocolDeviationCategory" ;
    ] ;
    sh:property [
        sh:path top:protocolDeviationImpact ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "NONE" "MINOR" "MAJOR" "CRITICAL" ) ;
        sh:description "Impact assessment for DEVIATION events per ICH E6(R3). MAJOR/CRITICAL deviations require root-cause analysis and CAPA." ;
        sh:name "protocolDeviationImpact" ;
    ] ;
    sh:property [
        sh:path top:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this Event record became operationally valid." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path top:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "NGSI-LD temporal property: when this Event record was superseded or invalidated (e.g., reclassified after assessment)." ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path top:forStudy ;
        sh:class top:Study ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this Event belongs to. Required — every Event ties to exactly one Study lifecycle." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path top:forParticipant ;
        sh:class top:Participant ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Participant the Event concerns (when applicable). 0..1 because some DEVIATION events (e.g., procedural failures, system outages) don't map to a single Participant; aggregate SAFETY_SIGNAL events may span participants and are queried via causedBy." ;
        sh:name "forParticipant" ;
    ] ;
    sh:property [
        sh:path top:forStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySite where the Event occurred or was detected. Critical for site-level monitoring and CAPA scoping." ;
        sh:name "forStudySite" ;
    ] ;
    sh:property [
        sh:path top:duringVisit ;
        sh:class top:Visit ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Visit during which the Event was detected (when applicable). Pairs with derivedFrom when detection happened via VisitObservation escalation." ;
        sh:name "duringVisit" ;
    ] ;
    sh:property [
        sh:path top:relatedToInvestigationalProduct ;
        sh:class top:InvestigationalProduct ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The IP suspected of causing or being relevant to the Event. Required for AE/SAE events where causality is being assessed; populated alongside causality and actionTakenWithIp." ;
        sh:name "relatedToInvestigationalProduct" ;
    ] ;
    sh:property [
        sh:path top:relatedToLot ;
        sh:class top:Lot ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Specific Lot of IP relevant to the Event. Critical for safety signals tied to a manufacturing batch (e.g., a recall trigger)." ;
        sh:name "relatedToLot" ;
    ] ;
    sh:property [
        sh:path top:relatedToKit ;
        sh:class top:Kit ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Specific Kit involved in the Event (for blinded studies). Pairs with IP_NONCOMPLIANCE deviations and BLINDING_BREAK events." ;
        sh:name "relatedToKit" ;
    ] ;
    sh:property [
        sh:path top:reportedBy ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person who reported the Event into the trial system. For AE/SAE typically the investigator or coordinator; for DEVIATION the monitor or coordinator. PROV: the event was wasAssociatedWith this Person." ;
        sh:name "reportedBy" ;
    ] ;
    sh:property [
        sh:path top:assignedTo ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person responsible for assessing/closing the Event (e.g., investigator for AE causality assessment, sponsor safety lead for SAE workup). PROV: prov:wasAssociatedWith with prov:hadRole assessor." ;
        sh:name "assignedTo" ;
    ] ;
    sh:property [
        sh:path top:derivedFrom ;
        sh:class top:VisitObservation ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The VisitObservation this Event was escalated from (when detectionSource=VISIT_OBSERVATION). PROV: this Event was wasInformedBy that observation. The escalation chain is operator-meaningful: a routine vital-sign observation escalates to a tracked AE Event with explicit lineage." ;
        sh:name "derivedFrom" ;
    ] ;
    sh:property [
        sh:path top:partOf ;
        sh:class top:Event ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Parent Event when this Event is a sub-component (e.g., an SAE that aggregates multiple precursor AEs; a Safety Signal aggregating individual AEs). Self-reference for nesting." ;
        sh:name "partOf" ;
    ] ;
    sh:property [
        sh:path top:causedBy ;
        sh:class top:Event ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Causal predecessor Events (e.g., a SAFETY_SIGNAL caused by a cluster of AEs; a SAFETY_REPORT caused by one or more SAEs). PROV: this Event was wasInformedBy these Events." ;
        sh:name "causedBy" ;
    ] ;
    sh:property [
        sh:path top:reviewedBy ;
        sh:class top:OversightBody ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "OversightBodies that reviewed this Event. DSMB/IDMC review SAEs and Safety Signals; IRB reviews aggregate safety reports per submitted updates; CEC adjudicates endpoint events." ;
        sh:name "reviewedBy" ;
    ] ;
    sh:property [
        sh:path top:documentedIn ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Supporting documents — MedWatch 3500A forms, narrative documents, CIOMS forms, CAPA records, deviation reports." ;
        sh:name "documentedIn" ;
    ] .

top:reportedBy rdfs:subPropertyOf prov:wasAssociatedWith .
top:assignedTo rdfs:subPropertyOf prov:wasAssociatedWith .
top:derivedFrom rdfs:subPropertyOf prov:wasInformedBy .
top:causedBy rdfs:subPropertyOf prov:wasInformedBy .

# === Cross-cutting horizontals ===

topc:Organization rdfs:subClassOf prov:Agent .

topc:OrganizationShape a sh:NodeShape ;
    sh:targetClass topc:Organization ;
    sh:name "Organization" ;
    sh:description "The legal/business entity. Sponsor and Site (TOP top-levels) reference Organization for their corporate identity and roll-up structure. Modeled after USDM v3 Organization, with the addition of parentOrganization for explicit corporate hierarchy. Cross-industry: Organizations also represent operating companies in energy (refiners, EPCs), manufacturing (parent corp, plants, contract manufacturers), and other domains." ;
    sh:property [
        sh:path topc:organizationId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "organizationId" ;
    ] ;
    sh:property [
        sh:path topc:organizationName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name." ;
        sh:name "organizationName" ;
    ] ;
    sh:property [
        sh:path topc:legalName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Registered legal name." ;
        sh:name "legalName" ;
    ] ;
    sh:property [
        sh:path topc:organizationType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PARENT_CORPORATION" "OPERATING_COMPANY" "SPONSOR" "CRO" "SITE" "SITE_NETWORK" "SMO" "REGULATORY_AUTHORITY" "OVERSIGHT_BODY" "VENDOR" "LABORATORY" "CONTRACT_MANUFACTURER" "ACADEMIC_INSTITUTION" "GOVERNMENT" "OTHER" ) ;
        sh:description "Role classification (multi-valued in practice via separate role assignments). Aligns with USDM Organization.type Code." ;
        sh:name "organizationType" ;
    ] ;
    sh:property [
        sh:path topc:identifierScheme ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "DUNS" "ORCID" "GLN" "GS1" "FDA_FEI" "EMA_EUD" "OTHER" ) ;
        sh:description "Scheme for the identifier value. USDM-aligned." ;
        sh:name "identifierScheme" ;
    ] ;
    sh:property [
        sh:path topc:identifier ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Identifier value scoped by identifierScheme." ;
        sh:name "identifier" ;
    ] ;
    sh:property [
        sh:path topc:legalAddress ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Headquarters/registered address (line1, line2, city, region, postalCode)." ;
        sh:name "legalAddress" ;
    ] ;
    sh:property [
        sh:path topc:country ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "ISO 3166-1 alpha-3 country code of registration." ;
        sh:name "country" ;
    ] ;
    sh:property [
        sh:path topc:website ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Organization website." ;
        sh:name "website" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "INACTIVE" "MERGED" "ACQUIRED" "DISSOLVED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:employeeSizeBand ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "1-10" "11-50" "51-200" "201-500" "501-1000" "1001-5000" "5001-10000" "10001-50000" "50001-100000" "100001+" ) ;
        sh:description "Banded employee count for the Organization. Corporate-scope fact; does not vary per Study, so lives here rather than on the per-Study Sponsor entity. Bands rather than exact counts because exact headcount is volatile and rarely needed for Sponsor portfolio analysis." ;
        sh:name "employeeSizeBand" ;
    ] ;
    sh:property [
        sh:path topc:fiscalYearEnd ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Fiscal year end as MM-DD (e.g., '12-31' for calendar year, '03-31' for many UK/Asian companies, '09-30' for US federal). Corporate-scope fact; useful for budget alignment, contract renewal cycles, and forecasting." ;
        sh:name "fiscalYearEnd" ;
    ] ;
    sh:property [
        sh:path topc:primaryTherapeuticAreas ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated or JSON array of the Organization's portfolio therapeutic areas (oncology, cardiology, infectious disease, rare disease, etc.). Corporate-scope: Pfizer's TA spread is many; J&J Innovative Medicine's TA spread is a subset. The per-Study TA focus lives on Sponsor.primaryTherapeuticArea. Multi-value semantics: native NGSI-LD multi-value once the broker layer is in place; for the strawman emission, comma-separated string is the lowest-risk encoding." ;
        sh:name "primaryTherapeuticAreas" ;
    ] ;
    sh:property [
        sh:path topc:parentOrganization ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Self-reference for corporate hierarchy. J&J Innovative Medicine.parentOrganization = J&J. Empty for root-level entities." ;
        sh:name "parentOrganization" ;
    ] ;
    sh:property [
        sh:path topc:hasChildOrganization ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Inverse of parentOrganization. Convenience relationship for downward traversal." ;
        sh:name "hasChildOrganization" ;
    ] ;
    sh:property [
        sh:path topc:managesSite ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sites operationally managed by this Organization. USDM Organization.managedSites." ;
        sh:name "managesSite" ;
    ] ;
    sh:property [
        sh:path topc:playsSponsorRole ;
        sh:class top:Sponsor ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Sponsor entities backed by this Organization in specific Studies." ;
        sh:name "playsSponsorRole" ;
    ] ;
    sh:property [
        sh:path topc:isLocatedIn ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Country of registration; flagged-missing object in OOUX v0.2." ;
        rdfs:comment "TARGET-MISSING: Country is a flagged-missing object in OOUX v0.2 (#57). minCount relaxed from 1 to 0 until target class is defined." ;
        sh:name "isLocatedIn" ;
    ] .

topc:Document rdfs:subClassOf prov:Entity .

topc:DocumentShape a sh:NodeShape ;
    sh:targetClass topc:Document ;
    sh:name "Document" ;
    sh:description "Document represents any file or formal record associated with the trial. ICH E6(R3) Appendix C is the canonical record taxonomy: records are categorized by purpose (what they contribute to under GCP) and by responsible party. The TMF Reference Model and the ISF section taxonomy are the legacy operational views that map onto R3 purposes. TOP carries both axes: essentialRecordPurpose (R3 primary) and isfSection (legacy operational view)." ;
    sh:property [
        sh:path topc:documentId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "documentId" ;
    ] ;
    sh:property [
        sh:path topc:documentTitle ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "documentTitle" ;
    ] ;
    sh:property [
        sh:path topc:documentType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PROTOCOL" "INVESTIGATOR_BROCHURE" "ICF" "FORM_1572" "IRB_APPROVAL_LETTER" "REGULATORY_SUBMISSION" "CTA" "CDA" "MTA" "DTA" "INSURANCE_CERTIFICATE" "SOURCE_DOCUMENT" "IP_HANDLING_INSTRUCTIONS" "IP_DESTRUCTION_RECORD" "BLINDING_PROCEDURES" "LAB_NORMAL_RANGES" "LAB_CERTIFICATION" "SAMPLE_HANDLING_MANUAL" "RECRUITMENT_AD" "TRANSLATION_CERTIFICATE" "DELEGATION_LOG" "SIGNATURE_SHEET" "CV" "TRAINING_RECORD" "MONITORING_REPORT" "AUDIT_REPORT" "VALIDATION_RECORD" "SECURITY_ASSESSMENT" "PROCEDURES_MANUAL" "SOP" "ESSENTIAL_RECORD_OTHER" "OTHER" ) ;
        sh:description "Document subtype taxonomy aligned to ISF section identifiers and TMF Reference Model zones. Operational/UX label." ;
        sh:name "documentType" ;
    ] ;
    sh:property [
        sh:path topc:documentVersion ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "documentVersion" ;
    ] ;
    sh:property [
        sh:path topc:documentStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "DRAFT" "IN_REVIEW" "APPROVED" "EFFECTIVE" "SUPERSEDED" "WITHDRAWN" "ARCHIVED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "documentStatus" ;
    ] ;
    sh:property [
        sh:path topc:essentialRecordPurpose ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "QUALIFICATION" "DELEGATION" "TRAINING" "IP_MANAGEMENT" "MONITORING" "DATA_HANDLING" "SYSTEMS_VALIDATION" "OTHER" ) ;
        sh:description "R3 Appendix C purpose: what this record contributes to under GCP." ;
        sh:name "essentialRecordPurpose" ;
    ] ;
    sh:property [
        sh:path topc:responsibleParty ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "INVESTIGATOR_INSTITUTION" "SPONSOR" "IRB_IEC" "REGULATORY_AUTHORITY" "VENDOR" "OTHER" ) ;
        sh:description "R3 Appendix C: party responsible for creating and maintaining this record." ;
        sh:name "responsibleParty" ;
    ] ;
    sh:property [
        sh:path topc:isfSection ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Legacy operational view: ISF section identifier (e.g., '11.1' for Signature Sheet, '14.3' for IP Accountability Log, '22.1' for Sponsor correspondences). Free-text rather than enum because ISF flavors vary (Singapore HSA, FDA, EMA all have variations)." ;
        sh:name "isfSection" ;
    ] ;
    sh:property [
        sh:path topc:etmfLocation ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "URI of the document in its eTMF/ISF system." ;
        sh:name "etmfLocation" ;
    ] ;
    sh:property [
        sh:path topc:creationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "creationDate" ;
    ] ;
    sh:property [
        sh:path topc:effectiveDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "effectiveDate" ;
    ] ;
    sh:property [
        sh:path topc:expirationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "expirationDate" ;
    ] ;
    sh:property [
        sh:path topc:retentionUntil ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Retention end date per regulatory/contractual requirements." ;
        sh:name "retentionUntil" ;
    ] ;
    sh:property [
        sh:path topc:language ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ISO 639-1 code." ;
        sh:name "language" ;
    ] .

topc:StudySite rdfs:subClassOf prov:Agent .

topc:StudySiteShape a sh:NodeShape ;
    sh:targetClass topc:StudySite ;
    sh:name "StudySite" ;
    sh:description "StudySite captures a Site's role on a specific Study. Every per-Site-per-Study fact (siteNumber, enrollmentTarget, IRB approval status, oversight tier, activation/deactivation dates, per-Study staff delegation, per-Study Systems, per-Study contracts) lives here. StudySite is the entry point for Participants enrolled at this Site for this Study and Visit-occurrences happening at this Site for this Study. The operational hierarchy runs Site -> StudySite -> Study -> Protocol -> SOA -> Visit -> Activity. StudySite is reached by the operator clicking 'this Site on this Study' and is where most of the day-to-day workflow lives." ;
    sh:property [
        sh:path topc:studySiteId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier for this Site-Study pair." ;
        sh:name "studySiteId" ;
    ] ;
    sh:property [
        sh:path topc:siteNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Site number assigned by the sponsor for this Study. Distinct from Site.siteId (which is global)." ;
        sh:name "siteNumber" ;
    ] ;
    sh:property [
        sh:path topc:studySiteStatus ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "PLANNED" "IN_QUALIFICATION" "ACTIVE" "ON_HOLD" "CLOSED" ) ;
        sh:description "Per-Study lifecycle state. R3 Section 2.6 grounds the termination/suspension framing." ;
        sh:name "studySiteStatus" ;
    ] ;
    sh:property [
        sh:path topc:activationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the Site was activated for this Study (Site Initiation Visit completed; first patient can be enrolled)." ;
        sh:name "activationDate" ;
    ] ;
    sh:property [
        sh:path topc:deactivationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date the Site was deactivated for this Study (close-out completed). Empty for currently-active StudySite records." ;
        sh:name "deactivationDate" ;
    ] ;
    sh:property [
        sh:path topc:enrollmentTarget ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Target number of Participants for this Site for this Study." ;
        sh:name "enrollmentTarget" ;
    ] ;
    sh:property [
        sh:path topc:actualEnrollment ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Current count of Participants enrolled at this Site for this Study. Could be a derived attribute via SPARQL aggregation; surfaced as a stored attribute for operational view speed." ;
        sh:name "actualEnrollment" ;
    ] ;
    sh:property [
        sh:path topc:lastMonitoringVisitDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastMonitoringVisitDate" ;
    ] ;
    sh:property [
        sh:path topc:oversightTier ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "LOW" "MEDIUM" "HIGH" ) ;
        sh:description "R3 Section 3.10 risk-based monitoring tier for this Site on this Study. Drives Sponsor monitoring frequency and Audit prioritization. The same Site can be HIGH on a complex oncology trial and LOW on a simple registry — hence per-StudySite, not per-Site." ;
        sh:name "oversightTier" ;
    ] ;
    sh:property [
        sh:path topc:recruitmentPotential ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Per-protocol estimate of recruitable Participants. Distinct from Site.totalTrialsConducted (lifetime) and Site.averageEnrollmentRate (cross-trial). R3 Section 2.2 grounds the requirement." ;
        sh:name "recruitmentPotential" ;
    ] ;
    sh:property [
        sh:path topc:staffingAdequate ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Per-protocol staffing adequacy. R3 Section 2.2 — adequacy depends on protocol complexity, so per-StudySite." ;
        sh:name "staffingAdequate" ;
    ] ;
    sh:property [
        sh:path topc:essentialRecordsRetentionUntil ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date until which essential records for this StudySite must be retained. Per-Study because retention requirements vary by jurisdiction x Study type." ;
        sh:name "essentialRecordsRetentionUntil" ;
    ] ;
    sh:property [
        sh:path topc:irbApprovalStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "PENDING" "APPROVED" "WITH_CONDITIONS" "EXPIRED" "WITHDRAWN" "DENIED" ) ;
        sh:description "Current IRB approval status for this StudySite." ;
        sh:name "irbApprovalStatus" ;
    ] ;
    sh:property [
        sh:path topc:irbApprovalDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "irbApprovalDate" ;
    ] ;
    sh:property [
        sh:path topc:regulatoryAuthorizationStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "NOT_REQUIRED" "PENDING" "APPROVED" "EXPIRED" "WITHDRAWN" "DENIED" ) ;
        sh:description "Per-Study regulatory authorization status (FDA, EMA, etc., per jurisdiction)." ;
        sh:name "regulatoryAuthorizationStatus" ;
    ] ;
    sh:property [
        sh:path topc:validFrom ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Datetime from which this StudySite participation is operationally valid. Mirrors Sponsor's NGSI-LD-native temporal property for relationship handoffs (e.g., when a Site changes Principal Investigator mid-study)." ;
        sh:name "validFrom" ;
    ] ;
    sh:property [
        sh:path topc:validUntil ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "validUntil" ;
    ] ;
    sh:property [
        sh:path topc:forSite ;
        sh:class top:Site ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Site this StudySite participation belongs to." ;
        sh:name "forSite" ;
    ] ;
    sh:property [
        sh:path topc:forStudy ;
        sh:class top:Study ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this Site is participating in." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path topc:hasPrincipalInvestigator ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The per-Study Principal Investigator at this Site. R3 Section 2.1 GCP-required. The Person must be drawn from forSite.hasStaff (SHACL invariant). Temporal handoffs use validFrom/validUntil on the relationship." ;
        sh:name "hasPrincipalInvestigator" ;
    ] ;
    sh:property [
        sh:path topc:delegatesAuthorityTo ;
        sh:class topc:Person ;
        sh:minCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study Delegation of Authority Log roster. R3 Section 2.3.3 GCP-required. Each delegation carries role (sub-investigator, study coordinator, regulatory coordinator, pharmacist, etc.), scope, validFrom, validUntil. Each Person must be drawn from forSite.hasStaff." ;
        sh:name "delegatesAuthorityTo" ;
    ] ;
    sh:property [
        sh:path topc:hasIRB ;
        sh:class top:OversightBody ;
        sh:minCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study IRB(s)/IEC(s) of record for this StudySite. 1..N for multi-IRB cases." ;
        sh:name "hasIRB" ;
    ] ;
    sh:property [
        sh:path topc:submitsTo ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-jurisdiction regulatory authorities this StudySite files with. R3 Section 2.3.5 inspection right anchored here." ;
        rdfs:comment "TARGET-MISSING: RegulatoryAuthority is a flagged-missing horizontal in OOUX v0.2." ;
        sh:name "submitsTo" ;
    ] ;
    sh:property [
        sh:path topc:hasContract ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site-level contracts for this StudySite participation: CTA, CDA, MTA, DTA, etc." ;
        rdfs:comment "TARGET-MISSING: Contract is a flagged-missing horizontal in OOUX v0.2." ;
        sh:name "hasContract" ;
    ] ;
    sh:property [
        sh:path topc:hasStudyStartupPackage ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study Startup Package distributed to this Site for this Study." ;
        rdfs:comment "TARGET-MISSING: StudyStartupPackage is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasStudyStartupPackage" ;
    ] ;
    sh:property [
        sh:path topc:hostsVisit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Visit-occurrences at this Site for this Study. Inverse of Visit.atStudySite (when Visit lifts)." ;
        rdfs:comment "TARGET-MISSING: Visit is a top-level in OOUX (one of the locked 8) but not yet in TOP source." ;
        sh:name "hostsVisit" ;
    ] ;
    sh:property [
        sh:path topc:hasParticipant ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Participants enrolled at this Site for this Study. The OOUX Participant->Site direct edge is a correction: should be Participant->StudySite when Participant lifts." ;
        rdfs:comment "TARGET-MISSING: Participant is a top-level in OOUX (one of the locked 8) but not yet in TOP source." ;
        sh:name "hasParticipant" ;
    ] ;
    sh:property [
        sh:path topc:hasMonitoringVisit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study monitoring visits at this Site." ;
        rdfs:comment "TARGET-MISSING: MonitoringVisit is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasMonitoringVisit" ;
    ] ;
    sh:property [
        sh:path topc:hasAudit ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study audits at this Site." ;
        rdfs:comment "TARGET-MISSING: Audit is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasAudit" ;
    ] ;
    sh:property [
        sh:path topc:engagesVendor ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study Site-engaged vendors (R3 Section 2.3.4 documented agreements). Sponsor-engaged vendors are different — those are the Sponsor's; this is the Site's per-Study direct engagement (after R3 Section 2.3 Investigator's right of acceptance/refusal)." ;
        sh:name "engagesVendor" ;
    ] ;
    sh:property [
        sh:path topc:usesLaboratory ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study Site-engaged laboratories (Organization with organizationType=LABORATORY). Distinct from Site.usesSystem for the Site's own internal lab system." ;
        sh:name "usesLaboratory" ;
    ] ;
    sh:property [
        sh:path topc:usesSystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study Systems used at this Site participation: Sponsor-provisioned EDC, per-Study IRT, per-Study eTMF, etc. The System's three-axis pattern (operatedBy / usedBy / oversightHeldBy) on the System entity captures the GCP responsibility allocation." ;
        sh:name "usesSystem" ;
    ] ;
    sh:property [
        sh:path topc:reportsTo ;
        sh:class top:Sponsor ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study reporting target (R3 Sections 2.7, 2.13: safety, data, noncompliance reporting from Site to Sponsor)." ;
        sh:name "reportsTo" ;
    ] ;
    sh:property [
        sh:path topc:hasEssentialRecord ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Per-Study essential records (with Document.essentialRecordPurpose set per R3 Appendix C and Document.responsibleParty=INVESTIGATOR_INSTITUTION). Distinct from Site.hasInstitutionalDocument (study-independent institutional records)." ;
        sh:name "hasEssentialRecord" ;
    ] ;
    sh:property [
        sh:path topc:hasEnrollment ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Enrollment records at this StudySite." ;
        rdfs:comment "TARGET-MISSING: Enrollment is in OOUX v0.2 catalog but not yet in TOP source." ;
        sh:name "hasEnrollment" ;
    ] .

topc:Person rdfs:subClassOf prov:Agent .

topc:PersonShape a sh:NodeShape ;
    sh:targetClass topc:Person ;
    sh:name "Person" ;
    sh:description "Person represents any individual involved in trial conduct: principal investigators, sub-investigators, study coordinators, regulatory coordinators, pharmacists, study nurses, lab techs, monitors, sponsor staff, regulators, IRB members. Persons exist independently of any specific Site or Study; their roles in specific contexts are captured via relationship targets and PersonRole assignments. USDM models AssignedPerson as nested inside StudyRole — TOP makes Person a horizontal so it can be referenced consistently across the operational chain (Site.hasStaff, StudySite.hasPrincipalInvestigator, StudySite.delegatesAuthorityTo, Sponsor.employs, OversightBody.members, Audit.auditors)." ;
    sh:property [
        sh:path topc:personId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Globally unique NGSI-LD identifier." ;
        sh:name "personId" ;
    ] ;
    sh:property [
        sh:path topc:firstName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "firstName" ;
    ] ;
    sh:property [
        sh:path topc:middleName ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "middleName" ;
    ] ;
    sh:property [
        sh:path topc:lastName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "lastName" ;
    ] ;
    sh:property [
        sh:path topc:title ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Professional title (Dr., Prof., MD, RN, PharmD, etc.)." ;
        sh:name "title" ;
    ] ;
    sh:property [
        sh:path topc:jobTitle ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "USDM AssignedPerson.jobTitle. The functional role at the employing Organization (e.g., Director of Clinical Research, Senior CRA, Study Coordinator)." ;
        sh:name "jobTitle" ;
    ] ;
    sh:property [
        sh:path topc:email ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "email" ;
    ] ;
    sh:property [
        sh:path topc:phone ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "phone" ;
    ] ;
    sh:property [
        sh:path topc:orcid ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "ORCID identifier." ;
        sh:name "orcid" ;
    ] ;
    sh:property [
        sh:path topc:npi ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "US National Provider Identifier (where applicable)." ;
        sh:name "npi" ;
    ] ;
    sh:property [
        sh:path topc:preferredLanguages ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Comma-separated ISO 639-1 codes." ;
        sh:name "preferredLanguages" ;
    ] ;
    sh:property [
        sh:path topc:gcpTrainingStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "CURRENT" "EXPIRED" "NOT_REQUIRED" ) ;
        sh:description "GCP training currency. Per-Person fact independent of any Site or Study." ;
        sh:name "gcpTrainingStatus" ;
    ] ;
    sh:property [
        sh:path topc:gcpTrainingExpiry ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "gcpTrainingExpiry" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "INACTIVE" "RETIRED" "DECEASED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:employedBy ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Primary employing Organization. USDM AssignedPerson.organizationId. A Person can have multiple StudySite/Sponsor relationships independent of where they're employed." ;
        sh:name "employedBy" ;
    ] ;
    sh:property [
        sh:path topc:hasCredential ;
        sh:class topc:Credential ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Credentials this Person holds (medical license, specialty board certifications, GCP certificate, etc.)." ;
        sh:name "hasCredential" ;
    ] ;
    sh:property [
        sh:path topc:hasTrainingRecord ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Training records for this Person (study-specific GCP, study-specific protocol training, etc.)." ;
        rdfs:comment "TARGET-MISSING: TrainingRecord is in OOUX v0.2 (#77) but not yet in TOP source." ;
        sh:name "hasTrainingRecord" ;
    ] .

topc:System rdfs:subClassOf prov:Entity .

topc:SystemShape a sh:NodeShape ;
    sh:targetClass topc:System ;
    sh:name "System" ;
    sh:description "System represents a computerised system used in trial conduct: EDC, CTMS, eTMF/ISF, IRT/RTSM, safety database, EMR, ELN, eCOA/ePRO platform, eConsent platform, sample management system, etc. Systems carry their R3 Section 4.3 governance attributes (validation, security, access controls, release status, contingency plans, support) and a three-axis role pattern (operatedBy / usedBy / oversightHeldBy) that captures GCP responsibility allocation." ;
    sh:property [
        sh:path topc:systemId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "systemId" ;
    ] ;
    sh:property [
        sh:path topc:vendor ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Vendor name (e.g., 'Veeva', 'Medidata', 'Oracle', 'Castor')." ;
        sh:name "vendor" ;
    ] ;
    sh:property [
        sh:path topc:productName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Product name (e.g., 'Vault eTMF', 'Rave EDC', 'Siebel CTMS', 'CDMS')." ;
        sh:name "productName" ;
    ] ;
    sh:property [
        sh:path topc:instanceId ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Tenant or instance identifier (e.g., 'mskcc-prod', 'pfizer-tenant-7')." ;
        sh:name "instanceId" ;
    ] ;
    sh:property [
        sh:path topc:baseUrl ;
        sh:datatype xsd:anyURI ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Base URL for the System instance. May be unpopulated when the consumer of the System record does not have access to the instance details (R3 Section 3.9 oversight-as-relationship framing)." ;
        sh:name "baseUrl" ;
    ] ;
    sh:property [
        sh:path topc:systemType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "EDC" "CTMS" "ETMF" "ISF" "IRT" "RTSM" "SAFETY_DB" "EMR" "ELN" "ECOA" "EPRO" "ECONSENT" "SAMPLE_TRACKING" "LABORATORY_INFORMATION" "PHARMACY_MANAGEMENT" "OTHER" ) ;
        sh:description "Functional system type." ;
        sh:name "systemType" ;
    ] ;
    sh:property [
        sh:path topc:version ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "version" ;
    ] ;
    sh:property [
        sh:path topc:validatedFor21CfrPart11 ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "FDA 21 CFR Part 11 validation status." ;
        sh:name "validatedFor21CfrPart11" ;
    ] ;
    sh:property [
        sh:path topc:validatedForEuAnnex11 ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "EU EudraLex Annex 11 validation status." ;
        sh:name "validatedForEuAnnex11" ;
    ] ;
    sh:property [
        sh:path topc:validationStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "NOT_VALIDATED" "IN_VALIDATION" "VALIDATED" "REVALIDATING" "RETIRED" ) ;
        sh:description "R3 Section 4.3.4 validation lifecycle state." ;
        sh:name "validationStatus" ;
    ] ;
    sh:property [
        sh:path topc:validationApproach ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "RISK_BASED" "FULL_VALIDATION" "VENDOR_QUALIFICATION_ONLY" ) ;
        sh:description "R3 Section 4.3.4 risk-based validation approach." ;
        sh:name "validationApproach" ;
    ] ;
    sh:property [
        sh:path topc:lastValidationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastValidationDate" ;
    ] ;
    sh:property [
        sh:path topc:releaseStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "DEVELOPMENT" "UAT" "PRODUCTION" "REVALIDATING" "RETIRED" ) ;
        sh:description "R3 Section 4.3.5 release lifecycle." ;
        sh:name "releaseStatus" ;
    ] ;
    sh:property [
        sh:path topc:auditTrailEnabled ;
        sh:datatype xsd:boolean ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "R3 Section 4.2.2 audit-trail capability." ;
        sh:name "auditTrailEnabled" ;
    ] ;
    sh:property [
        sh:path topc:operatedBy ;
        sh:class topc:Organization ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization that operates this System and is responsible for its R3 Section 4.3 obligations (validation, security, access controls, release, contingency, support). A Sponsor's EDC has operatedBy -> Pfizer (or Pfizer's CRO under documented agreement); a Site's local source-document repository has operatedBy -> MSKCC." ;
        sh:name "operatedBy" ;
    ] ;
    sh:property [
        sh:path topc:usedBy ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Organizations that use this System for trial activities. A Sponsor-operated EDC is used by both Sponsor staff and authorized Site staff; the same System has multiple usedBy triples." ;
        sh:name "usedBy" ;
    ] ;
    sh:property [
        sh:path topc:oversightHeldBy ;
        sh:class topc:Organization ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization holding GCP-mandated oversight responsibility for this System's use in trial conduct. R3 Section 3.9 (Sponsor for trial-critical Sponsor-delegated Systems) or Section 2.12 (Site for Site-internal records). For sponsor-critical Systems (EDC, CTMS, IRT, SAFETY_DB), oversightHeldBy must be a Sponsor (SHACL invariant); the IIT case where Site=Sponsor exempts via the Site.belongsToOrganization == Sponsor.belongsToOrganization equality test." ;
        sh:name "oversightHeldBy" ;
    ] ;
    sh:property [
        sh:path topc:proceduresDocumentRef ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "R3 Section 4.3.1 procedures document." ;
        sh:name "proceduresDocumentRef" ;
    ] ;
    sh:property [
        sh:path topc:securityAssessmentRef ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "R3 Section 4.3.3 security assessment." ;
        sh:name "securityAssessmentRef" ;
    ] ;
    sh:property [
        sh:path topc:accessControlPolicyRef ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "R3 Section 4.3.8 access control policy." ;
        sh:name "accessControlPolicyRef" ;
    ] ;
    sh:property [
        sh:path topc:contingencyPlanRef ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "R3 Section 4.3.6 contingency / disaster-recovery plan." ;
        sh:name "contingencyPlanRef" ;
    ] ;
    sh:property [
        sh:path topc:supportContact ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "R3 Section 4.3.7 technical support point of contact." ;
        sh:name "supportContact" ;
    ] .

topc:Log rdfs:subClassOf prov:Activity .

topc:LogShape a sh:NodeShape ;
    sh:targetClass topc:Log ;
    sh:name "Log" ;
    sh:description "Log represents records of events, actions, or observations that occur during trial conduct. Logs are append-only by GCP discipline (R3 Section 4.2.2) — entries cannot be retroactively edited. Logs are distinct from Documents in that they have lifecycle behavior (entry, review, sign-off) and are inherently temporal sequences. Covers Subject Logs (ISF section 13), IP Accountability Logs (14.3), IP Storage Temperature Logs (14.5), Site Visit Logs (16.1), Sample Handling Logs (18.1), SAE Tracking Logs (19.1), Communication Logs (22), and others." ;
    sh:property [
        sh:path topc:logId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "logId" ;
    ] ;
    sh:property [
        sh:path topc:logType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "SUBJECT_SCREENING" "SUBJECT_ENROLLMENT" "SUBJECT_IDENTIFICATION" "SUBJECT_VISIT_TRACKING" "IP_DISPENSING" "IP_ACCOUNTABILITY" "IP_TEMPERATURE" "IP_STORAGE_TEMPERATURE" "SITE_VISIT" "MONITORING_VISIT" "SAMPLE_HANDLING" "SAMPLE_SHIPPING" "SAE_TRACKING" "COMMUNICATION" "TRAINING" "AUDIT_TRAIL" "DELEGATION" "TEMPERATURE_DEVIATION" "OTHER" ) ;
        sh:description "The log's discriminator. CommunicationLog folds in as COMMUNICATION; AuditTrail folds in as AUDIT_TRAIL." ;
        sh:name "logType" ;
    ] ;
    sh:property [
        sh:path topc:logTitle ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Human-readable title of the log (e.g., 'IP Storage Temperature — Freezer F-12 — 2026 Q1')." ;
        sh:name "logTitle" ;
    ] ;
    sh:property [
        sh:path topc:openedDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "openedDate" ;
    ] ;
    sh:property [
        sh:path topc:closedDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Empty for currently-active logs." ;
        sh:name "closedDate" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "CLOSED" "ARCHIVED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:entryCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Count of entries in this log; aggregate signal." ;
        sh:name "entryCount" ;
    ] ;
    sh:property [
        sh:path topc:retentionUntil ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Date until which this log must be retained per regulatory/contractual requirements." ;
        sh:name "retentionUntil" ;
    ] ;
    sh:property [
        sh:path topc:forStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySite this log belongs to. Most operational logs are per-StudySite. Empty for Site-wide logs (e.g., institutional audit trail) which use forSite instead." ;
        sh:name "forStudySite" ;
    ] ;
    sh:property [
        sh:path topc:forSite ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Site this log belongs to, when the log is study-independent (e.g., institutional GCP training log)." ;
        sh:name "forSite" ;
    ] ;
    sh:property [
        sh:path topc:forStudy ;
        sh:class top:Study ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Study this log belongs to, when the log is Sponsor-side and not Site-scoped." ;
        sh:name "forStudy" ;
    ] ;
    sh:property [
        sh:path topc:forSystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The System this log records (e.g., audit-trail logs are System-scoped under R3 Section 4.2.2)." ;
        sh:name "forSystem" ;
    ] ;
    sh:property [
        sh:path topc:owner ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person responsible for maintaining this log." ;
        sh:name "owner" ;
    ] ;
    sh:property [
        sh:path topc:correspondent ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "For logType=COMMUNICATION: the parties this correspondence log captures exchanges with (Sponsor, IRB, RegulatoryAuthority, central lab, vendor). 0..N because some correspondence logs aggregate across multiple parties." ;
        sh:name "correspondent" ;
    ] ;
    sh:property [
        sh:path topc:forEquipment ;
        sh:class topc:Equipment ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Equipment this log is about (e.g., a temperature log for freezer F-12). Distinct from forSystem which captures the monitoring System; logs about Equipment with IoT monitoring will populate both forEquipment and forSystem." ;
        sh:name "forEquipment" ;
    ] .

topc:Equipment rdfs:subClassOf prov:Entity .

topc:EquipmentShape a sh:NodeShape ;
    sh:targetClass topc:Equipment ;
    sh:name "Equipment" ;
    sh:description "Equipment represents physical devices used in clinical-trial conduct: refrigerators, freezers, centrifuges, scales, ECG machines, ultrasound, blood pressure devices, glucometers, pulse oximeters, etc. Equipment is distinct from System (computerised systems): a freezer with IoT temperature monitoring is Equipment + a System (the monitoring platform), linked via Equipment.monitoredBySystem. Under R3, Equipment carries qualification, calibration, maintenance, and training discipline; computerised-systems aspects (validation, audit trails, access controls per R3 Section 4.3) live on the linked System." ;
    sh:property [
        sh:path topc:equipmentId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "equipmentId" ;
    ] ;
    sh:property [
        sh:path topc:equipmentName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name (e.g., 'Freezer F-12 Pharmacy', 'Eppendorf 5810R Centrifuge')." ;
        sh:name "equipmentName" ;
    ] ;
    sh:property [
        sh:path topc:equipmentClass ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "FREEZER" "REFRIGERATOR" "CENTRIFUGE" "SCALE" "ECG_MACHINE" "ULTRASOUND" "BP_DEVICE" "THERMOMETER" "INCUBATOR" "AUTOCLAVE" "MICROSCOPE" "PIPETTE" "PH_METER" "SPIROMETER" "GLUCOMETER" "PULSE_OXIMETER" "WEARABLE" "INFUSION_PUMP" "COMPUTER" "PRINTER" "OTHER" ) ;
        sh:description "Equipment class taxonomy. Covers most clinical-trial Equipment classes including participant-loaned wearables." ;
        sh:name "equipmentClass" ;
    ] ;
    sh:property [
        sh:path topc:equipmentBinding ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "SITE_BOUND" "VENDOR_OWNED" "PARTICIPANT_LOANED" ) ;
        sh:description "OOUX-locked binding pattern: SITE_BOUND for institution-owned equipment at a Site; VENDOR_OWNED for sponsor- or vendor-supplied equipment used at a Site; PARTICIPANT_LOANED for equipment loaned to a participant (wearables, BP cuff, glucometer)." ;
        sh:name "equipmentBinding" ;
    ] ;
    sh:property [
        sh:path topc:manufacturer ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "manufacturer" ;
    ] ;
    sh:property [
        sh:path topc:model ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "model" ;
    ] ;
    sh:property [
        sh:path topc:serialNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "serialNumber" ;
    ] ;
    sh:property [
        sh:path topc:purchaseDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "purchaseDate" ;
    ] ;
    sh:property [
        sh:path topc:installationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "installationDate" ;
    ] ;
    sh:property [
        sh:path topc:lastCalibrationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastCalibrationDate" ;
    ] ;
    sh:property [
        sh:path topc:nextCalibrationDueDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "nextCalibrationDueDate" ;
    ] ;
    sh:property [
        sh:path topc:calibrationFrequency ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "DAILY" "WEEKLY" "MONTHLY" "QUARTERLY" "SEMIANNUAL" "ANNUAL" "BIANNUAL" "ON_USE" "NOT_REQUIRED" ) ;
        sh:name "calibrationFrequency" ;
    ] ;
    sh:property [
        sh:path topc:lastMaintenanceDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastMaintenanceDate" ;
    ] ;
    sh:property [
        sh:path topc:maintenanceSchedule ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "DAILY" "WEEKLY" "MONTHLY" "QUARTERLY" "SEMIANNUAL" "ANNUAL" "ON_DEMAND" "NOT_REQUIRED" ) ;
        sh:name "maintenanceSchedule" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "OPERATIONAL" "UNDER_MAINTENANCE" "OUT_OF_SERVICE" "RETIRED" "QUARANTINED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:locationDescription ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Free-text location (e.g., 'Pharmacy Room 3, second shelf'). For structured location, use atStorageLocation relationship via the StorageLocation horizontal." ;
        sh:name "locationDescription" ;
    ] ;
    sh:property [
        sh:path topc:temperatureRange ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "For temperature-controlled Equipment: {minTempC, maxTempC, units}. Distinct from monitored temperature readings, which live in Log entries." ;
        sh:name "temperatureRange" ;
    ] ;
    sh:property [
        sh:path topc:atSite ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Site where this Equipment is located. Inverse of Site.hostsEquipment. 0..1 because VENDOR_OWNED equipment may move between sites; PARTICIPANT_LOANED equipment is at a Participant." ;
        sh:name "atSite" ;
    ] ;
    sh:property [
        sh:path topc:atStudySite ;
        sh:class topc:StudySite ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "The StudySites this Equipment is dedicated to. Empty for Site-shared Equipment (a general-purpose centrifuge); populated for per-Study dedicated Equipment (a study-specific freezer)." ;
        sh:name "atStudySite" ;
    ] ;
    sh:property [
        sh:path topc:vendor ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization that manufactured or supplies this Equipment. Organization with organizationType=VENDOR." ;
        sh:name "vendor" ;
    ] ;
    sh:property [
        sh:path topc:monitoredBySystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Computerised System(s) that monitor or control this Equipment. A pharmacy-grade freezer with cloud-based temperature logging has monitoredBySystem pointing to the IoT temperature monitor System. The Equipment-System split keeps R3 Section 4.3 obligations on the System and Equipment-qualification obligations on the Equipment." ;
        sh:name "monitoredBySystem" ;
    ] ;
    sh:property [
        sh:path topc:responsiblePerson ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Person responsible for maintaining/operating this Equipment." ;
        sh:name "responsiblePerson" ;
    ] ;
    sh:property [
        sh:path topc:hasCredential ;
        sh:class topc:Credential ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Equipment-level credentials: calibration certificates, validation certificates, vendor certifications." ;
        sh:name "hasCredential" ;
    ] ;
    sh:property [
        sh:path topc:hasUserManual ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "User manual document." ;
        sh:name "hasUserManual" ;
    ] .

topc:StorageLocation rdfs:subClassOf prov:Entity .

topc:StorageLocationShape a sh:NodeShape ;
    sh:targetClass topc:StorageLocation ;
    sh:name "StorageLocation" ;
    sh:description "StorageLocation represents an addressable storage point: a slot in a freezer rack, a shelf in a refrigerator, a position in a sample matrix, a drawer in a secure cabinet, an offsite vault location. StorageLocation is distinct from Equipment: an Equipment freezer provides multiple StorageLocations (its slots/shelves/racks); a secure cabinet (Equipment) provides drawer StorageLocations. Some StorageLocations are not inside Equipment (an ambient-temperature shelf in a controlled-access pharmacy room)." ;
    sh:property [
        sh:path topc:storageLocationId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "storageLocationId" ;
    ] ;
    sh:property [
        sh:path topc:locationName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Display name (e.g., 'Freezer F-12, Rack 3, Slot A4')." ;
        sh:name "locationName" ;
    ] ;
    sh:property [
        sh:path topc:storageType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "FREEZER_SLOT" "REFRIGERATOR_SHELF" "AMBIENT_SHELF" "SECURE_CABINET_DRAWER" "OFFSITE_VAULT" "PHARMACY_SHELF" "BIOREPOSITORY_RACK" "OTHER" ) ;
        sh:name "storageType" ;
    ] ;
    sh:property [
        sh:path topc:temperatureRangeC ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "MINUS_80" "MINUS_20" "FOUR" "FIFTEEN" "ROOM_TEMP" "AMBIENT" "OTHER" ) ;
        sh:description "Standard temperature ranges in Celsius. For non-standard ranges document via the linked Equipment.temperatureRange." ;
        sh:name "temperatureRangeC" ;
    ] ;
    sh:property [
        sh:path topc:capacity ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Total capacity (count or volume)." ;
        sh:name "capacity" ;
    ] ;
    sh:property [
        sh:path topc:capacityUnit ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Unit of capacity (slots, mL, etc.)." ;
        sh:name "capacityUnit" ;
    ] ;
    sh:property [
        sh:path topc:currentOccupancy ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Current count of items stored." ;
        sh:name "currentOccupancy" ;
    ] ;
    sh:property [
        sh:path topc:accessRestriction ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "UNRESTRICTED" "AUTHORIZED_ONLY" "PI_ONLY" "PHARMACY_ONLY" "OTHER" ) ;
        sh:name "accessRestriction" ;
    ] ;
    sh:property [
        sh:path topc:securityLevel ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "LOW" "MEDIUM" "HIGH" ) ;
        sh:name "securityLevel" ;
    ] ;
    sh:property [
        sh:path topc:gpsCoordinates ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Lat/long for offsite vaults (e.g., '40.7128,-74.0060')." ;
        sh:name "gpsCoordinates" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "DECOMMISSIONED" "QUARANTINED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:atSite ;
        sh:class top:Site ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Site this StorageLocation belongs to. Inverse of Site.hasStorageLocation." ;
        sh:name "atSite" ;
    ] ;
    sh:property [
        sh:path topc:inEquipment ;
        sh:class topc:Equipment ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Equipment this StorageLocation is housed in (when applicable). A slot in freezer F-12 has inEquipment pointing to Freezer F-12; an ambient pharmacy shelf has no inEquipment." ;
        sh:name "inEquipment" ;
    ] ;
    sh:property [
        sh:path topc:monitoredBySystem ;
        sh:class topc:System ;
        sh:minCount 0 ;
        sh:nodeKind sh:IRI ;
        sh:description "Computerised systems that monitor this StorageLocation (inventory tracking, slot-level temperature monitoring)." ;
        sh:name "monitoredBySystem" ;
    ] .

topc:Credential rdfs:subClassOf prov:Entity .

topc:CredentialShape a sh:NodeShape ;
    sh:targetClass topc:Credential ;
    sh:name "Credential" ;
    sh:description "Credential represents a qualification, certification, license, or accreditation. Credentials apply polymorphically to Persons (medical licenses, GCP certificates, board certifications), Sites (CLIA certification, JCAHO accreditation, ISO 9001), and Equipment (calibration certificates, validation certificates). Each Credential carries an issuing authority, an issue/expiry window, and a verification status, plus an optional reference to the certificate Document." ;
    sh:property [
        sh:path topc:credentialId ;
        sh:datatype xsd:anyURI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "credentialId" ;
    ] ;
    sh:property [
        sh:path topc:credentialType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "MEDICAL_LICENSE" "BOARD_CERTIFICATION" "GCP_CERTIFICATE" "CITI_CERTIFICATE" "ACLS" "BLS" "CV" "CLIA_CERTIFICATION" "CAP_CERTIFICATION" "JCAHO_ACCREDITATION" "ISO_9001" "ISO_15189" "CALIBRATION_CERTIFICATE" "VALIDATION_CERTIFICATE" "VENDOR_CERTIFICATION" "INSTITUTIONAL_LICENSE" "OTHER" ) ;
        sh:name "credentialType" ;
    ] ;
    sh:property [
        sh:path topc:credentialNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "Identifier issued by the issuing authority." ;
        sh:name "credentialNumber" ;
    ] ;
    sh:property [
        sh:path topc:issuingAuthorityName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:description "Free-text name when the issuing authority is not modeled as an Organization (e.g., 'Texas Medical Board'); use issuingAuthorityOrg relationship for modeled authorities." ;
        sh:name "issuingAuthorityName" ;
    ] ;
    sh:property [
        sh:path topc:issueDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "issueDate" ;
    ] ;
    sh:property [
        sh:path topc:expirationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "expirationDate" ;
    ] ;
    sh:property [
        sh:path topc:status ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "ACTIVE" "PENDING_VERIFICATION" "EXPIRED" "REVOKED" "SUSPENDED" ) ;
        sh:description "Lifecycle state." ;
        sh:name "status" ;
    ] ;
    sh:property [
        sh:path topc:verificationStatus ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "NOT_VERIFIED" "IN_PROGRESS" "VERIFIED" "FAILED_VERIFICATION" ) ;
        sh:name "verificationStatus" ;
    ] ;
    sh:property [
        sh:path topc:verificationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "verificationDate" ;
    ] ;
    sh:property [
        sh:path topc:lastReviewDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "lastReviewDate" ;
    ] ;
    sh:property [
        sh:path topc:nextReviewDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:name "nextReviewDate" ;
    ] ;
    sh:property [
        sh:path topc:forPerson ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Person this Credential is held by (medical license, GCP certificate, etc.). Mutually exclusive with forSite and forEquipment in operational practice; SHACL invariant enforces exactly-one-target." ;
        sh:name "forPerson" ;
    ] ;
    sh:property [
        sh:path topc:forSite ;
        sh:class top:Site ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Site this Credential is held by (CLIA, JCAHO, etc.)." ;
        sh:name "forSite" ;
    ] ;
    sh:property [
        sh:path topc:forEquipment ;
        sh:class topc:Equipment ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Equipment this Credential is held by (calibration certificate, validation certificate)." ;
        sh:name "forEquipment" ;
    ] ;
    sh:property [
        sh:path topc:issuingAuthorityOrg ;
        sh:class topc:Organization ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The Organization issuing this Credential, when modeled (e.g., American Board of Internal Medicine). Use when the issuing authority is also referenced elsewhere; otherwise use issuingAuthorityName free-text." ;
        sh:name "issuingAuthorityOrg" ;
    ] ;
    sh:property [
        sh:path topc:verifiedBy ;
        sh:class topc:Person ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "Person who verified this Credential." ;
        sh:name "verifiedBy" ;
    ] ;
    sh:property [
        sh:path topc:referenceDocument ;
        sh:class topc:Document ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:IRI ;
        sh:description "The certificate / license / attestation document file." ;
        sh:name "referenceDocument" ;
    ] .

# === Domain invariants (hand-maintained for strawman phase) ===
# Cross-property and cross-entity rules that property shapes cannot capture.
# Encoded as SHACL-SPARQL constraints (pyshacl supports SPARQLConstraint natively).
# v0.2 will introduce a structured 'invariants' field on each class for mechanical emission.

top:SponsorSoRImpliesRegRespShape a sh:NodeShape ;
    sh:targetClass top:Sponsor ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Sponsor flagged isSponsorOfRecord=true but hasRegulatoryResponsibility=false. Permitted under FDA 21 CFR 312.52 (transfer of obligations) but unusual; verify intent." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this top:isSponsorOfRecord true .
                $this top:hasRegulatoryResponsibility false .
            }
        """ ;
    ] .

top:StudyMustHaveSoRShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has no Sponsor flagged isSponsorOfRecord=true. At least one sponsor of record is required to carry regulatory accountability." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                FILTER NOT EXISTS {
                    ?sponsor top:runs $this .
                    ?sponsor top:isSponsorOfRecord true .
                }
            }
        """ ;
    ] .

top:OneSoRPerStudyPerJurisdictionShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has more than one Sponsor flagged isSponsorOfRecord=true under the same RegulatoryAuthority (or with no explicit scope). Exactly one SoR per (Study x jurisdiction) is required; Sponsors lacking regulatoryAuthorityScope are treated as the implicit single-jurisdiction bucket." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this ?ra WHERE {
                {
                    SELECT $this ?ra (COUNT(?sponsor) AS ?cnt) WHERE {
                        ?sponsor top:runs $this .
                        ?sponsor top:isSponsorOfRecord true .
                        OPTIONAL { ?sponsor top:regulatoryAuthorityScope ?ra }
                    }
                    GROUP BY $this ?ra
                }
                FILTER (?cnt > 1)
            }
        """ ;
    ] .

top:StudyMustHaveOperationalSponsorShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has no Sponsor with hasOperationalResponsibility=true. Day-to-day operational ownership must be assigned to at least one Sponsor entity." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                FILTER NOT EXISTS {
                    ?sponsor top:runs $this .
                    ?sponsor top:hasOperationalResponsibility true .
                }
            }
        """ ;
    ] .

# === Site / StudySite / System / Credential invariants (v0.2.0) ===

top:SiteDecentralizedHubNeedsOrgShape a sh:NodeShape ;
    sh:targetClass top:Site ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Site has siteType=DECENTRALIZED_HUB but no belongsToOrganization. A decentralized hub still requires a legal/operational Organization to anchor regulatory and contractual responsibility." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Site .
                $this top:siteType "DECENTRALIZED_HUB" .
                FILTER NOT EXISTS { $this top:belongsToOrganization ?org }
            }
        """ ;
    ] .

top:SiteNetworkOrgTypeShape a sh:NodeShape ;
    sh:targetClass top:Site ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Site.partOfSiteNetwork points at an Organization whose organizationType is not SITE_NETWORK or SMO. The relationship is intended for site-network or SMO Organizations only; pointing at other types is a modeling error." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this ?org ?orgType WHERE {
                $this top:partOfSiteNetwork ?org .
                ?org topc:organizationType ?orgType .
                FILTER (?orgType != "SITE_NETWORK" && ?orgType != "SMO")
            }
        """ ;
    ] .

topc:StudySiteActiveNeedsPIShape a sh:NodeShape ;
    sh:targetClass topc:StudySite ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "StudySite has studySiteStatus=ACTIVE but no hasPrincipalInvestigator. ICH E6(R3) Section 2.1 requires a qualified Principal Investigator for any active Site participation." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this WHERE {
                $this a topc:StudySite .
                $this topc:studySiteStatus "ACTIVE" .
                FILTER NOT EXISTS { $this topc:hasPrincipalInvestigator ?pi }
            }
        """ ;
    ] .

topc:StudySiteActiveNeedsDelegationShape a sh:NodeShape ;
    sh:targetClass topc:StudySite ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "StudySite has studySiteStatus=ACTIVE but no delegatesAuthorityTo entries. ICH E6(R3) Section 2.3.3 GCP-requires a Delegation of Authority Log for any active Site participation." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this WHERE {
                $this a topc:StudySite .
                $this topc:studySiteStatus "ACTIVE" .
                FILTER NOT EXISTS { $this topc:delegatesAuthorityTo ?p }
            }
        """ ;
    ] .

topc:SystemSponsorCriticalOversightShape a sh:NodeShape ;
    sh:targetClass topc:System ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "System has systemType in {EDC, CTMS, IRT, SAFETY_DB} (sponsor-critical) but oversightHeldBy points at an Organization with organizationType=SITE. ICH E6(R3) Section 3.9 places oversight on the Sponsor for sponsor-critical systems. IIT exception applies when the same Organization also plays a Sponsor role on a Study using this System (checked via playsSponsorRole)." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this ?org WHERE {
                $this a topc:System .
                $this topc:systemType ?type .
                FILTER (?type IN ("EDC", "CTMS", "IRT", "SAFETY_DB"))
                $this topc:oversightHeldBy ?org .
                ?org topc:organizationType "SITE" .
                FILTER NOT EXISTS {
                    ?org topc:playsSponsorRole ?sponsor .
                    ?studySite topc:usesSystem $this .
                    ?studySite topc:forStudy ?study .
                    ?sponsor top:runs ?study .
                }
            }
        """ ;
    ] .

topc:CredentialExactlyOneTargetShape a sh:NodeShape ;
    sh:targetClass topc:Credential ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Credential must have exactly one of forPerson, forSite, forEquipment populated. Polymorphic-target discipline requires single-target binding; a Credential cannot apply to multiple subject types." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this WHERE {
                $this a topc:Credential .
                {
                    SELECT $this (COUNT(?t) AS ?cnt) WHERE {
                        $this a topc:Credential .
                        OPTIONAL { $this topc:forPerson ?p . BIND(?p AS ?t) }
                        OPTIONAL { $this topc:forSite ?s . BIND(?s AS ?t) }
                        OPTIONAL { $this topc:forEquipment ?e . BIND(?e AS ?t) }
                    }
                    GROUP BY $this
                }
                FILTER (?cnt != 1)
            }
        """ ;
    ] .

# === Study invariants (v0.3.0) ===

top:StudyActiveNeedsEndpointShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has studyStatus in {RECRUITING, ENROLLING_BY_INVITATION, ACTIVE_NOT_RECRUITING, COMPLETED} but no hasEndpoint. A study at or past activation must have at least one defined endpoint to measure." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:studyStatus ?status .
                FILTER (?status IN ("RECRUITING", "ENROLLING_BY_INVITATION", "ACTIVE_NOT_RECRUITING", "COMPLETED"))
                FILTER NOT EXISTS { $this top:hasEndpoint ?ep }
            }
        """ ;
    ] .

top:StudyActiveNeedsInclusionCriterionShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has studyStatus in {RECRUITING, ENROLLING_BY_INVITATION, ACTIVE_NOT_RECRUITING, COMPLETED} but no hasInclusionCriterion. A study at or past recruitment must have at least one inclusion criterion to define eligibility." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:studyStatus ?status .
                FILTER (?status IN ("RECRUITING", "ENROLLING_BY_INVITATION", "ACTIVE_NOT_RECRUITING", "COMPLETED"))
                FILTER NOT EXISTS { $this top:hasInclusionCriterion ?ic }
            }
        """ ;
    ] .

top:StudyActiveNeedsScheduleShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has studyStatus in {RECRUITING, ENROLLING_BY_INVITATION, ACTIVE_NOT_RECRUITING, COMPLETED} but no hasSchedule (Schedule of Assessments). A study at or past recruitment must have a defined SOA to guide site operations." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:studyStatus ?status .
                FILTER (?status IN ("RECRUITING", "ENROLLING_BY_INVITATION", "ACTIVE_NOT_RECRUITING", "COMPLETED"))
                FILTER NOT EXISTS { $this top:hasSchedule ?soa }
            }
        """ ;
    ] .

top:InterventionalStudyNeedsPhaseShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has studyType=INTERVENTIONAL but no studyPhase populated. Interventional studies must declare their phase per ICH E8 / ClinicalTrials.gov. Observational, expanded-access, and registry studies are exempt." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:studyType "INTERVENTIONAL" .
                FILTER NOT EXISTS { $this top:studyPhase ?phase }
            }
        """ ;
    ] .

top:StudyCompletedNeedsCompletionDateShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has studyStatus=COMPLETED but no actualCompletionDate. Verify the completion date is recorded; this is typically last-patient-last-visit (LPLV)." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:studyStatus "COMPLETED" .
                FILTER NOT EXISTS { $this top:actualCompletionDate ?d }
            }
        """ ;
    ] .

top:MultiArmModelNeedsMultipleArmsShape a sh:NodeShape ;
    sh:targetClass top:Study ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Study has interventionModel in {PARALLEL, CROSSOVER, FACTORIAL, SEQUENTIAL} but only one hasArm. Multi-arm intervention models require multiple Arm sub-objects." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Study .
                $this top:interventionModel ?model .
                FILTER (?model IN ("PARALLEL", "CROSSOVER", "FACTORIAL", "SEQUENTIAL"))
                {
                    SELECT $this (COUNT(?arm) AS ?armCount) WHERE {
                        $this top:hasArm ?arm .
                    }
                    GROUP BY $this
                }
                FILTER (?armCount = 1)
            }
        """ ;
    ] .

# === Participant invariants (v0.4.0) ===

top:ParticipantOnTrialNeedsConsentShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant has participantStatus in {ENROLLED, RANDOMIZED, ON_TREATMENT, IN_FOLLOW_UP, COMPLETED, WITHDRAWN, DISCONTINUED, LOST_TO_FOLLOW_UP} but no InformedConsent sub-object with consentStatus=OBTAINED. ICH E6(R3) Section 2.4 GCP-required: cannot be on study without documented consent." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:participantStatus ?status .
                FILTER (?status IN ("ENROLLED", "RANDOMIZED", "ON_TREATMENT", "IN_FOLLOW_UP", "COMPLETED", "WITHDRAWN", "DISCONTINUED", "LOST_TO_FOLLOW_UP"))
                FILTER NOT EXISTS {
                    $this top:hasInformedConsent ?consent .
                    ?consent top:consentStatus "OBTAINED" .
                }
            }
        """ ;
    ] .

top:ParticipantScreenFailedNeedsRecordShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant has participantStatus=SCREEN_FAILED but no ScreeningRecord with screeningOutcome=SCREEN_FAILED. The status implies a recorded screening event with documented failure." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:participantStatus "SCREEN_FAILED" .
                FILTER NOT EXISTS {
                    $this top:hasScreeningRecord ?sr .
                    ?sr top:screeningOutcome "SCREEN_FAILED" .
                }
            }
        """ ;
    ] .

top:ParticipantRandomizedNeedsArmAndDateShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant has participantStatus=RANDOMIZED but is missing assignedToArm or randomizationDate (or both). Randomization is a discrete event requiring both an arm assignment and a date." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:participantStatus "RANDOMIZED" .
                FILTER (NOT EXISTS { $this top:assignedToArm ?arm } || NOT EXISTS { $this top:randomizationDate ?d })
            }
        """ ;
    ] .

top:ParticipantWithdrawnNeedsConsentWithdrawalShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant has participantStatus=WITHDRAWN but no WithdrawalRecord with withdrawalCategory=CONSENT_WITHDRAWN. The WITHDRAWN status is reserved for subject-initiated withdrawal of consent; investigator-initiated discontinuation should use participantStatus=DISCONTINUED instead." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:participantStatus "WITHDRAWN" .
                FILTER NOT EXISTS {
                    $this top:hasWithdrawalRecord ?wr .
                    ?wr top:withdrawalCategory "CONSENT_WITHDRAWN" .
                }
            }
        """ ;
    ] .

top:ParticipantCompletedNeedsCompletionDateShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant has participantStatus=COMPLETED but no completionDate populated. Verify the completion date is recorded; this is the Participant-level last-visit date." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:participantStatus "COMPLETED" .
                FILTER NOT EXISTS { $this top:completionDate ?d }
            }
        """ ;
    ] .

top:ParticipantArmStudyConsistencyShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant.assignedToArm references an Arm that does not belong to Participant.forStudy. The Arm must come from this Study's design, not from another Study." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:assignedToArm ?arm .
                $this top:forStudy ?participantStudy .
                FILTER NOT EXISTS {
                    ?armStudy a top:Study .
                    ?armStudy top:hasArm ?arm .
                    FILTER (?armStudy = ?participantStudy)
                }
            }
        """ ;
    ] .

top:ParticipantStudySiteStudyConsistencyShape a sh:NodeShape ;
    sh:targetClass top:Participant ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Participant.forStudySite references a StudySite whose forStudy does not match Participant.forStudy. The StudySite anchor and the Study anchor must agree." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Participant .
                $this top:forStudySite ?ss .
                $this top:forStudy ?participantStudy .
                ?ss topc:forStudy ?ssStudy .
                FILTER (?ssStudy != ?participantStudy)
            }
        """ ;
    ] .

# === Recruit invariants (v0.4.0) ===

top:RecruitConvertedNeedsParticipantShape a sh:NodeShape ;
    sh:targetClass top:Recruit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Recruit has recruitStatus=CONVERTED_TO_PARTICIPANT but convertedToParticipant relationship is not populated. The terminal-funnel-success status requires the operational link to the created Participant." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Recruit .
                $this top:recruitStatus "CONVERTED_TO_PARTICIPANT" .
                FILTER NOT EXISTS { $this top:convertedToParticipant ?p }
            }
        """ ;
    ] .

top:RecruitStalledManyAttemptsShape a sh:NodeShape ;
    sh:targetClass top:Recruit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Recruit has contactAttempts > 5 but is still in early-funnel state (RESPONDED or CONTACTED). Consider moving to DROPPED to keep the funnel clean and surface true conversion-rate metrics." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Recruit .
                $this top:recruitStatus ?status .
                FILTER (?status IN ("RESPONDED", "CONTACTED"))
                $this top:contactAttempts ?n .
                FILTER (?n > 5)
            }
        """ ;
    ] .

# === Visit invariants (v0.5.0) ===

top:VisitCompletedNeedsActualStartShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Visit has visitStatus in {COMPLETED, PARTIALLY_COMPLETED, OUT_OF_WINDOW} but no actualStartDate populated. A visit cannot be completed without recording when it actually happened." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Visit .
                $this top:visitStatus ?status .
                FILTER (?status IN ("COMPLETED", "PARTIALLY_COMPLETED", "OUT_OF_WINDOW"))
                FILTER NOT EXISTS { $this top:actualStartDate ?d }
            }
        """ ;
    ] .

top:VisitCompletedNeedsActualEndShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Visit has visitStatus=COMPLETED but no actualEndDate populated. A completed visit requires both start and end timestamps." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Visit .
                $this top:visitStatus "COMPLETED" .
                FILTER NOT EXISTS { $this top:actualEndDate ?d }
            }
        """ ;
    ] .

top:VisitUnscheduledNeedsReasonShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Visit has visitStatus=UNSCHEDULED but no unscheduledReason populated. Unscheduled visits require operator justification per protocol-deviation discipline." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Visit .
                $this top:visitStatus "UNSCHEDULED" .
                FILTER NOT EXISTS { $this top:unscheduledReason ?r }
            }
        """ ;
    ] .

top:VisitOutOfWindowNeedsDeviationCodeShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Visit has visitStatus=OUT_OF_WINDOW but no protocolDeviationCode populated. Out-of-window visits are protocol deviations and require a deviation code." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Visit .
                $this top:visitStatus "OUT_OF_WINDOW" .
                FILTER NOT EXISTS { $this top:protocolDeviationCode ?c }
            }
        """ ;
    ] .

top:VisitParticipantStudySiteConsistencyShape a sh:NodeShape ;
    sh:targetClass top:Visit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Visit's forParticipant.forStudySite does not match Visit.forStudySite. The participant's anchored StudySite and the visit's hosting StudySite must agree." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX topc: <https://top.scientix.ai/onto/commons/v1#>
            SELECT $this WHERE {
                $this a top:Visit .
                $this top:forParticipant ?participant .
                $this top:forStudySite ?visitStudySite .
                ?participant top:forStudySite ?participantStudySite .
                FILTER (?participantStudySite != ?visitStudySite)
            }
        """ ;
    ] .

top:ActivityNotPerformedNeedsReasonShape a sh:NodeShape ;
    sh:targetClass top:Activity ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Activity has activityStatus=NOT_PERFORMED but no notPerformedReason populated. Non-performance requires documented justification." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Activity .
                $this top:activityStatus "NOT_PERFORMED" .
                FILTER NOT EXISTS { $this top:notPerformedReason ?r }
            }
        """ ;
    ] .

top:TaskNotPerformedNeedsReasonShape a sh:NodeShape ;
    sh:targetClass top:Task ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Task has taskStatus=NOT_PERFORMED but no notPerformedReason populated. Non-performance requires documented justification." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Task .
                $this top:taskStatus "NOT_PERFORMED" .
                FILTER NOT EXISTS { $this top:notPerformedReason ?r }
            }
        """ ;
    ] .

top:TaskCompletedNeedsValueShape a sh:NodeShape ;
    sh:targetClass top:Task ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Task has taskStatus=COMPLETED but taskValue is missing. Completed Tasks must carry the captured value." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Task .
                $this top:taskStatus "COMPLETED" .
                FILTER NOT EXISTS { $this top:taskValue ?v }
            }
        """ ;
    ] .

top:VisitObservationEscalatedNeedsLinkShape a sh:NodeShape ;
    sh:targetClass top:VisitObservation ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "VisitObservation has escalated=true but no escalatedTo populated. Escalated observations should link to the resulting Event (when Event lifts in v0.7+)." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:VisitObservation .
                $this top:escalated true .
                FILTER NOT EXISTS { $this top:escalatedTo ?e }
            }
        """ ;
    ] .

# === OversightBody invariants (v0.5.1) ===

top:OversightBodyIRBScopeShape a sh:NodeShape ;
    sh:targetClass top:OversightBody ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "OversightBody has oversightBodyType in {IRB, EC} but scope is not LOCAL or CENTRAL. IRBs and ECs are review boards with defined geographic / institutional scope; SPONSOR or REGULATORY scopes indicate a typing mismatch." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:OversightBody .
                $this top:oversightBodyType ?type .
                FILTER (?type IN ("IRB", "EC"))
                $this top:scope ?scope .
                FILTER (?scope NOT IN ("LOCAL", "CENTRAL"))
            }
        """ ;
    ] .

top:OversightBodyDSMBScopeShape a sh:NodeShape ;
    sh:targetClass top:OversightBody ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "OversightBody has oversightBodyType in {DSMB, IDMC} but scope is not SPONSOR. DSMB / IDMC are sponsor-convened safety review boards." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:OversightBody .
                $this top:oversightBodyType ?type .
                FILTER (?type IN ("DSMB", "IDMC"))
                $this top:scope ?scope .
                FILTER (?scope != "SPONSOR")
            }
        """ ;
    ] .

# === InvestigationalProduct invariants (v0.6.0) ===

top:IPBlindedNeedsKitShape a sh:NodeShape ;
    sh:targetClass top:InvestigationalProduct ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "InvestigationalProduct has isBlinded=true but no hasKit. Blinded studies require Kit sub-objects for IRT-managed treatment-assignment masking." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:InvestigationalProduct .
                $this top:isBlinded true .
                FILTER NOT EXISTS { $this top:hasKit ?k }
            }
        """ ;
    ] .

top:KitAssignedNeedsParticipantShape a sh:NodeShape ;
    sh:targetClass top:Kit ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Kit has kitStatus in {ASSIGNED, DISPENSED} but no assignedToParticipant. Assigned and dispensed kits must reference the Participant." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Kit .
                $this top:kitStatus ?status .
                FILTER (?status IN ("ASSIGNED", "DISPENSED"))
                FILTER NOT EXISTS { $this top:assignedToParticipant ?p }
            }
        """ ;
    ] .

top:LotPastExpiryNeedsExpiredStatusShape a sh:NodeShape ;
    sh:targetClass top:Lot ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Lot has expirationDate in the past but lotStatus is not EXPIRED, RECALLED, or DESTROYED. Verify the lot has been retired from active distribution." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
            SELECT $this WHERE {
                $this a top:Lot .
                $this top:expirationDate ?exp .
                $this top:lotStatus ?status .
                BIND (xsd:dateTime(CONCAT(STR(?exp), "T23:59:59Z")) AS ?expDT)
                FILTER (?expDT < NOW())
                FILTER (?status NOT IN ("EXPIRED", "RECALLED", "DESTROYED"))
            }
        """ ;
    ] .

top:LotExpiredNeedsPastExpiryShape a sh:NodeShape ;
    sh:targetClass top:Lot ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Lot has lotStatus=EXPIRED but expirationDate is missing or in the future. EXPIRED status requires a past expiration date." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
            SELECT $this WHERE {
                $this a top:Lot .
                $this top:lotStatus "EXPIRED" .
                FILTER (NOT EXISTS { $this top:expirationDate ?exp } ||
                        EXISTS { $this top:expirationDate ?exp .
                                 BIND (xsd:dateTime(CONCAT(STR(?exp), "T00:00:00Z")) AS ?expDT)
                                 FILTER (?expDT > NOW()) })
            }
        """ ;
    ] .

# === Event invariants (v0.7.0) ===

top:EventSAEMustBeReportableShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Event has eventCategory=SERIOUS_ADVERSE_EVENT but regulatoryReportable is not true. SAEs trigger regulatory submission by definition per ICH E2A and 21 CFR 312.32." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Event .
                $this top:eventCategory "SERIOUS_ADVERSE_EVENT" .
                FILTER NOT EXISTS { $this top:regulatoryReportable true }
            }
        """ ;
    ] .

top:EventCtcaeOnlyForAEShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Event has ctcaeGrade populated but eventCategory is not ADVERSE_EVENT or SERIOUS_ADVERSE_EVENT. CTCAE grades only apply to clinical adverse events per CTCAE 5.0." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Event .
                $this top:ctcaeGrade ?grade .
                $this top:eventCategory ?cat .
                FILTER (?cat NOT IN ("ADVERSE_EVENT", "SERIOUS_ADVERSE_EVENT"))
            }
        """ ;
    ] .

top:EventDeviationCategoryOnlyForDeviationShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Event has protocolDeviationCategory populated but eventCategory is not DEVIATION. Protocol deviation categorization only applies to DEVIATION events per ICH E6(R3) Section 6." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Event .
                $this top:protocolDeviationCategory ?dc .
                $this top:eventCategory ?cat .
                FILTER (?cat != "DEVIATION")
            }
        """ ;
    ] .

top:EventSubmittedNeedsDateShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Event has regulatoryReportSubmitted=true but no regulatoryReportSubmittedDate. Audit-trail integrity requires the submission timestamp." ;
        sh:severity sh:Violation ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            SELECT $this WHERE {
                $this a top:Event .
                $this top:regulatoryReportSubmitted true .
                FILTER NOT EXISTS { $this top:regulatoryReportSubmittedDate ?d }
            }
        """ ;
    ] .

top:EventOverdueReportShape a sh:NodeShape ;
    sh:targetClass top:Event ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Event is regulatoryReportable=true with regulatoryReportDeadline in the past and regulatoryReportSubmitted still false. Submission is overdue; verify whether the report has been filed and update the record, or escalate per E2B SUSAR timelines." ;
        sh:severity sh:Warning ;
        sh:select """
            PREFIX top: <https://top.scientix.ai/onto/clinical/v1#>
            PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
            SELECT $this WHERE {
                $this a top:Event .
                $this top:regulatoryReportable true .
                $this top:regulatoryReportDeadline ?dl .
                FILTER (?dl < NOW())
                FILTER NOT EXISTS { $this top:regulatoryReportSubmitted true }
            }
        """ ;
    ] .
