---
title: How ontologies and filtering work
description: Every contract declares what it is about — and that declaration drives candidate ranking, draft alignment and brand defense, with filters that each own exactly one concern.
locale: en
version: 0.2.0
canonical: https://enterium-developer-docs.pages.dev/developers/how-it-works/ontologies-and-filtering/
---

> Documentation index: https://enterium-developer-docs.pages.dev/developers/llms.txt — every page of this portal has a Markdown twin at <route>.md; the frozen API contract is at https://enterium-developer-docs.pages.dev/developers/api/openapi.json.

**The problem:** "relevant to the niche" is not a vibe — it has to be a
computable property, or discovery drifts and brand safety becomes luck.
The platform's answer: make the niche an explicit, derived **ontology**,
and make every filter a separate gate with one owner.

## Layer 1 — the ontology is derived, not hand-typed

Each contract's niche ontology is **derived from its brand description**
(the same source that feeds brand voice) plus the contract's own
structural pages — About and Services, not articles. Empty inputs fail
loud: a contract that cannot say what it is about cannot collect. The
result is a tiered term structure: core concepts, actions, ecosystem
entities, user intents.

```mermaid
flowchart LR
    BD[Brand description +<br/>structural pages] --> O[Niche ontology<br/>tiers · core · actions ·<br/>ecosystem · intents]
    O --> Q[Curated search queries]
    O --> CMP[Per-candidate comparison]
    O --> GEN[Draft alignment in Generate]
```

## Layer 2 — per-candidate taxonomy, then comparison

During discovery every surviving candidate gets its **own** small
LLM-built taxonomy in the same shape — and scoring compares taxonomies,
not keywords: term overlap across tiers plus a domain affinity bonus,
blended into the candidate's overall rank. "Is this about our niche?"
becomes a structured comparison instead of substring luck.

## Layer 3 — filters with one owner each

Filtering is deliberately not one big function. Each gate owns a concern
(the same charter philosophy as the
[writing framework](../writing-framework/)):

| Gate | Owns | Verdict |
| --- | --- | --- |
| URL dedup | "have we seen this exact source" | delete |
| Per-domain cap | funnel diversity | cap |
| LLM screener | cheap topicality pass/fail | drop |
| Ontology comparison | niche fit, structured | score |
| Topic dedup | semantic novelty vs committed topics | **defer** (reversible) |
| Export policy | rank floor + capped bands | never seeded |

## Layer 4 — brand defense as declarations plus signals

The ontology says what the contract *is* about; brand-defense
declarations say what it must *not* promote. Competitor lists live in the
brand description as explicit sections — maintained by an automated
candidate miner (signals: donor gravity, corpus frequency) with an
owner-approved lifecycle, so the lists cannot silently rot. Downstream,
a battery of deterministic signals watches the *rendered result*: donor
on a competitor domain, competitor name in the title,
recommendation-verbs near a competitor, third-party promotion even for
brands never declared (a list-independent rule: *promote only your own*),
buyers-guide shapes, outbound links. A brand-safety hit doesn't lower the
quality number — it flips the **verdict** (reject/rework), a deliberate
separation of "how good is the text" from "may this ship".

## Trade-offs

Filtering by declared ontology means the ontology's quality is the
ceiling — a vague brand description yields vague collection, which is why
derivation fails loud instead of falling back to defaults. Competitor
filtering is intentionally *absent* at the discovery stage (a competitor
URL can be a legitimate research source); defense is enforced where
intent is visible — in what gets written and published.

## See it in two minutes

Any candidate row carries its ontology comparison and rank; any published
article carries its brand-safety signal battery — both readable through
the API.
