@prefix ph:      <https://id.pedalhidrografi.co/terms#> .
@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 dcterms: <http://purl.org/dc/terms/> .
@prefix schema:  <https://schema.org/> .
@prefix prov:    <http://www.w3.org/ns/prov#> .

<https://id.pedalhidrografi.co/terms> a owl:Ontology ;
    dcterms:title "Pedal Hidrográfico — vocabulário"@pt ;
    dcterms:description
        "Vocabulário específico do coletivo Pedal Hidrográfico. Reutiliza PROV-O, schema.org, Dublin Core, NFO, EXIF, GeoSPARQL; mint termos ph: apenas para conceitos próprios."@pt ;
    owl:versionInfo "0.1-draft" .

# =====================================================================
# Classes
# =====================================================================

ph:Tour a owl:Class ;
    rdfs:label "Passeio"@pt ;
    rdfs:comment
        "Pedalada coletiva organizada pelo coletivo, com data, rota e participantes. Tratada como prov:Activity (gera fotos, atribuição a pessoas) e schema:Event (data, organizador, link público)."@pt ;
    rdfs:subClassOf prov:Activity, schema:Event .

ph:Image a owl:Class ;
    rdfs:label "Mídia visual"@pt ;
    rdfs:comment
        "Classe-base ABSTRATA de mídia visual capturada num passeio — foto (ph:StillImage) ou vídeo (ph:MotionImage). Reúne as exigências compartilhadas: procedência (PROV-O), coordenada GPS, data, autoria e licença. Instâncias concretas nunca são `a ph:Image` diretamente; usam uma das subclasses. (Mantém o nome histórico ph:Image por compatibilidade dos prefixos de IRI phd:image_/phd:video_ e do vocabulário.)"@pt ;
    rdfs:subClassOf prov:Entity, schema:MediaObject .

ph:StillImage a owl:Class ;
    rdfs:label "Foto"@pt ;
    rdfs:comment
        "Fotografia (imagem estática) tirada durante um passeio. Subclasse concreta de ph:Image; acrescenta direção (exif:gpsImgDirection), focal eq. 35mm (exif:focalLengthIn35mmFilm) e hash perceptual (nfo:hasHash). IRIs no prefixo phd:image_."@pt ;
    rdfs:subClassOf ph:Image, schema:ImageObject .

ph:MotionImage a owl:Class ;
    rdfs:label "Vídeo"@pt ;
    rdfs:comment
        "Captura A/V curta (imagem em movimento) gravada durante um passeio. Subclasse concreta de ph:Image; NÃO requer direção nem focal (não fazem sentido pra vídeo) e acrescenta schema:duration, ph:availableResolution, ph:audio e as variantes de resolução. Quando ph:availableResolution só contém 'audio', é uma captura puramente sonora. IRIs no prefixo phd:video_."@pt ;
    rdfs:subClassOf ph:Image, schema:VideoObject .

ph:GeoreferencedImage a owl:Class ;
    rdfs:label "Mídia georreferenciada"@pt ;
    rdfs:comment
        "Mixin: mídia visual (foto OU vídeo) com coordenada GPS — o subconjunto que o mapa renderiza como marcador de foto/vídeo. Usada JUNTO com a subclasse concreta (ex.: `a ph:StillImage, ph:GeoreferencedImage`). Na base ph:Image a coordenada é apenas ideal (sh:Warning); aqui ela vira obrigatória (ph:GeoreferencedImageShape). Os emitters (upload_images.html, build-clips.py) acrescentam este tipo quando a mídia tem GPS."@pt ;
    rdfs:subClassOf ph:Image .

ph:availableResolution a owl:DatatypeProperty ;
    rdfs:label "resolução disponível"@pt ;
    rdfs:comment
        "Resolução(ões) em que o vídeo está disponível no armazenamento. Use uma das constantes: 'audio' (apenas áudio, sem vídeo), '360p', '480p', '720p', '1080p'. Múltiplos valores são esperados (ex.: 'audio', '360p', '720p')."@pt ;
    rdfs:domain ph:MotionImage ;
    rdfs:range xsd:string .

ph:audio a owl:DatatypeProperty ;
    rdfs:label "trilha de áudio"@pt ;
    rdfs:comment
        "Caminho relativo (sob web/clips/) para a faixa de áudio do clipe, em WAV PCM (sem compressão lossy adicional)."@pt ;
    rdfs:domain ph:MotionImage ;
    rdfs:range xsd:string .

ph:video360p a owl:DatatypeProperty ;
    rdfs:label "webm 360p"@pt ;
    rdfs:comment
        "Caminho relativo (sob web/clips/) para o webm 360p sem áudio."@pt ;
    rdfs:domain ph:MotionImage ;
    rdfs:range xsd:string .

ph:video720p a owl:DatatypeProperty ;
    rdfs:label "webm 720p"@pt ;
    rdfs:comment
        "Caminho relativo (sob web/clips/) para o webm 720p sem áudio."@pt ;
    rdfs:domain ph:MotionImage ;
    rdfs:range xsd:string .

ph:RouteReference a owl:Class ;
    rdfs:label "Referência a rota externa"@pt ;
    rdfs:comment
        "Ligação para uma rota publicada em um serviço (RideWithGPS, Strava, ou uma rota salva do próprio Amora em /route/<slug>). Carrega URL e provedor."@pt .

ph:SeriesEdition a owl:Class ;
    rdfs:label "Edição de série"@pt ;
    rdfs:comment
        "Uma edição concreta de uma schema:EventSeries — a posição na sequência (ex.: o 4º Baixada Paulista) realizada por um passeio. IRI própria e resolvível em …/passeio/<série>/<nº> (ex.: …/passeio/BP/4). Cada edição é realizada por EXATAMENTE um passeio (colisões de numeração viram edições distintas, ex.: BP/3-5). Ex-ph:Association; não confundir com prov:Association (atribuição de responsabilidade a um agente)."@pt .

# =====================================================================
# Propriedades — Passeio (ph:Tour)
# =====================================================================

ph:linkRoute a owl:ObjectProperty ;
    rdfs:label "rota externa"@pt ;
    rdfs:comment
        "Referência a uma rota publicada em um serviço (URL + provedor). Substitui ph:linkRWGPS, que ficava restrita ao RideWithGPS."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range ph:RouteReference .

ph:linkRWGPS a owl:DatatypeProperty ;
    rdfs:label "rota RideWithGPS (legado)"@pt ;
    rdfs:comment
        "Substituído por ph:linkRoute, que carrega URL + provedor."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:anyURI ;
    rdfs:subPropertyOf schema:url ;
    owl:deprecated true .

ph:linkActivity a owl:DatatypeProperty ;
    rdfs:label "atividade GPS gravada"@pt ;
    rdfs:comment
        "URL de uma atividade GPS gravada durante o passeio (ex.: RideWithGPS/Strava trip) — a gravação real de quem pedalou, distinta da rota planejada (ph:linkRoute). Repetível: cada gravação (de qualquer participante) entra como uma triple própria. Alimenta scripts de backfill que extraem métricas reais (ph:departedAt/ph:arrivedAt/ph:movingDuration/ph:measuredEnergy) do rastro GPS."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:anyURI .

# Provedores de rota (instâncias reutilizáveis)

ph:rwgps a schema:Organization ;
    schema:name "Ride with GPS" ;
    schema:url <https://ridewithgps.com> .

ph:strava a schema:Organization ;
    schema:name "Strava" ;
    schema:url <https://www.strava.com> .

# O próprio amora como provedor: rotas salvas no editor de traçado,
# compartilhadas por nome em https://amora.pedalhidrografi.co/route/<slug>.
ph:amora a schema:Organization ;
    schema:name "Amora (Pedal Hidrográfico)" ;
    schema:url <https://amora.pedalhidrografi.co> .

ph:linkInstagram a owl:DatatypeProperty ;
    rdfs:label "post no Instagram"@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:anyURI ;
    rdfs:subPropertyOf schema:url .

ph:countAttendee a owl:DatatypeProperty ;
    rdfs:label "participantes"@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:nonNegativeInteger .

ph:countNewcomer a owl:DatatypeProperty ;
    rdfs:label "iniciantes"@pt ;
    rdfs:comment
        "Subconjunto de ph:countAttendee: pessoas no primeiro pedal com o coletivo. Independente de ph:hasNewcomer (que lista nominalmente as pessoas) — ambos podem coexistir, mas só `countNewcomer` é obrigatório."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:nonNegativeInteger .

ph:hasNewcomer a owl:ObjectProperty ;
    rdfs:label "iniciante"@pt ;
    rdfs:comment
        "Pessoa que está participando pela primeira vez de um passeio de uma dada série de eventos (SHACL impõe unicidade por série). Subpropriedade de schema:attendee, então um newcomer também é attendee. Aparece nominalmente — diferente de ph:countNewcomer, que é só o número agregado."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range schema:Person ;
    rdfs:subPropertyOf schema:attendee .

ph:energyEstimate a owl:DatatypeProperty ;
    rdfs:label "estimativa de energia"@pt ;
    rdfs:comment
        "Energia estimada gasta no passeio, em quilojoules (kJ). Valor numérico literal direto no passeio."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:decimal .

ph:measuredEnergy a owl:DatatypeProperty ;
    rdfs:label "energia medida"@pt ;
    rdfs:comment
        "Energia efetivamente medida no passeio (ex.: trabalho mecânico de uma atividade GPS), em quilojoules (kJ). Valor numérico literal direto no passeio."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:decimal .

ph:estimate_quilojoules a owl:DatatypeProperty ;
    rdfs:label "estimativa em quilojoules (legado)"@pt ;
    rdfs:comment
        "Nome legado em snake_case. Preferir ph:energyEstimate (em kJ)."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:decimal ;
    owl:equivalentProperty ph:energyEstimate ;
    owl:deprecated true .

# ph:intensityClassification foi removida: a intensidade ('De boa', 'Ok',
# 'Endorfinado', 'Frito', 'Insano') é derivada do valor em kJ por faixas fixas,
# computada nos leitores (censo.html / app.js) — não é mais armazenada.

ph:departedAt a owl:DatatypeProperty ;
    rdfs:label "saída"@pt ;
    rdfs:comment
        "Horário em que o grupo efetivamente partiu (pode diferir do horário marcado em dcterms:date)."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:dateTime .

ph:arrivedAt a owl:DatatypeProperty ;
    rdfs:label "chegada"@pt ;
    rdfs:comment
        "Horário em que o grupo chegou ao destino final do passeio."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:dateTime .

ph:departureLocation a owl:DatatypeProperty ;
    rdfs:label "local de saída"@pt ;
    rdfs:comment
        "Nome do local de partida do passeio, em texto livre (ex.: 'Bar Nova Magnata — Metrô Barra Funda')."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:string .

ph:arrivalLocation a owl:DatatypeProperty ;
    rdfs:label "local de chegada"@pt ;
    rdfs:comment
        "Nome do local onde o passeio termina, em texto livre (ex.: 'CPTM Guaianases')."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:string .

ph:movingDuration a owl:DatatypeProperty ;
    rdfs:label "tempo em movimento"@pt ;
    rdfs:comment
        "Duração efetivamente pedalando (exclui paradas), no formato ISO 8601 (ex.: 'PT1H22M')."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:duration .

ph:totalDuration a owl:DatatypeProperty ;
    rdfs:label "tempo total"@pt ;
    rdfs:comment
        "Duração total do passeio, da saída à chegada (paradas incluídas), no formato ISO 8601 (ex.: 'PT3H35M'). Só é gravada quando ph:departedAt e ph:arrivedAt NÃO estão ambos registrados: com os dois, o tempo total é derivado (arrivedAt − departedAt) e este predicado é omitido/removido. Leitores derivam primeiro e caem neste literal na falta dos horários."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:duration .

ph:hadIncident a owl:DatatypeProperty ;
    rdfs:label "incidente"@pt ;
    rdfs:comment
        "Registro textual e livre de um incidente ou fato notável ocorrido durante o passeio. Repetível: cada ocorrência é uma triple própria."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:string .

ph:hadBonde a owl:DatatypeProperty ;
    rdfs:label "teve bonde"@pt ;
    rdfs:comment
        "true se o passeio teve 'bonde' (grupo grande pedalando juntos, além do usual). Ausência do predicado = não informado (não é o mesmo que false)."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:boolean .

ph:hadRain a owl:DatatypeProperty ;
    rdfs:label "teve chuva"@pt ;
    rdfs:comment
        "true se choveu durante o passeio. Ausência do predicado = não informado (não é o mesmo que false)."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:boolean .

# =====================================================================
# Passe de coleta de mídia (ph:MediaSweep)
# =====================================================================
# Registra o ato de VARRER o grupo de WhatsApp depois do passeio e arquivar
# o que as pessoas compartilharam na Biblioteca Hidrográfica (Drive). É a
# única fonte de captura que o amora não enxerga sozinho: as fotos ficam
# fora do catálogo, então sem este marcador não há como distinguir
# "ninguém compartilhou nada" de "ninguém foi buscar".
#
# A EXISTÊNCIA do nó é o marcador: nó presente = o passe foi feito.
# ph:collectedFileCount 0 = passe feito e ninguém compartilhou. Nó ausente
# = passe NUNCA foi feito (não consta). Não há enum de status — o modelo
# já distingue os três casos sem ele.

ph:MediaSweep a owl:Class ;
    rdfs:label "Passe de coleta de mídia"@pt ;
    rdfs:comment
        "Atividade de coletar a mídia que os participantes compartilharam fora do amora (hoje: o grupo de WhatsApp) e arquivá-la na Biblioteca Hidrográfica. Um passeio tem no máximo um passe (o mais recente); re-varrer atualiza a contagem e a data no lugar."@pt ;
    rdfs:subClassOf prov:Activity .

ph:mediaSweep a owl:ObjectProperty ;
    rdfs:label "passe de coleta"@pt ;
    rdfs:comment
        "Liga o passeio ao seu passe de coleta de mídia. O nó é derivado do passeio (pas:<slug>_sweep), então a purga por aritmética de prefixo do backend o remove junto com o passeio."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range ph:MediaSweep .

# ATENÇÃO: os predicados abaixo penduram no NÓ DO PASSE, não no passeio.
# O domínio TEM que ser ph:MediaSweep — o validador roda com inference=rdfs,
# e um rdfs:domain ph:Tour aqui tiparia o nó do passe como ph:Tour, jogando-o
# na ph:TourShape (que exige título e data) e produzindo Violation.

ph:collectedFileCount a owl:DatatypeProperty ;
    rdfs:label "arquivos coletados"@pt ;
    rdfs:comment
        "Quantos arquivos o passe arquivou. Zero é significativo: o passe foi feito e ninguém compartilhou nada."@pt ;
    rdfs:domain ph:MediaSweep ;
    rdfs:range xsd:nonNegativeInteger .

ph:archiveFolder a owl:DatatypeProperty ;
    rdfs:label "pasta do acervo"@pt ;
    rdfs:comment
        "Nome da pasta do passeio na Biblioteca Hidrográfica (ex.: '2025-09-09 54 Água Preta, Tiburtino, 1 ano de PH'). Caminho relativo à raiz do acervo, não URL — o Drive não tem IRI estável e pública."@pt ;
    rdfs:domain ph:MediaSweep ;
    rdfs:range xsd:string .

ph:sweepContributor a owl:DatatypeProperty ;
    rdfs:label "quem compartilhou"@pt ;
    rdfs:comment
        "Slug (handle) de quem compartilhou ao menos um arquivo neste passe, extraído do prefixo do nome do arquivo arquivado. Repetível. Fica como LITERAL de propósito: o slug é o dado bruto, e a resolução para uma pessoa é um join via schema:alternateName em identities.ttl — que pode não existir ainda. Assim o passe nunca fica bloqueado esperando alguém ser cadastrado."@pt ;
    rdfs:domain ph:MediaSweep ;
    rdfs:range xsd:string .

ph:mediaCount a owl:DatatypeProperty ;
    rdfs:label "mídias coletadas"@pt ;
    rdfs:comment
        "OBSOLETO — substituído por ph:mediaSweep/ph:collectedFileCount, que carrega a mesma contagem COM procedência (quando o passe foi feito, por quem, de qual pasta). Resíduo do import da planilha histórica: contava os arquivos do acervo do Drive, mas nada o mantinha atualizado e a ausência era ambígua (não distinguia 'não coletei' de 'coletei e não veio nada'). Não escreva mais neste predicado."@pt ;
    owl:deprecated true ;
    rdfs:domain ph:Tour ;
    rdfs:range xsd:nonNegativeInteger .

# =====================================================================
# Propriedades — Foto (ph:Image)
# =====================================================================

ph:Upload a owl:Class ;
    rdfs:label "Envio"@pt ;
    rdfs:comment
        "Atividade server-side: o ato de receber + persistir um ph:Image. Cada upload aparece como uma activity própria, com timestamp do envio."@pt ;
    rdfs:subClassOf prov:Activity .

ph:anonymized a owl:DatatypeProperty ;
    rdfs:label "anonimizada"@pt ;
    rdfs:comment
        "true se o arquivo distribuído teve seus metadados (EXIF, etc.) removidos."@pt ;
    rdfs:domain ph:StillImage ;
    rdfs:range xsd:boolean .

ph:compressed a owl:DatatypeProperty ;
    rdfs:label "comprimida"@pt ;
    rdfs:comment
        "true se o arquivo 'original' distribuído foi reduzido para um tamanho-alvo (ex.: 500 KB)."@pt ;
    rdfs:domain ph:StillImage ;
    rdfs:range xsd:boolean .

ph:capturedDuring a owl:ObjectProperty ;
    rdfs:label "registrada durante"@pt ;
    rdfs:comment
        "Liga a mídia (foto ou vídeo) ao passeio em que foi capturada."@pt ;
    rdfs:domain ph:Image ;
    rdfs:range ph:Tour ;
    rdfs:subPropertyOf prov:wasGeneratedBy .

# =====================================================================
# Propriedades — Filiação a série (ph:SeriesEdition)
# =====================================================================
# Sucessores propostos para os usos atuais
# ( `schema:EventSeries` como predicado e `ph:EventSeriesSequenceNumber`
#   em PascalCase). Mantemos os legados como deprecados.

ph:inSeriesEdition a owl:ObjectProperty ;
    rdfs:label "edição na série"@pt ;
    rdfs:comment
        "Liga um passeio à filiação que registra sua posição em uma série de eventos. Um passeio pode pertencer a mais de uma série (cada uma com sua Association)."@pt ;
    rdfs:domain ph:Tour ;
    rdfs:range ph:SeriesEdition .

ph:inEventSeries a owl:ObjectProperty ;
    rdfs:label "na série"@pt ;
    rdfs:comment
        "Série de eventos à qual o passeio pertence. Substitui o uso (incorreto) de schema:EventSeries como predicado."@pt ;
    rdfs:domain ph:SeriesEdition ;
    rdfs:range schema:EventSeries ;
    rdfs:subPropertyOf schema:partOfSeries .

ph:sequenceInSeries a owl:DatatypeProperty ;
    rdfs:label "posição na série"@pt ;
    rdfs:comment
        "Número ordinal na sequência da série (1 = primeira edição). Substitui ph:EventSeriesSequenceNumber."@pt ;
    rdfs:domain ph:SeriesEdition ;
    rdfs:range xsd:positiveInteger ;
    rdfs:subPropertyOf schema:position .

ph:EventSeriesSequenceNumber a owl:DatatypeProperty ;
    rdfs:label "posição na série (legado)"@pt ;
    rdfs:comment
        "Nome legado em PascalCase (próprio de classes, não propriedades). Preferir ph:sequenceInSeries."@pt ;
    owl:equivalentProperty ph:sequenceInSeries ;
    owl:deprecated true .
