# Score semantics

> The numbers the API returns are not probabilities: each one carries a class that says what it measures, what it can be compared with and what it cannot.

The API returns around twenty numbers between 0 and 1, under three names: `confidence`, `strength` and `value`. They look like confidence levels; none of them is one. This page explains what each number measures, what it may be compared with, and what to read instead when you need to decide.

## What these numbers are not

No number returned by the API is a probability. A `confidence.entity` of 0.85 does not mean "an 85 % chance that the entity is right"; a `match_strength.value` of 0.5 does not mean "one chance in two". The contract says so in two ways:

- the `calibration` field, carried by every score object (`confidence`, `match_strength`, `provenance`, `cadence_detail`, `calendar_pattern`, `lifecycle`), is always `non_calibree`: none of these values has been related to an observed frequency on a reference corpus;
- the `x-noryo-score` extension, carried by every number in the contract that looks like a confidence, always declares its `class`, `calibration: non_calibree` and `is_probability: false`, and depending on the number `comparable_within`, `not_comparable_with`, `formula`, `source` or `duplicate_of`.

`non_calibree` is the only value the contract admits in this version, and that is a constraint, not a convention. These annotations can be read at `https://api.noryo.app/openapi.json` or in the [generated reference](https://developer.noryo.app/en-GB/transaction-intelligence/reference).

## The six classes

The classes are described under `components.x-noryo-score-classes` in the contract, exactly as served:

| Class | Meaning | Monotonic | Use |
| --- | --- | --- | --- |
| `declared_constant` (constante declaree) | Une valeur POSEE par la personne qui a ecrit le motif ou la regle, lue telle quelle. Elle ne resulte d'aucun calcul sur la transaction : toute transaction qui touche le meme motif recoit le meme nombre. Mesure sur 644 libelles : la precision n'est PAS monotone en ce nombre (0,85 -> 0,86 ; 0,88 -> 0,33 ; 0,92 -> 0,25 ; 0,97 -> 0,79). | false | Ne l'utilisez ni comme seuil ni comme rang. Utilisez `policy.outcome`. |
| `evidence_ratio` (part des signaux corroborants) | `present / possible` : combien de signaux corroborants ce TYPE sait produire, et combien sont effectivement la. Le denominateur DEPEND du type — 5 pour `refund_of`, 4 pour `reversal_of`, 2 pour `transfer_pair`, 5 criteres heterogenes pour une serie — et `corroborations` le rend a cote, parce que sans lui le nombre ne se lit pas. | within_type | Comparable entre deux objets du MEME type et de la meme `detector_version`. Jamais entre un `refund_of` et un `transfer_pair`, jamais entre une relation et une serie. |
| `calendar_fill` (taux de remplissage calendaire) | La part des periodes calendaires de l'etendue observee qui portent au moins une occurrence, pour l'hypothese de cadence retenue. ★ `0` signifie « aucune hypothese calendaire n'a ete retenue » (cadence `irregular_recurring`), pas « faible ». | within_cadence | Comparable entre deux series de MEME cadence. Une hebdomadaire sur 8 semaines et une annuelle sur 2 ans ne se comparent pas. |
| `anchor_stability` (stabilite de l'ancrage) | Un jeu de constantes par TYPE d'ancrage (1,0 pour un jour fixe de semaine, 0,9 pour un jour ajuste au jour ouvre) et deux formules lineaires sur l'etendue des jours pour `fixed_day_of_month` et `flexible_calendar_window`. `0` pour `unknown`. ★ `flexible_calendar_window` a DEUX regles selon la cadence : une constante 0,3 sans `day_range` en cadence `weekly`/`biweekly`, et `1 - etendue/10` (plancher 0,1) avec `day_range` en cadence mensuelle ou plus longue. | within_anchor_type_and_cadence | Comparable entre deux series de MEME type d'ancrage ET de MEME cadence. Un 0,33 `fixed_day_of_month` et un 0,3 `flexible_calendar_window` ne disent pas la meme chose ; un 0,3 `flexible_calendar_window` hebdomadaire et un 0,3 mensuel non plus. |
| `authority_tiebreak` (departage a autorite egale) | La confiance declaree de la source d'une activite. Elle n'entre en jeu qu'APRES la nature d'autorite (`curated` > `deterministic` > `provider` > `inferred` > `participant`), pour departager deux sources de meme nature. | within_nature | Ne compare jamais deux natures : un 0,95 `inferred` ne l'emporte pas sur un 0,90 `curated`. |
| `text_coverage` (couverture textuelle) | Un rapport de longueurs : la part du libelle utile que le motif apparie couvre, ou la part d'un motif compact qu'un fragment tronque recouvre. C'est une propriete du TEXTE, pas une preuve d'identite : un seuil dessus couterait 61 justes pour ecarter 17 faux. | true | Comparable entre libelles. N'en faites pas un seuil d'identite. |

## Where each class applies

| Field | Class | Comparable with | Never comparable with |
| --- | --- | --- | --- |
| `resolution.confidence.entity`, `.counterparty`, `.transaction_type`, `.category`, `.location` | `declared_constant` | nothing | any other score |
| `relationships[].match_strength.value` | `evidence_ratio` | a relationship of the same `type` and the same `detector_version` | a relationship of another type, `series[].match_strength` |
| `series[].match_strength.value` | `evidence_ratio` | a series of the same `detector_version` | `relationships[].match_strength`, `cadence_detail.strength` |
| `series[].cadence_detail.strength` (repeated in `lifecycle.cadence_strength`) | `calendar_fill` | a series of the same `cadence` | another cadence, `match_strength`, `calendar_pattern.strength` |
| `series[].calendar_pattern.strength` (repeated in `lifecycle.calendar_anchor`) | `anchor_stability` | a series of the same anchor `type` and the same cadence | another anchor type, another cadence, `cadence_detail.strength`, `match_strength` |
| `resolution.counterparty.semantics.provenance.strength` | `authority_tiebreak` | a source of the same `nature` | another nature of authority, any other score |
| `resolution.counterparty.evidence.coverage`, `.covered` | `text_coverage` | other descriptors (`coverage`), other truncated cases (`covered`) | any identity or series score (`coverage`); any other score (`covered`) |

`lifecycle.cadence_strength` and `lifecycle.calendar_anchor` are not additional scores: they are copies (`duplicate_of` in the contract), returned so that the lifecycle can be read on its own. The name `confidence` under `resolution` is kept for `ti_v1` compatibility: read it as "declared strength of the pattern", never as a confidence.

## Comparing: only within a class and a type

A number may only be compared with another number of the same class, produced for the same kind of object, by the same detector version. Take `evidence_ratio`: the value is `present / possible`, and the denominator depends on the type (5 possible signals for `refund_of`, 4 for `reversal_of`, 2 for `transfer_pair`, 5 heterogeneous criteria for a series). The same `0.5` is therefore one signal out of two for a transfer pair and two out of four for a reversal:

```json
{
  "type": "transfer_pair",
  "match_strength": {
    "value": 0.5,
    "calibration": "non_calibree",
    "corroborations": { "present": 1, "possible": 2 }
  }
}
```

```json
{
  "type": "reversal_of",
  "match_strength": {
    "value": 0.5,
    "calibration": "non_calibree",
    "corroborations": { "present": 2, "possible": 4 }
  }
}
```

This is why `corroborations` is returned next to `value`: without `present` and `possible`, the number cannot be read. A series `match_strength` counts five criteria (calendar cadence, fixed or narrow amount, at least three occurrences, known entity, specialised type): same name, different measure.

The other classes follow the same logic:

- `calendar_fill`: between series of the same cadence. A weekly series over eight weeks and a yearly one over two years cannot be compared. `0` means "no calendar hypothesis was retained" (cadence `irregular_recurring`), not "weak".
- `anchor_stability`: between series of the same anchor type and the same cadence. A `0.33` on `fixed_day_of_month` and a `0.3` on `flexible_calendar_window` do not say the same thing, and `flexible_calendar_window` changes its rule with the cadence. `0` corresponds to the `unknown` type.
- `authority_tiebreak`: only after the nature of authority. The order `curated`, `deterministic`, `provider`, `inferred`, `participant` is declared, not derived from a number: a `0.95` of nature `inferred` does not beat a `0.90` of nature `curated`. `strength` only separates two sources of the same `nature`.
- `text_coverage`: a property of the text, the share of the useful descriptor covered by the pattern (`coverage`) or the share of a compact pattern covered by a truncated fragment (`covered`, the `tronque` route). Comparable between descriptors, it is not proof of identity.

> **Warning** Never rank two objects of different types by their score, and never add up scores of different classes.

## Decide with `policy.outcome`, not with a threshold

The `declared_constant` class is the most tempting one to use as a threshold. The five numbers under `resolution.confidence` are constants set by the person who wrote the pattern or the rule: every transaction that hits the same pattern receives the same number. Their class is marked `monotonic: false` in the contract: the measured precision, documented in the class description, is not monotone in this number, and a threshold on it does not separate right from wrong.

Read `resolution.policy` instead:

- `outcome` is `resolved` (every dimension the archetype requires is present), `partial` (the nature is known, a required dimension is missing, or signals contradict each other) or `unresolved` (even the nature of the transaction is unknown);
- `missing` names the absent dimensions: `counterparty_entity`, `counterparty_type`, `transaction_type` or `resolution_policy`;
- `archetype` says what "resolved" requires here: a transfer to a person is `resolved` without any entity, a cash withdrawal without a merchant, a card purchase requires its counterparty.

```json
{
  "policy": {
    "outcome": "partial",
    "archetype": "achat_carte",
    "missing": ["counterparty_entity"],
    "policy_version": "ti_resolution_v1"
  },
  "counterparty": null,
  "transaction_type": { "canonical": "card_purchase", "evidence": "rail_rule", "contradiction": null },
  "confidence": {
    "entity": 0,
    "counterparty": 0,
    "transaction_type": 0.95,
    "category": 0,
    "location": 0,
    "calibration": "non_calibree"
  }
}
```

Here `confidence.transaction_type` is `0.95` even though the transaction is not resolved: the type is known from a rail rule, the counterparty is not. It is `policy.outcome` and `policy.missing` that tell you so, not the numbers.

`0` has a precise meaning per field: abstention for `entity`, unknown role for `counterparty`, conflict with no common ancestor for `transaction_type`; `location` is always `0` in this version. When `signal_conflicts` is not empty, `outcome` is `partial`: the engine declines to conclude. An identity abstention (`identity_abstention`), by contrast, leaves `outcome` at `resolved` if the archetype did not require an entity.

## Keeping a number: keep the version with it

If you store a value, store the version that produced it: `resolution.versions` (`engine`, `resolution_policy`, `calibration`) for the resolution, `detector_version` for a relationship or a series. The same value, under another version, may no longer measure the same thing.

## Statistical calibration: not available

No statistical calibration is available in this version. When a real calibration exists, it will enter through a declared migration, together with the corpus that supports it. Until then, the `confidence` and `strength` values are deterministic evidence strengths: the same pattern, signals and version always give the same number. Decide on `policy.outcome`, on the enums (`status`, `lifecycle.state`, `identity.status`) and on the evidence (`evidence`, `corroborations`, `signals.used`).

## Further reading

- [Enrich a transaction](https://developer.noryo.app/en-GB/transaction-intelligence/enrich-a-transaction)
- [Relationships](https://developer.noryo.app/en-GB/transaction-intelligence/relationships)
- [Recurring series](https://developer.noryo.app/en-GB/transaction-intelligence/recurring-series)
- [Counterparties and entities](https://developer.noryo.app/en-GB/transaction-intelligence/counterparties-and-entities)
- [Generated reference](https://developer.noryo.app/en-GB/transaction-intelligence/reference), every field with its `x-noryo-score`

---

Canonical page: https://developer.noryo.app/en-GB/transaction-intelligence/score-semantics
