@prefix pbo: <https://ontology.property.local/pbo#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .

# SHACL 2017 shapes for operational record completeness (DEC-015).
# These constrain data the platform stores; they do not assert domain truths.
# Cardinalities here are SHACL, not OWL cardinality restrictions.

pbo:ParticipationShape a sh:NodeShape ;
    sh:targetClass pbo:Participation ;
    sh:property [ sh:path pbo:participant ; sh:class pbo:Party ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A participation must name exactly one party."@en ] ,
                [ sh:path pbo:inRole ; sh:class pbo:Role ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A participation must name exactly one role."@en ] ,
                [ sh:path pbo:participationContext ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A participation must have exactly one context (agreement, asset, project or business)."@en ] ,
                [ sh:path pbo:effectiveFrom ; sh:datatype xsd:date ; sh:maxCount 1 ] ,
                [ sh:path pbo:effectiveUntil ; sh:datatype xsd:date ; sh:maxCount 1 ] .

pbo:AgreementShape a sh:NodeShape ;
    sh:targetClass pbo:Agreement ;
    sh:property [ sh:path pbo:hasParticipation ; sh:class pbo:Participation ; sh:minCount 2 ; sh:message "An agreement needs at least two participations (for example landlord and tenant)."@en ] ,
                [ sh:path pbo:concernsAsset ; sh:class pbo:PropertyAsset ; sh:minCount 1 ; sh:message "An agreement must concern at least one property asset or rentable unit."@en ] ,
                [ sh:path pbo:weeklyRent ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ; sh:minInclusive 0 ] ,
                [ sh:path pbo:termMonths ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ] ,
                [ sh:path pbo:escalationRatePercent ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ] ,
                [ sh:path pbo:fixedRent ; sh:datatype xsd:boolean ; sh:maxCount 1 ] ,
                [ sh:path pbo:leaseType ; sh:class pbo:LeaseType ; sh:maxCount 1 ] ,
                [ sh:path pbo:permittedUse ; sh:class pbo:AccommodationType ] ,
                [ sh:path pbo:hasClause ; sh:class pbo:ContractClause ] .

pbo:HeadLeaseShape a sh:NodeShape ;
    sh:targetClass pbo:HeadLease ;
    sh:property [ sh:path pbo:fixedRent ; sh:hasValue true ; sh:minCount 1 ; sh:message "A head/master lease record must state fixedRent true (fixed rent regardless of occupancy)."@en ] ,
                [ sh:path pbo:hasClause ; sh:qualifiedValueShape [ sh:class pbo:SublettingPermissionClause ] ; sh:qualifiedMinCount 1 ;
                  sh:severity sh:Warning ; sh:message "A head lease intended for subletting should record an express subletting permission clause."@en ] .

pbo:SubleaseShape a sh:NodeShape ;
    sh:targetClass pbo:Sublease ;
    sh:property [ sh:path pbo:agreementUnderHeadLease ; sh:class pbo:HeadLease ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A sublease must reference the head lease it derives from."@en ] .

pbo:OutgoingAllocationShape a sh:NodeShape ;
    sh:targetClass pbo:OutgoingAllocation ;
    sh:property [ sh:path pbo:outgoingCategory ; sh:class pbo:OutgoingCategory ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:borneByRole ; sh:class pbo:Role ; sh:minCount 1 ; sh:maxCount 1 ] .

pbo:AuthorisationShape a sh:NodeShape ;
    sh:targetClass pbo:Authorisation ;
    sh:property [ sh:path pbo:authorisationType ; sh:class pbo:AuthorisationType ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "An authorisation must have exactly one authorisation type."@en ] ,
                [ sh:path pbo:authorisationStatus ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ;
                  sh:in ( "held" "pending" "expired" "refused" "not_required" "unknown" ) ; sh:message "authorisationStatus must be one of held, pending, expired, refused, not_required, unknown."@en ] ,
                [ sh:path pbo:issuedBy ; sh:class pbo:GovernmentBody ; sh:maxCount 1 ] ,
                [ sh:path pbo:authorisedParty ; sh:class pbo:Party ; sh:maxCount 1 ] ,
                [ sh:path pbo:underRegime ; sh:class pbo:RegulatoryRegime ; sh:maxCount 1 ] ,
                [ sh:path pbo:dateIssued ; sh:datatype xsd:date ; sh:maxCount 1 ] ,
                [ sh:path pbo:effectiveUntil ; sh:datatype xsd:date ; sh:maxCount 1 ] ;
    sh:or ( [ sh:property [ sh:path pbo:concernsAsset ; sh:minCount 1 ] ]
            [ sh:property [ sh:path pbo:authorisedParty ; sh:minCount 1 ] ] ) ;
    sh:message "An authorisation must concern an asset or be granted to a party."@en .

pbo:RegulatoryRegimeShape a sh:NodeShape ;
    sh:targetClass pbo:RegulatoryRegime ;
    sh:property [ sh:path pbo:appliesIn ; sh:class pbo:Jurisdiction ; sh:minCount 1 ; sh:message "A regime must apply in at least one jurisdiction."@en ] ,
                [ sh:path pbo:governedBy ; sh:class pbo:Legislation ; sh:minCount 1 ; sh:message "A regime must cite the legislation that establishes it."@en ] ,
                [ sh:path pbo:administeredBy ; sh:class pbo:GovernmentBody ] ,
                [ sh:path pbo:hasThreshold ; sh:class pbo:OccupancyThreshold ] ,
                [ sh:path pbo:requiresAuthorisationType ; sh:class pbo:AuthorisationType ] ,
                [ sh:path pbo:hasRule ; sh:class pbo:RegulatoryRule ] .

pbo:OccupancyThresholdShape a sh:NodeShape ;
    sh:targetClass pbo:OccupancyThreshold ;
    sh:property [ sh:path pbo:thresholdKind ; sh:class pbo:ThresholdKind ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:thresholdComparator ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( "gte" "lte" "gt" "lt" "eq" "range" ) ] ,
                [ sh:path pbo:thresholdValue ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:thresholdUpperValue ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ] ,
                [ sh:path pbo:thresholdUnit ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:occupancyThresholdEffect ; sh:class pbo:ThresholdEffect ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A threshold must state its regulatory effect."@en ] .

pbo:RegulatoryRuleShape a sh:NodeShape ;
    sh:targetClass pbo:RegulatoryRule ;
    sh:property [ sh:path pbo:underRegime ; sh:class pbo:RegulatoryRegime ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A rule must sit under exactly one regime."@en ] ,
                [ sh:path pbo:effectiveFrom ; sh:datatype xsd:date ; sh:maxCount 1 ] ,
                [ sh:path pbo:effectiveUntil ; sh:datatype xsd:date ; sh:maxCount 1 ] ,
                [ sh:path pbo:cutoffDate ; sh:datatype xsd:dateTime ; sh:maxCount 1 ] .

pbo:TaxRuleShape a sh:NodeShape ;
    sh:targetClass pbo:TaxRule ;
    sh:property [ sh:path pbo:appliesToPropertyClass ; sh:class pbo:PropertyClass ; sh:minCount 1 ; sh:message "A tax rule must state the property class it applies to."@en ] .

pbo:MetricValueShape a sh:NodeShape ;
    sh:targetClass pbo:MetricValue ;
    sh:property [ sh:path pbo:metric ; sh:class pbo:Metric ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:numericValue ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A metric value must carry one decimal or integer numeric value."@en ] ,
                [ sh:path pbo:upperValue ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ] ,
                [ sh:path pbo:unit ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:assertedIn ; sh:class pbo:SourceDocument ; sh:maxCount 1 ] ,
                [ sh:path pbo:claimDate ; sh:datatype xsd:date ; sh:maxCount 1 ] .

pbo:FinancialScenarioShape a sh:NodeShape ;
    sh:targetClass pbo:FinancialScenario ;
    sh:property [ sh:path pbo:scenarioName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:assumes ; sh:class pbo:MetricValue ; sh:minCount 3 ; sh:message "A scenario must declare at least three input assumptions (for example rooms, tariff, vacancy)."@en ] ,
                [ sh:path pbo:yields ; sh:class pbo:MetricValue ; sh:minCount 1 ; sh:message "A scenario must record at least one result metric."@en ] ,
                [ sh:path pbo:scenarioForModel ; sh:class pbo:OperatingModel ; sh:maxCount 1 ] ,
                [ sh:path pbo:assertedIn ; sh:class pbo:SourceDocument ; sh:maxCount 1 ] .

pbo:PropertyAssetShape a sh:NodeShape ;
    sh:targetClass pbo:PropertyAsset ;
    sh:property [ sh:path pbo:locatedIn ; sh:class pbo:Jurisdiction ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "A property asset must be located in exactly one (most specific) jurisdiction."@en ] ,
                [ sh:path pbo:hasIdentifier ; sh:class pbo:PropertyIdentifier ] ,
                [ sh:path pbo:roomCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ] ,
                [ sh:path pbo:bedroomCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ] ,
                [ sh:path pbo:floorAreaSqm ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ] ,
                [ sh:path pbo:hasAccommodationType ; sh:class pbo:AccommodationType ] ,
                [ sh:path pbo:intendedOperatingModel ; sh:class pbo:OperatingModel ] ,
                [ sh:path pbo:hasBuildingClassification ; sh:class pbo:BuildingClassification ; sh:maxCount 1 ] ,
                [ sh:path pbo:hasAuthorisation ; sh:class pbo:Authorisation ] .

pbo:DwellingIdentifierShape a sh:NodeShape ;
    sh:targetClass pbo:Dwelling ;
    sh:property [ sh:path pbo:hasIdentifier ; sh:minCount 1 ; sh:severity sh:Warning ; sh:message "A dwelling record should carry at least one identifier (address or lot/plan)."@en ] .

pbo:PropertyIdentifierShape a sh:NodeShape ;
    sh:targetClass pbo:PropertyIdentifier ;
    sh:property [ sh:path pbo:identifierValue ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] .

pbo:JurisdictionShape a sh:NodeShape ;
    sh:targetClass pbo:Jurisdiction ;
    sh:property [ sh:path pbo:jurisdictionCode ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:withinJurisdiction ; sh:class pbo:Jurisdiction ] .

pbo:DataSourceShape a sh:NodeShape ;
    sh:targetClass pbo:DataSource ;
    sh:property [ sh:path pbo:publishedBy ; sh:class pbo:Organisation ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:accessTier ; sh:class pbo:DataAccessTier ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:suppliesAttribute ; sh:class pbo:PropertyAttribute ; sh:minCount 1 ] ,
                [ sh:path pbo:coversJurisdiction ; sh:class pbo:Jurisdiction ; sh:minCount 1 ] ,
                [ sh:path pbo:refreshCadence ; sh:datatype xsd:string ; sh:maxCount 1 ] .

pbo:RiskShape a sh:NodeShape ;
    sh:targetClass pbo:Risk ;
    sh:property [ sh:path pbo:riskCategory ; sh:class pbo:RiskCategory ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:mitigatedBy ; sh:class pbo:Mitigation ] .

pbo:MitigationShape a sh:NodeShape ;
    sh:targetClass pbo:Mitigation ;
    sh:property [ sh:path pbo:mitigationNote ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] .

pbo:DecisionGateShape a sh:NodeShape ;
    sh:targetClass pbo:DecisionGate ;
    sh:property [ sh:path pbo:gateCondition ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:gateRequiresAuthorisationType ; sh:class pbo:AuthorisationType ] .

pbo:ServiceShape a sh:NodeShape ;
    sh:targetClass pbo:Service ;
    sh:property [ sh:path pbo:hasFeeModel ; sh:class pbo:FeeModel ; sh:minCount 1 ] ,
                [ sh:path pbo:feePercentOfRent ; sh:or ( [ sh:datatype xsd:decimal ] [ sh:datatype xsd:integer ] ) ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 100 ] .

pbo:SourceDocumentShape a sh:NodeShape ;
    sh:targetClass pbo:SourceDocument ;
    sh:property [ sh:path pbo:authorityLevel ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ;
                  sh:in ( "normative" "authoritative" "primary" "secondary" "expert" "reference" "illustrative" ) ] ,
                [ sh:path pbo:sourceUrl ; sh:datatype xsd:anyURI ; sh:maxCount 1 ] ,
                [ sh:path pbo:publicationDate ; sh:datatype xsd:date ; sh:maxCount 1 ] .

pbo:StructureCharacteristicShape a sh:NodeShape ;
    sh:targetClass pbo:StructureCharacteristic ;
    sh:property [ sh:path pbo:characteristicNote ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ] ,
                [ sh:path pbo:assertedIn ; sh:class pbo:SourceDocument ; sh:minCount 1 ; sh:message "A structure characteristic must cite the source document that states it."@en ] .

pbo:ProjectPhaseShape a sh:NodeShape ;
    sh:targetClass pbo:ProjectPhase ;
    sh:property [ sh:path pbo:phaseOrder ; sh:datatype xsd:integer ; sh:maxCount 1 ] .

pbo:DevelopmentProjectShape a sh:NodeShape ;
    sh:targetClass pbo:DevelopmentProject ;
    sh:property [ sh:path pbo:hasPhase ; sh:class pbo:ProjectPhase ; sh:minCount 1 ] ,
                [ sh:path pbo:concernsAsset ; sh:class pbo:PropertyAsset ; sh:minCount 1 ] .
