# =====================================================================
#  Jev Ontology  (jev:)
#  An RDF rendering of TypeSafe AI's Jev / System One vocabulary.
#
#  Layer 2 of 3:
#      decision-making.ttl   general semantics of decisions
#      jev.ttl               native Jev / System One vocabulary  <- THIS FILE
#      inference.ttl         semantic bridge between the two
#
#  Governing principle: this file describes Jev AS JEV. Every term below
#  corresponds to something that actually exists on the System One wire
#  (POST https://api.typesafe.ai/v1/systemone) or in the published SDKs.
#  Nothing is added because the decision model would like it to be there,
#  and NO dm: term appears anywhere in this file. It imports nothing and
#  is usable on its own.
#
#  Source of the modelled API surface: docs.typesafe.ai (Primitives,
#  Choice, Score, Noul, State, Confidence), retrieved 2026-09-20.
# =====================================================================

@prefix jev:     <https://www.openlinksw.com/ontology/jev#> .
@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 dcterms: <http://purl.org/dc/terms/> .
@prefix prov:    <http://www.w3.org/ns/prov#> .
@prefix sh:      <http://www.w3.org/ns/shacl#> .
@prefix vann:    <http://purl.org/vocab/vann/> .

# ---------------------------------------------------------------------
# Ontology header
# ---------------------------------------------------------------------

<https://www.openlinksw.com/ontology/jev>
    a owl:Ontology ;
    owl:versionIRI <https://www.openlinksw.com/ontology/jev/1.0.0> ;
    dcterms:title "Jev Ontology"@en ;
    dcterms:description """An RDF/OWL vocabulary for TypeSafe AI's Jev, the first System One model: a model that evaluates typed questions against a state and returns typed, calibrated answers instead of generated text.

The vocabulary covers the three Jev question primitives (Choice, Score, Noul), the answers they return, the evaluation call that carries them, and the model that answers them. It is deliberately confined to Jev's own API surface so that a Jev graph can be written, read and validated without committing to any other ontology."""@en ;
    dcterms:creator <https://www.openlinksw.com#this>, <https://github.com/danielhmills#this> ;
    dcterms:created "2026-09-20"^^xsd:date ;
    dcterms:license <https://creativecommons.org/licenses/by/4.0/> ;
    dcterms:source <https://docs.typesafe.ai/primitives> ;
    vann:preferredNamespacePrefix "jev" ;
    vann:preferredNamespaceUri "https://www.openlinksw.com/ontology/jev#" ;
    rdfs:seeAlso <https://www.openlinksw.com/ontology/jev/inference> ;
    skos:scopeNote """Independence note. This ontology is published by OpenLink Software to describe a third-party API; it is not a TypeSafe AI publication. Its alignment with the general decision model lives in a separate bridge ontology, <https://www.openlinksw.com/ontology/jev/inference>, so that neither file has to collapse into the other.

Naming note. Property names follow the wire, not the decision model: jev:instructions rather than jev:question, jev:level rather than jev:scaleValue, jev:noul rather than jev:probability. An engineer reading a Jev graph next to a Jev request body should not have to translate, and a reader must not be able to mistake a Jev term for a generic one. The translation is exactly what inference.ttl is for."""@en .

# =====================================================================
# 1. QUESTIONS -- the three Jev primitives
# =====================================================================

jev:Question
    a owl:Class ;
    rdfs:label "Question"@en ;
    rdfs:comment "A typed question evaluated against a state by a System One model. Every question has an id, a type, instructions, and -- for Choice and Score -- criteria."@en ;
    skos:scopeNote "Questions in one evaluation are answered in parallel and in isolation: no answer becomes context for another. That independence is the reason a question is modelled as a standalone resource rather than as part of a request."@en .

jev:Choice
    a owl:Class ;
    rdfs:subClassOf jev:Question ;
    rdfs:label "Choice"@en ;
    rdfs:comment "A Jev question that selects one option from a defined set. The answer carries the selected option, a probability for every option, and a confidence."@en ;
    skos:scopeNote "Serialises as {\"type\": \"choice\", \"instructions\": ..., \"criteria\": {key: description, ...}}. The API accepts up to 255 options."@en .

jev:Score
    a owl:Class ;
    rdfs:subClassOf jev:Question ;
    rdfs:label "Score"@en ;
    rdfs:comment "A Jev question that rates the state against ordered, descriptive levels. The answer carries a position on the level index line, a probability for every level, a legend and a confidence."@en ;
    skos:scopeNote "Serialises as {\"type\": \"score\", \"instructions\": ..., \"criteria\": [level0, level1, ...]}. At least two levels, at most ten. The returned score is the probability-weighted mean of the level indices, so it may fall between two levels."@en .

jev:Noul
    a owl:Class ;
    rdfs:subClassOf jev:Question ;
    rdfs:label "Noul"@en ;
    rdfs:comment "A Jev question that evaluates a yes/no proposition and returns the probability that the answer is yes."@en ;
    skos:scopeNote "Serialises as {\"type\": \"noul\", \"instructions\": ..., \"criteria\": {\"true\": ..., \"false\": ...}}, where criteria is optional. A Noul answer has no separate confidence: with only two outcomes, the single probability describes the distribution completely."@en .

jev:Choice owl:disjointWith jev:Score, jev:Noul .
jev:Score  owl:disjointWith jev:Noul .

jev:Level
    a owl:Class ;
    rdfs:label "Level"@en ;
    rdfs:comment "One ordered level of a Jev Score question: a point on the spectrum, described in words, whose number is its position in the criteria array."@en ;
    skos:scopeNote "Levels are numbered from 0. The number is the position in the serialised array, so a client orders the criteria by jev:levelIndex when it builds the request."@en .

# ---------------------------------------------------------------------
# 1.1 Question properties
# ---------------------------------------------------------------------

jev:questionId
    a owl:DatatypeProperty ;
    rdfs:subPropertyOf dcterms:identifier ;
    rdfs:label "question id"@en ;
    rdfs:domain jev:Question ;
    rdfs:range xsd:string ;
    rdfs:comment "The key under which this question is sent and its answer returned, e.g. \"department\"."@en ;
    skos:scopeNote "The id is never shown to the model. It is an application-side handle, which is why the complete question must still be written out in jev:instructions."@en .

jev:instructions
    a owl:DatatypeProperty ;
    rdfs:label "instructions"@en ;
    rdfs:domain jev:Question ;
    rdfs:comment "The question the model answers, or the statement it judges."@en ;
    skos:scopeNote "A plain string suits most questions. The API also accepts an object or array, carried here as an rdf:JSON literal, when part of the question is supporting data or is assembled in code."@en .

jev:option
    a owl:ObjectProperty ;
    rdfs:label "option"@en ;
    rdfs:domain jev:Choice ;
    rdfs:comment "An option of this Choice question, identified by IRI."@en ;
    skos:scopeNote """No rdfs:range, so that an option may be any resource the user already governs -- a skos:Concept, an owl:NamedIndividual, an owl:Class -- and acquires no Jev typing from being used here.

The option's description, which Jev sends as the criteria value, is NOT restated on the question: a client reads it from the option's own skos:definition, rdfs:comment or dcterms:description. One curated description therefore serves every request, and cannot drift between two questions that use the same option."""@en .

jev:key
    a owl:DatatypeProperty ;
    rdfs:label "key"@en ;
    rdfs:range xsd:string ;
    rdfs:comment "The literal key to use for this option or level when the question is serialised to JSON."@en ;
    skos:scopeNote "No rdfs:domain: the subject is an option or level resource from the user's own ontology, which must not be retyped by carrying a serialisation hint. Optional -- a client may derive the key from skos:notation or from the IRI's local name. Wire keys live in this ontology and not in the general decision model because they are an artefact of Jev's JSON encoding, not of the decision being made."@en .

jev:level
    a owl:ObjectProperty ;
    rdfs:label "level"@en ;
    rdfs:domain jev:Score ;
    rdfs:comment "A level of this Score question, identified by IRI."@en ;
    skos:scopeNote "No rdfs:range: a level may be a jev:Level minted for this question, or a resource from a shared ordinal scale the organisation already maintains. Using the latter is what lets one definition of severity or risk drive both a Jev Score and every other system that reads that scale."@en .

jev:levelIndex
    a owl:DatatypeProperty ;
    rdfs:label "level index"@en ;
    rdfs:range xsd:nonNegativeInteger ;
    rdfs:comment "The position of a level in the serialised criteria array, counting from 0."@en ;
    skos:scopeNote "No rdfs:domain, for the same reason as jev:key: the subject may be an externally governed level resource."@en .

jev:trueCriterion
    a owl:DatatypeProperty ;
    rdfs:label "true criterion"@en ;
    rdfs:domain jev:Noul ;
    rdfs:comment "A description of what counts as yes for this Noul question."@en ;
    skos:scopeNote "Optional. Used when the boundary between yes and no is subtle enough that the instruction alone leaves it ambiguous."@en .

jev:falseCriterion
    a owl:DatatypeProperty ;
    rdfs:label "false criterion"@en ;
    rdfs:domain jev:Noul ;
    rdfs:comment "A description of what counts as no for this Noul question."@en .

# =====================================================================
# 2. EVALUATION -- one System One call
# =====================================================================

jev:Evaluation
    a owl:Class ;
    rdfs:subClassOf prov:Activity ;
    rdfs:label "Evaluation"@en ;
    rdfs:comment "One call to a System One model: one state, one model, and one or more questions answered against it."@en ;
    skos:scopeNote "The call is modelled explicitly because it is the unit of cost and latency in Jev. Every question in a call sees the same state and is evaluated in parallel, so batching many questions into one evaluation is close to free -- and a graph that records evaluations can prove that, question by question, in SPARQL."@en .

jev:Model
    a owl:Class ;
    rdfs:label "Model"@en ;
    rdfs:comment "A System One model that answers evaluations, e.g. jev-1.13.0."@en ;
    skos:scopeNote "Requests normally name the moving alias jev-latest while responses name the resolved version. Record the resolved version: an answer is only reproducible against the model that actually produced it."@en .

jev:modelIdentifier
    a owl:DatatypeProperty ;
    rdfs:subPropertyOf dcterms:identifier ;
    rdfs:label "model identifier"@en ;
    rdfs:domain jev:Model ;
    rdfs:range xsd:string ;
    rdfs:comment "The model string used by the API, e.g. \"jev-latest\" or \"jev-1.13.0\"."@en .

jev:state
    a owl:DatatypeProperty ;
    rdfs:label "state"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:comment "The content evaluated in this call: the serialised string, JSON object or JSON array sent in the state field."@en ;
    skos:scopeNote "Jev accepts text only. Carry a structured state as an rdf:JSON literal so the exact bytes the model saw are recoverable."@en .

jev:stateOf
    a owl:ObjectProperty ;
    rdfs:label "state of"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:comment "The resource the state was built from -- the ticket, document or record that this call is about."@en ;
    skos:scopeNote "No rdfs:range: the subject matter belongs to the user's own ontology. This is the property that reconnects a flat text state to the graph it came from, which is what makes an answer checkable against the graph afterwards."@en .

jev:model
    a owl:ObjectProperty ;
    rdfs:label "model"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:range jev:Model ;
    rdfs:comment "The System One model that answered this call."@en .

jev:asked
    a owl:ObjectProperty ;
    rdfs:label "asked"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:range jev:Question ;
    rdfs:comment "A question put in this call."@en .

jev:producedAnswer
    a owl:ObjectProperty ;
    owl:inverseOf jev:fromEvaluation ;
    rdfs:label "produced answer"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:range jev:Answer ;
    rdfs:comment "An answer returned by this call."@en .

jev:fromEvaluation
    a owl:ObjectProperty ;
    rdfs:label "from evaluation"@en ;
    rdfs:domain jev:Answer ;
    rdfs:range jev:Evaluation ;
    rdfs:comment "The call that returned this answer."@en .

jev:inputTokens
    a owl:DatatypeProperty ;
    rdfs:label "input tokens"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:range xsd:nonNegativeInteger ;
    rdfs:comment "Input tokens reported in the usage block of the response."@en .

jev:outputTokens
    a owl:DatatypeProperty ;
    rdfs:label "output tokens"@en ;
    rdfs:domain jev:Evaluation ;
    rdfs:range xsd:nonNegativeInteger ;
    rdfs:comment "Output tokens reported in the usage block of the response."@en .

# =====================================================================
# 3. ANSWERS -- what Jev returns
# =====================================================================

jev:Answer
    a owl:Class ;
    rdfs:subClassOf prov:Entity ;
    rdfs:label "Answer"@en ;
    rdfs:comment "A typed answer returned for one question in one evaluation."@en .

jev:ChoiceAnswer
    a owl:Class ;
    rdfs:subClassOf jev:Answer ;
    rdfs:label "Choice Answer"@en ;
    rdfs:comment "The answer to a Choice question: the selected option, the distribution over all options, and a confidence."@en .

jev:ScoreAnswer
    a owl:Class ;
    rdfs:subClassOf jev:Answer ;
    rdfs:label "Score Answer"@en ;
    rdfs:comment "The answer to a Score question: a position on the level index line, the distribution over all levels, and a confidence."@en .

jev:NoulAnswer
    a owl:Class ;
    rdfs:subClassOf jev:Answer ;
    rdfs:label "Noul Answer"@en ;
    rdfs:comment "The answer to a Noul question: the probability that the proposition is true."@en .

jev:ChoiceAnswer owl:disjointWith jev:ScoreAnswer, jev:NoulAnswer .
jev:ScoreAnswer  owl:disjointWith jev:NoulAnswer .

jev:ProbabilityEntry
    a owl:Class ;
    rdfs:label "Probability Entry"@en ;
    rdfs:comment "One entry of the probabilities map of a Choice or Score answer: an outcome and the probability mass assigned to it."@en ;
    skos:scopeNote "Modelled as a named resource rather than a blank node so that individual entries can be cited, indexed and compared across runs."@en .

jev:answerTo
    a owl:ObjectProperty ;
    rdfs:label "answer to"@en ;
    rdfs:domain jev:Answer ;
    rdfs:range jev:Question ;
    rdfs:comment "The question this answer answers."@en .

jev:choice
    a owl:ObjectProperty ;
    rdfs:label "choice"@en ;
    rdfs:domain jev:ChoiceAnswer ;
    rdfs:comment "The selected option: the one carrying the highest probability."@en ;
    skos:scopeNote "No rdfs:range: the value is the option resource itself, exactly as supplied to the question. Keeping the IRI rather than the wire key is what lets an answer join straight back into the graph."@en .

jev:score
    a owl:DatatypeProperty ;
    rdfs:label "score"@en ;
    rdfs:domain jev:ScoreAnswer ;
    rdfs:range xsd:decimal ;
    rdfs:comment "The position on the level index line, from 0 to the highest level number. It is the sum of each level number weighted by its probability, so it may fall between two levels."@en .

jev:noul
    a owl:DatatypeProperty ;
    rdfs:label "noul"@en ;
    rdfs:domain jev:NoulAnswer ;
    rdfs:range xsd:decimal ;
    rdfs:comment "The probability that the answer to the question is yes: near 1 a strong yes, near 0 a strong no, near 0.5 undecided."@en ;
    skos:scopeNote """This is a PROBABILITY, not a confidence and not a degree of the property asked about. A Noul of 0.5 means the model divides its belief evenly between yes and no; it does not mean 'medium'. Questions about degree belong in a jev:Score, whose levels the model actually judges."""@en .

jev:confidence
    a owl:DatatypeProperty ;
    rdfs:label "confidence"@en ;
    rdfs:range xsd:decimal ;
    rdfs:comment "How peaked the answer's probability distribution is, from 0 to 1. Reported for Choice and Score answers only."@en ;
    skos:scopeNote "No rdfs:domain, because the property is shared by jev:ChoiceAnswer and jev:ScoreAnswer but is absent from jev:NoulAnswer; declaring jev:Answer as the domain would suggest a Noul could carry one."@en .

jev:probabilities
    a owl:ObjectProperty ;
    rdfs:label "probabilities"@en ;
    rdfs:range jev:ProbabilityEntry ;
    rdfs:comment "An entry of this answer's probability distribution. Named for the wire field; in RDF the map is a set of entries rather than one object."@en ;
    skos:scopeNote "No rdfs:domain, for the same reason as jev:confidence: Choice and Score answers carry a distribution, Noul answers do not."@en .

jev:outcome
    a owl:ObjectProperty ;
    rdfs:label "outcome"@en ;
    rdfs:domain jev:ProbabilityEntry ;
    rdfs:comment "The option or level this entry scores."@en ;
    skos:scopeNote "No rdfs:range: the outcome is the option or level resource itself."@en .

jev:probability
    a owl:DatatypeProperty ;
    rdfs:label "probability"@en ;
    rdfs:domain jev:ProbabilityEntry ;
    rdfs:range xsd:decimal ;
    rdfs:comment "The probability mass on this entry's outcome. Masses within one answer sum to 1."@en .

# Note on the legend field. A Score response repeats each level number and
# its description under "legend". No jev:legend property is defined: in RDF
# the level resources carry their own descriptions and their own
# jev:levelIndex, so the legend is already present and would be duplication.

# =====================================================================
# 4. SHACL -- request-shape validation before a call is made
#    These shapes encode documented API limits, so a malformed question
#    can be caught in the graph rather than by an HTTP 422.
# =====================================================================

jev:ChoiceShape
    a sh:NodeShape ;
    rdfs:label "Choice question shape"@en ;
    sh:targetClass jev:Choice ;
    sh:property jev:ChoiceInstructionsConstraint, jev:ChoiceOptionsConstraint .

jev:ChoiceInstructionsConstraint
    a sh:PropertyShape ;
    sh:path jev:instructions ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:message "A Choice question needs exactly one instructions value."@en .

jev:ChoiceOptionsConstraint
    a sh:PropertyShape ;
    sh:path jev:option ;
    sh:nodeKind sh:IRI ;
    sh:minCount 2 ;
    sh:maxCount 255 ;
    sh:message "A Choice question needs between 2 and 255 options, each identified by IRI."@en .

jev:ScoreShape
    a sh:NodeShape ;
    rdfs:label "Score question shape"@en ;
    sh:targetClass jev:Score ;
    sh:property jev:ScoreInstructionsConstraint, jev:ScoreLevelsConstraint .

jev:ScoreInstructionsConstraint
    a sh:PropertyShape ;
    sh:path jev:instructions ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:message "A Score question needs exactly one instructions value."@en .

jev:ScoreLevelsConstraint
    a sh:PropertyShape ;
    sh:path jev:level ;
    sh:nodeKind sh:IRI ;
    sh:minCount 2 ;
    sh:maxCount 10 ;
    sh:message "A Score question needs between 2 and 10 ordered levels, each identified by IRI."@en .

jev:NoulShape
    a sh:NodeShape ;
    rdfs:label "Noul question shape"@en ;
    sh:targetClass jev:Noul ;
    sh:property jev:NoulInstructionsConstraint, jev:NoulNoOptionsConstraint .

jev:NoulInstructionsConstraint
    a sh:PropertyShape ;
    sh:path jev:instructions ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:message "A Noul question needs exactly one instructions value."@en .

jev:NoulNoOptionsConstraint
    a sh:PropertyShape ;
    sh:path jev:option ;
    sh:maxCount 0 ;
    sh:message "A Noul question takes no options: its outcomes are yes and no. Use jev:trueCriterion and jev:falseCriterion to describe them."@en .

jev:NoulAnswerShape
    a sh:NodeShape ;
    rdfs:label "Noul answer shape"@en ;
    sh:targetClass jev:NoulAnswer ;
    sh:property jev:NoulValueConstraint, jev:NoulNoConfidenceConstraint .

jev:NoulValueConstraint
    a sh:PropertyShape ;
    sh:path jev:noul ;
    sh:datatype xsd:decimal ;
    sh:minInclusive 0.0 ;
    sh:maxInclusive 1.0 ;
    sh:minCount 1 ;
    sh:maxCount 1 ;
    sh:message "A Noul answer carries exactly one probability between 0 and 1."@en .

jev:NoulNoConfidenceConstraint
    a sh:PropertyShape ;
    sh:path jev:confidence ;
    sh:maxCount 0 ;
    sh:message "A Noul answer has no confidence value; the single probability describes its two-outcome distribution completely."@en .
