Règles de risque
Déclarez un score de risque expliqué et dépendant du contexte pour les objets d'un type — un ensemble fermé de raisons déterministes, chacune citant les objets qui l'ont rendue vraie.
Les règles de risque sont créées dans une extension d'ontologie aux côtés de vos Types d'objets, Types de liens, Liaisons et règles de surveillance. Cette capacité est provisoire : la forme de la règle peut changer avant d'être déclarée stable.
Une règle de risque attribue un score à chaque objet d'un type cible — un navire, une société, une installation — et dit pourquoi. Chaque raison vérifiée ajoute son poids déclaré et cite les objets qui l'ont rendue vraie, de sorte que le score est exactement la somme des raisons listées. Aucun modèle n'intervient : les mêmes faits donnent toujours la même évaluation.
Chaque évaluation est écrite dans l'ontologie comme un objet dérivé d'un type que vous déclarez. Elle se lit comme n'importe quel autre objet, et un lecteur qui n'est pas habilité pour chacun des faits qui l'ont alimentée ne reçoit rien — pas une erreur.
Déclarer une règle de risque
Ajoutez un tableau riskRules à votre appel defineOntology :
import { defineOntology } from '@scrydon/sdk-authoring/ontologies'
export default defineOntology({
id: 'maritime-picture',
version: '1.0.0',
displayName: 'Maritime picture',
// ... object types (Vessel, PortCall, RiskAssessment, ...), link types, bindings, watch rules ...
riskRules: [
{
slug: 'vessel-risk',
displayName: 'Vessel risk',
version: '1.0.0',
target: { objectTypeSlug: 'Vessel' },
reasons: [
{
id: 'flag-hopping',
label: 'Flag changed twice in 12 months',
weight: 20,
evaluator: {
kind: 'propertyChanges',
viaLinkTypeSlug: 'SourceRecordOfVessel',
property: 'flag',
timeProperty: 'sourceAsOf',
windowSeconds: 31_536_000,
minChanges: 2,
},
},
{
id: 'sanctioned-port-call',
label: 'Port call at a sanctioned terminal',
weight: 30,
evaluator: {
kind: 'linkedPropertyIn',
viaLinkTypeSlug: 'PortCallOfVessel',
property: 'portLocode',
values: ['IRBND'],
where: { basis: 'observed' },
},
},
{
id: 'port-call-mismatch',
label: 'Declared port calls disagree with what was observed',
weight: 15,
evaluator: {
kind: 'declaredObservedMismatch',
viaLinkTypeSlug: 'PortCallOfVessel',
basisProperty: 'basis',
declaredValue: 'declared',
observedValue: 'observed',
alignProperty: 'sequence',
compareProperty: 'portLocode',
timeProperty: 'arrivedAt',
},
},
{
id: 'ais-gap',
label: 'AIS gap',
weight: 25,
evaluator: {
kind: 'detectionWithin',
objectTypeSlug: 'DarkVesselDetection',
timeProperty: 'firedAt',
windowSeconds: 86_400,
evidenceProperty: 'evidence',
evidenceKey: 'entityId',
subjectProperty: 'mmsi',
},
},
],
// The same vessel scores higher inside a zone of interest.
context: {
area: { objectTypeSlug: 'ZoneOfInterest', areaIds: ['be-eez'] },
position: { latProperty: 'lastLatitude', lngProperty: 'lastLongitude' },
multiplier: 1.5,
},
// Which sources may feed the score (a filter, see below).
sources: { property: 'sourceSystem', allow: ['LLOYDS', 'GISIS', 'EMSA'], unattributed: 'include' },
freshness: { maxAgeSeconds: 604_800 },
bands: [
{ name: 'low', minScore: 0 },
{ name: 'elevated', minScore: 30 },
{ name: 'high', minScore: 60 },
],
emit: {
objectTypeSlug: 'RiskAssessment',
clearanceCeiling: { schemeId: 'default', rank: 2 },
},
},
],
})Référence des champs
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
slug | string | Oui | Identifiant stable, utilisé comme nom de la déclaration SCHEMA_RISK_RULE émise et enregistré sur chaque évaluation. |
displayName | string (1–100 caractères) | Oui | Nom lisible. |
description | string (≤500 caractères) | Non | Description plus longue. |
enabled | boolean | Non | Vaut true par défaut. Une règle désactivée est signalée et n'est pas évaluée. |
version | string | Oui | Votre version de la règle, apposée sur chaque évaluation sous ruleVersion. |
target.objectTypeSlug | string | Oui | Le type dont les objets sont évalués. |
reasons | tableau (1–32) | Oui | Les raisons, chacune avec un id unique en kebab-case, un label, un weight positif et un evaluator. |
context | objet | Non | Un multiplicateur (≥ 1) appliqué tant que l'objet se trouve dans l'une des zones d'intérêt listées. |
sources | objet | Oui | La liste des sources autorisées. |
freshness.maxAgeSeconds | number | Oui | Une preuve plus ancienne que ce délai abaisse la confiance de l'évaluation. Une position liée plus ancienne que ce délai ne place pas la cible. |
bands | tableau (1–8) | Oui | Tranches de score nommées. La première commence à 0 ; les seuils croissent strictement. |
emit.objectTypeSlug | string | Oui | Le type d'objet sous lequel chaque évaluation est écrite. Déclarez-le dans la même ontologie. |
emit.clearanceCeiling | { schemeId, rank } | Oui | Le marquage le plus élevé que la règle peut lire. Obligatoire — une règle agrège, elle échoue donc de façon fermée plutôt que de lire tout ce que l'appelant peut voir. |
Chaque type d'objet et type de lien nommé par une règle doit être déclaré dans la même ontologie, et chaque lien qu'une raison parcourt doit toucher le type cible. Une règle qui nomme un élément absent échoue à l'installation.
Évaluateurs
L'ensemble des évaluateurs est fermé. Chacun lit les objets liés à la cible (viaLinkTypeSlug) ou les détections d'un type, et est vérifié lorsque sa condition est remplie.
Un type de lien déclaré avec une join et sans liaison de lien est lu par sa jointure : les objets liés sont ceux de l'autre type dont la colonne de jointure est égale à celle de la cible. Une jointure avec normalize ou une extrémité "*" ne peut pas être lue ainsi ; une cible qui en dépend est signalée incomplete-read au lieu d'être notée.
kind | Vérifié lorsque | Cite |
|---|---|---|
propertyChanges | property a pris au moins minChanges nouvelles valeurs dans windowSeconds, les enregistrements liés étant ordonnés par timeProperty. Utilisez minChanges: 1 pour « a changé récemment ». | Les enregistrements de part et d'autre de chaque changement. |
linkedPropertyIn | La property d'un objet lié fait partie de values, après les égalités where optionnelles et la borne de récence optionnelle timeProperty + windowSeconds (déclarées ensemble). | Les objets correspondants. |
declaredObservedMismatch | Les objets liés dont basisProperty vaut declaredValue ne concordent pas avec ceux qui valent observedValue, alignés sur alignProperty et comparés sur compareProperty. Une entrée déclarée sans entrée observée, ou l'inverse, est une discordance. Les objets sans valeur d'alignement sont ignorés. | Les deux côtés de chaque discordance. |
detectionWithin | Une détection de objectTypeSlug s'est déclenchée dans windowSeconds (selon timeProperty) et sa liste evidenceProperty nomme la cible : un élément dont evidenceKey est égal à la subjectProperty de la cible. C'est la forme d'une détection de règle de surveillance. | Les détections correspondantes. |
Chaque raison cite au plus 16 objets. Tout fait lu par la règle compte quand même dans le marquage de l'évaluation, cité ou non.
Lecture des objets liés
Une raison dotée d'une fenêtre (propertyChanges, ou linkedPropertyIn avec timeProperty + windowSeconds) ne lit que les objets liés que son timeProperty date dans windowSeconds : une cible au long historique — un navire aux milliers de positions AIS — est lue une fenêtre à la fois. Les objets liés hors de toute fenêtre n'alimentent ni l'évaluation ni son marquage. Une lecture de liens renvoie au plus 500 objets, et une lecture de détections au plus 200 par type de détection. Lorsqu'une lecture s'arrête à cette limite, la cible est quand même évaluée si les objets lus tranchent la raison :
kind | Tranchée par une lecture arrêtée à sa limite lorsque |
|---|---|
linkedPropertyIn, detectionWithin | Elle a trouvé une correspondance. Sinon la cible est incomplete-read. |
propertyChanges | Les changements entre objets situés dans la fenêtre atteignent déjà minChanges. Sinon incomplete-read. Une fenêtre complète à un changement de minChanges est relue sans fenêtre, car l'enregistrement juste avant la fenêtre peut apporter ce changement. |
declaredObservedMismatch | Jamais : tous les objets liés sont nécessaires, donc plus de 500 donne incomplete-read. |
La fenêtre n'est appliquée par la lecture que pour un lien déclaré avec une join et sans liaison de lien. Un lien doté d'une liaison de lien est lu en entier, et la même règle tranche lorsque plus de 500 objets l'arrêtent.
Contexte
context.position place la cible soit par ses propres latProperty / lngProperty, soit — avec viaLinkTypeSlug et timeProperty — par le dernier objet lié qui n'est pas plus ancien que freshness.maxAgeSeconds. Seul ce dernier objet est lu : l'évaluation remonte depuis l'instant présent, en doublant à partir d'une minute, et ne lit donc jamais un long historique pour le trouver. Les zones sont des lignes de context.area.objectTypeSlug portant un areaId et une boîte minLatitude / maxLatitude / minLongitude / maxLongitude, la même forme de ligne qu'une règle de surveillance lit. Lorsque la cible se trouve dans l'une d'elles, la contribution de chaque raison est multipliée par multiplier.
Liste des sources autorisées
sources décide quels faits peuvent alimenter le score, selon la valeur de sources.property sur chaque fait. Un fait provenant d'une source absente de allow est exclu du score et du marquage. unattributed indique le sort d'un fait qui ne nomme aucune source (une détection, une ligne de zone) : include ou exclude.
La liste des sources autorisées est un filtre sur ce que lit cette règle, pas une contrainte d'usage appliquée. Un fait qu'elle exclut reste lisible par tous les autres chemins gouvernés. Ne comptez pas sur elle pour tenir les données d'une source à l'écart d'un lecteur.
L'évaluation
Chaque évaluation est un objet du type emit, une par (règle, objet cible). Une nouvelle évaluation remplace la précédente pour la même cible, il y a donc toujours exactement une évaluation en vigueur.
| Propriété | Description |
|---|---|
score | La somme de reasons[].contribution. |
band | La tranche la plus haute dont le score atteint le minScore. |
reasons[] | { reasonId, label, contribution, fresh, evidence[] } pour chaque raison vérifiée. contribution vaut weight × multiplier. Chaque preuve est { objectType, objectId, marking, observedAt }. |
context | { applied, multiplier, areaIds, evidence }, ou null si la règle ne déclare pas de contexte. |
confidence | La part des raisons données dont toutes les preuves sont datées et comprises dans freshness.maxAgeSeconds. Une preuve sans date compte comme non fraîche. Sans raison, elle vaut 1. |
ruleName, ruleVersion, evaluatedAt, subjectType, subjectId | Ce qui a été évalué, par quelle règle, et quand. |
Marquage. Une évaluation est marquée à la jointure de tous les faits qui l'ont alimentée et de la règle elle-même, jamais au-dessus de emit.clearanceCeiling, et jamais en dessous de l'évaluation qu'elle remplace. Un lecteur habilité pour tout cela la lit ; tout autre lecteur ne reçoit aucun objet.
Lancer une évaluation
Les évaluations sont produites lorsqu'une évaluation est lancée, et seulement alors : risk.assess ne peut pas être soumise via actions.submit, de sorte que chaque évaluation a été dérivée par une évaluation lancée.
La plateforme lance une évaluation pour chaque environnement d'espace de travail selon la même planification que les règles de surveillance, juste après les surveillances de cet environnement, afin qu'une raison detectionWithin voie les détections produites par ce passage. Elle s'exécute sous le compte d'exécution des surveillances de l'environnement, avec les mêmes effets : sans compte sélectionné, seules les règles dont le plafond est de rang 0 sont évaluées ; avec un compte, une règle dont le plafond dépasse le rang d'habilitation du compte est ignorée ; avec un compte qui ne remplit plus les conditions, aucune règle n'est évaluée et aucune ne se rabat sur l'identité de la plateforme. Une règle ignorée pour habilitation figure avec sa raison dans les résultats d'une évaluation à la demande ; le passage planifié ne la journalise pas.
Pour évaluer à la demande, appelez-la depuis le SDK d'ontologie :
const result = await ontology.riskAssessments.evaluate({ ontology: 'maritime-picture' })
// { evaluated, assessed, degradedReads, outcomes: [{ ruleName, objectId, assessed, reason?, assessmentId?, marking? }] }L'évaluation lit au emit.clearanceCeiling de chaque règle et ignore une règle dont votre habilitation n'atteint pas le plafond. Une évaluation identique à celle en vigueur n'est pas réécrite (reason: "unchanged"). Une cible dont les faits n'ont pas pu être lus entièrement n'est pas évaluée (reason: "incomplete-read") plutôt que de recevoir un score bas.