# Google JSON-LD SHACL — LocalBusiness + SoftwareApplication
# Canonical: https://incins.com/shapes/listings.ttl
# Docs: https://incins.com/docs/listings-shacl/
# Google LocalBusiness: https://developers.google.com/search/docs/appearance/structured-data/local-business
# Google Software App: https://developers.google.com/search/docs/appearance/structured-data/software-app

@prefix sh:     <http://www.w3.org/ns/shacl#> .
@prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
@prefix schema: <https://schema.org/> .
@prefix cap:    <https://incins.com/ns/cap#> .
@prefix listings: <https://incins.com/ns/listings#> .

<https://incins.com/shapes/listings>
  a schema:DataCatalog ;
  schema:name "IncIns listings SHACL shapes" ;
  schema:url <https://incins.com/shapes/listings> ;
  schema:hasPart
    <https://incins.com/shapes/listings#localbusiness> ,
    <https://incins.com/shapes/listings#postaladdress> ,
    <https://incins.com/shapes/listings#geo> ,
    <https://incins.com/shapes/listings#hours> ,
    <https://incins.com/shapes/listings#imageobject> ,
    <https://incins.com/shapes/listings#softwareapplication> ,
    <https://incins.com/shapes/listings#offer> ,
    <https://incins.com/shapes/listings#aggregaterating> ,
    <https://incins.com/shapes/listings#capability> .

<https://incins.com/shapes/listings#localbusiness>
  a sh:NodeShape ;
  sh:name "Google LocalBusiness JSON-LD" ;
  sh:description "SHACL for schema.org LocalBusiness (and MedicalBusiness subtypes) matching Google Search Central required and recommended properties: name, address, plus image, priceRange, telephone, url, geo." ;
  sh:targetClass schema:LocalBusiness, schema:MedicalBusiness, schema:MedicalClinic, schema:SubstanceUseDisorderTreatmentFacility ;
  sh:property [
    sh:path schema:name ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:minLength 2 ;
    sh:message "Google requires name — the trading name as on the Business Profile."
  ] ;
  sh:property [
    sh:path schema:address ;
    sh:minCount 1 ;
    sh:node <https://incins.com/shapes/listings#postaladdress> ;
    sh:message "Google requires address as a nested PostalAddress, not a string."
  ] ;
  sh:property [
    sh:path schema:telephone ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^\\+[0-9]" ;
    sh:message "Google recommends telephone with country code (E.164-style, e.g. +13185550142)."
  ] ;
  sh:property [
    sh:path schema:url ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^https://" ;
    sh:message "Google recommends a fully-qualified https URL for this location."
  ] ;
  sh:property [
    sh:path schema:email ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" ;
    sh:message "Email is not returned by Places; pull it from the business website (schema.org or mailto)."
  ] ;
  sh:property [
    sh:path schema:image ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends at least one image URL or ImageObject (1x1, 4x3, and 16x9 preferred)."
  ] ;
  sh:property [
    sh:path schema:geo ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:node <https://incins.com/shapes/listings#geo> ;
    sh:message "Google recommends geo with latitude/longitude to ≥5 decimal places."
  ] ;
  sh:property [
    sh:path schema:openingHoursSpecification ;
    sh:severity sh:Warning ;
    sh:node <https://incins.com/shapes/listings#hours> ;
    sh:message "Google recommends openingHoursSpecification (not free-text openingHours)."
  ] ;
  sh:property [
    sh:path schema:priceRange ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:maxLength 99 ;
    sh:message "Google recommends priceRange (e.g. $$ or $10-15), under 100 characters."
  ] .

<https://incins.com/shapes/listings#postaladdress>
  a sh:NodeShape ;
  sh:name "Google PostalAddress" ;
  sh:targetClass schema:PostalAddress ;
  sh:property [
    sh:path schema:streetAddress ;
    sh:minCount 1 ;
    sh:message "Include streetAddress."
  ] ;
  sh:property [
    sh:path schema:addressLocality ;
    sh:minCount 1 ;
    sh:message "Include addressLocality (city)."
  ] ;
  sh:property [
    sh:path schema:addressRegion ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Include addressRegion (state/province)."
  ] ;
  sh:property [
    sh:path schema:postalCode ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Include postalCode."
  ] ;
  sh:property [
    sh:path schema:addressCountry ;
    sh:minCount 1 ;
    sh:message "Include addressCountry (ISO 3166, e.g. US)."
  ] .

<https://incins.com/shapes/listings#geo>
  a sh:NodeShape ;
  sh:name "Google GeoCoordinates" ;
  sh:targetClass schema:GeoCoordinates ;
  sh:property [
    sh:path schema:latitude ;
    sh:minCount 1 ;
    sh:minInclusive -90 ;
    sh:maxInclusive 90 ;
    sh:message "latitude is required (−90 to 90)."
  ] ;
  sh:property [
    sh:path schema:longitude ;
    sh:minCount 1 ;
    sh:minInclusive -180 ;
    sh:maxInclusive 180 ;
    sh:message "longitude is required (−180 to 180)."
  ] .

<https://incins.com/shapes/listings#hours>
  a sh:NodeShape ;
  sh:name "Google OpeningHoursSpecification" ;
  sh:targetClass schema:OpeningHoursSpecification ;
  sh:property [
    sh:path schema:dayOfWeek ;
    sh:minCount 1 ;
    sh:message "dayOfWeek is required (Monday … Sunday)."
  ] ;
  sh:property [
    sh:path schema:opens ;
    sh:minCount 1 ;
    sh:pattern "^[0-2][0-9]:[0-5][0-9]" ;
    sh:message "opens must be hh:mm (24-hour)."
  ] ;
  sh:property [
    sh:path schema:closes ;
    sh:minCount 1 ;
    sh:pattern "^[0-2][0-9]:[0-5][0-9]" ;
    sh:message "closes must be hh:mm (24-hour)."
  ] .

<https://incins.com/shapes/listings#imageobject>
  a sh:NodeShape ;
  sh:name "Google ImageObject (image license metadata)" ;
  sh:description "SHACL for Google image license metadata. contentUrl is required for the rich result; acquireLicensePage, license, creditText, creator, and copyrightNotice are recommended." ;
  sh:targetClass schema:ImageObject ;
  sh:property [
    sh:path schema:contentUrl ;
    sh:minCount 1 ;
    sh:pattern "^https://" ;
    sh:message "Google image license metadata requires contentUrl as a fully-qualified https URL."
  ] ;
  sh:property [
    sh:path schema:acquireLicensePage ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^https://" ;
    sh:message "Google recommends acquireLicensePage — a page where a user can license this image."
  ] ;
  sh:property [
    sh:path schema:license ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^https://" ;
    sh:message "Google recommends license as a URL (e.g. Unsplash License or a site license page)."
  ] ;
  sh:property [
    sh:path schema:creditText ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends creditText for the photographer or owner."
  ] ;
  sh:property [
    sh:path schema:copyrightNotice ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends copyrightNotice."
  ] ;
  sh:property [
    sh:path schema:creator ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends creator as a Person or Organization (photographer or source)."
  ] .

<https://incins.com/shapes/listings#softwareapplication>
  a sh:NodeShape ;
  sh:name "Google SoftwareApplication / WebApplication JSON-LD" ;
  sh:description "SHACL for Google Software App rich results. Google requires two of: offers, aggregateRating, applicationCategory, operatingSystem. This shape treats all four as recommended warnings so Rich Results Test stays clean." ;
  sh:targetClass schema:SoftwareApplication, schema:WebApplication ;
  sh:property [
    sh:path schema:name ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:minLength 2 ;
    sh:message "Google requires name for SoftwareApplication."
  ] ;
  sh:property [
    sh:path schema:url ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:pattern "^https://" ;
    sh:message "Google recommends a fully-qualified https URL."
  ] ;
  sh:property [
    sh:path schema:applicationCategory ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google Software App: applicationCategory is one of the two-of-four required properties (offers, aggregateRating, applicationCategory, operatingSystem)."
  ] ;
  sh:property [
    sh:path schema:operatingSystem ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google Software App: operatingSystem is one of the two-of-four required properties."
  ] ;
  sh:property [
    sh:path schema:offers ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:node <https://incins.com/shapes/listings#offer> ;
    sh:message "Google Software App: offers (price + priceCurrency) is one of the two-of-four required properties."
  ] ;
  sh:property [
    sh:path schema:aggregateRating ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:node <https://incins.com/shapes/listings#aggregaterating> ;
    sh:message "Google Software App: aggregateRating is recommended (ratingValue plus ratingCount or reviewCount)."
  ] .

<https://incins.com/shapes/listings#offer>
  a sh:NodeShape ;
  sh:name "Google Offer (Software App)" ;
  sh:targetClass schema:Offer ;
  sh:property [
    sh:path schema:price ;
    sh:minCount 1 ;
    sh:message "Offer needs price (use 0 for free)."
  ] ;
  sh:property [
    sh:path schema:priceCurrency ;
    sh:minCount 1 ;
    sh:minLength 3 ;
    sh:maxLength 3 ;
    sh:message "Offer needs an ISO 4217 priceCurrency (e.g. USD)."
  ] .

<https://incins.com/shapes/listings#aggregaterating>
  a sh:NodeShape ;
  sh:name "Google AggregateRating" ;
  sh:targetClass schema:AggregateRating ;
  sh:property [
    sh:path schema:ratingValue ;
    sh:minCount 1 ;
    sh:message "Google requires ratingValue on AggregateRating."
  ] ;
  sh:property [
    sh:path schema:ratingCount ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google requires ratingCount or reviewCount on AggregateRating."
  ] ;
  sh:property [
    sh:path schema:bestRating ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends bestRating (typically 5)."
  ] ;
  sh:property [
    sh:path schema:worstRating ;
    sh:severity sh:Warning ;
    sh:minCount 1 ;
    sh:message "Google recommends worstRating (typically 1)."
  ] .

<https://incins.com/shapes/listings#capability>
  a sh:NodeShape ;
  sh:name "Linked view capability" ;
  sh:targetClass cap:Capability ;
  sh:property [
    sh:path schema:name ;
    sh:minCount 1 ;
    sh:message "Capability needs a name."
  ] ;
  sh:property [
    sh:path cap:allowedAction ;
    sh:minCount 1 ;
    sh:message "Capability needs allowedAction."
  ] ;
  sh:property [
    sh:path cap:controller ;
    sh:minCount 1 ;
    sh:nodeKind sh:IRI ;
    sh:message "Capability needs a controller."
  ] ;
  sh:property [
    sh:path cap:invocationTarget ;
    sh:minCount 1 ;
    sh:message "Capability needs an invocationTarget."
  ] .
