Skip to content

Classifier cascade

Eleven classifier nodes participate in the per-record DAG. Nine run concurrently in a parallel: collect placement; two run sequentially after the parallel because they read other classifiers' proposals. The conflict resolver reduces every non-sentinel proposal into a single winning state.classification.

The cascade is deterministic: same config + same record produce identical proposals, identical winner, identical PROV-O quads.

Opt-in

Classifiers are registered by the plugin via register(dispatcher). Each classifier is a generic ScalarNode subclass instantiated with domain config in the plugin. The plugin also authors its own per-record DAG (*.dag.jsonld) that chains only the classifiers it needs.

The nine parallel classifiers + two sequential classifiers + the conflict resolver are wired in SquashageRun.forRun(...) from the matching config slots.

Primary path — the discriminator

For targets where records carry a discriminator field (e.g. _type), the classify:discriminator node is the primary classification path. It reads the literal value at a configured JSON Pointer and uses it directly as the class name proposal — no per-class enumeration required.

classify:discriminatorparallel

Reads a configured JSON Pointer (default /_type) from the record. Uses the resolved string (after optional sanitization) as the className proposal. Open-world: any non-empty string value classifies without enumeration.

slot
classification.discriminator
priority
50
outputs
proposed | no-match

Config slot: classification.discriminator

json
{
  "discriminator": {
    "from":     "/_type",
    "sanitize": "pascalCase",
    "priority": 80
  }
}
FieldTypeDefaultDescription
fromstringrequiredJSON Pointer (RFC 6901) into the record.
fallbackstringPointer used when from is absent or non-string.
prioritynumber50Proposal priority; set higher than the other classifiers to ensure it wins on conflict.
sanitizestring"verbatim""verbatim" uses the value as-is; "pascalCase" and "kebabToPascal" split on [-_\s]+ and capitalize each segment.

When the pointer resolves to a non-empty string, a proposal is emitted with confidence: 1.0. When the pointer is absent or non-string, the node outputs no-match and the other classifiers may still produce a proposal.

Source: src/nodes/record/classifiers/DiscriminatorClassifierNode.ts

Parallel classifiers (9)

These run concurrently inside classify-all. Each writes to its own slot in state.proposals[<classifier-name>], so the writes are race-free. Each classifier's outputs (proposed / no-match) route to null within the parallel placement; the parallel's combined output (success) routes forward to the first sequential classifier.

classify:sourceparallel

Inspects the record's _source block; emits a __source__ metadata marker. Never proposes a class.

slot
classification.source
priority
0
outputs
proposed | no-match
classify:url-patternparallel

Compiled regex over _source.url (priority) or top-level url. Highest-priority matching pattern wins the slot.

slot
classification.urlPattern
priority
35
outputs
proposed | no-match

configuration →

classify:structuralparallel

Compiled JSON-pointer predicates over state.input via the Predicate engine.

slot
classification.structural
priority
10
outputs
proposed | no-match
classify:rulesparallel

Full decision-table predicates (equals, in, exists, range, length, regex, all/any composition). Same engine as structural; different config slot.

slot
classification.rules
priority
20
outputs
proposed | no-match
classify:schemaparallel

Per-class AJV validators loaded from JSON Schema files and compiled via services.ajv.

slot
classification.schemas
priority
30
outputs
proposed | no-match
classify:shacl-shapeparallel

SHACL NodeShape ABox validation against record-projected quads. Reads shapes from a Turtle file or services.ontology.shacl().

slot
classification.shaclShape
priority
45
outputs
proposed | no-match

configuration →

classify:property-fingerprintparallel

Jaccard similarity between the record's top-level key set and pre-compiled fingerprints. Confidence = the Jaccard score.

slot
classification.propertyFingerprint
priority
32
outputs
proposed | no-match

configuration →

classify:winknlp-entitiesparallel

winkNLP custom-entity pattern NER over configured prose fields. Patterns compiled once at construction.

slot
classification.winknlpEntities
priority
28
outputs
proposed | no-match

configuration →

Sequential post-parallel classifiers (2)

These read every other classifier's proposal, so they cannot run in parallel without race conditions. Order: classify:ontology runs first, then classify:taxonomic-narrowing.

classify:ontologysequential

Validates every other classifier's className against config.classes. Emits a __validation__ sentinel listing unknown classes.

slot
classification.ontologyClassifier
priority
0
outputs
validated | no-match
classify:taxonomic-narrowingsequential

Drops supertype proposals when a more-specific subtype is also present. Uses OWL subClassOf transitive closure built once at construction.

slot
classification.taxonomicNarrowing
priority
0
outputs
narrowed | no-op

configuration →

Conflict resolver

classify-conflict runs after the gate (record-health-gate) has confirmed at least one non-sentinel proposal exists. It implements the documented resolution algorithm:

  1. Filter sentinels (__source__, __validation__, __narrowing_applied__, unknown).
  2. If every surviving proposal agrees on a single className → that class wins; engine becomes the comma-joined unique sources.
  3. If multiple classes propose, find the highest priority. Single winner at the top → it wins. Tie → apply onConflict:
    • quarantine: bucket 'conflicts', exit via the quarantine path.
    • pickPriority: lexicographically first className wins; candidates lists all tied classes.
  4. Confidence comes from the winning proposal.

Configure via classification.conflict:

json
{
  "onConflict": "pickPriority",
  "evidence":   true
}

evidence: true concatenates every contributing proposal's reasons into the final state.classification.reasons. evidence: false keeps only the winner's first reason.

Sentinels

SentinelProducerMeaning
__source__classify:sourceThe record's _source block was inspected; preserved in evidence as provenance.
__validation__classify:ontologyOne or more classifiers proposed a class outside the known map.
__narrowing_applied__classify:taxonomic-narrowingSupertype proposals were dropped in favor of subtypes.

All three are filtered before conflict resolution and preserved in the final ClassificationEvidence.reasons array when conflict.evidence: true.

See also

Released under the MIT License.