Skip to content

Search fields

dots_es/api/search_fields.py holds a single registry, SEARCH_FIELDS, that declares every metadata field the application knows about. It is the one place to edit when you want a new field indexed, facetable or sortable: the same declaration drives filtering at index time and aggregations, sorting and query aliases at search time.

The SearchField dataclass

Attribute Default Meaning
id — Internal identifier, e.g. dct:creator. Also the name of the Elasticsearch aggregation.
path — Source and index path, e.g. dublincore.creator.
family — One of CLI, DTS, DCT, SCHEMA, DOTS, THUNDERDOTS. Drives ES prefixing and sortability.
type — One of KEYWORD, TEXT, TEMPORAL, URL, INTEGER. Drives .keyword suffixing, aggregation eligibility and sort strategy.
index True The field is kept in resource_metadata by the indexer.
facet False Build a terms aggregation for this field.
autocomplete False Not operational yet — see the note below.
fulltext False Only content declares it. Not operational yet.
multiple False Not operational yet.
range_start / range_end None ES paths of the numeric bounds of a temporal range facet.

Two derived properties matter:

  • key — the canonical metadata key exposed to clients. dublincore.created and temporal.dublincore.created both surface as dublinCore.created. Facets are published under this key, not under id.
  • is_range_facet — true when facet is set together with range_start and range_end.

Flags that are not operational yet

autocomplete, fulltext and multiple can be declared on a field, but they are not operational at the moment: setting them changes neither indexing nor querying.

Families

CLI, DTS, DCT (Dublin Core), SCHEMA (schema.org), DOTS (DoTS extensions) and THUNDERDOTS.

Two groupings drive the Elasticsearch paths:

  • METADATA_FAMILIES = (DCT, SCHEMA, DOTS) — these are prefixed with resource_metadata. when queried;
  • SORTABLE_FAMILIES = METADATA_FAMILIES + (DTS,).

The registry is organised in blocks: CLI navigation fields (parent_id, path, path_ids, ancestors), DTS fields (id, type, title, description, download, content), Dublin Core, schema.org, DoTS extensions, and finally temporal range facets generated from ThunderDots output.

What the registry does at index time

extract_metadata() filters incoming DTS metadata against the registry:

  • allowed Dublin Core keys are the path of every DCT field, minus the dublincore. prefix;
  • allowed extension keys are the path of every SCHEMA field;
  • anything not declared is dropped.

Temporal metadata goes through build_filtered_temporal_metadata(), which keeps only the _start/_end bounds of declared range facets and discards raw and *_iso values.

DOTS fields are declared but deliberately not indexed

The extension whitelist collects only fields whose family is SCHEMA, so the two DOTS extensions — dots:shortTitle and dots:resourceIIIFManifest — never reach the index, even though they carry the default index=True.

This is a deliberate choice for the time being: these two fields are not meant to be indexed yet. Adding SearchFieldFamily.DOTS to the whitelist in extract_metadata is all it would take to start writing them into every resource document — so do not treat the current behaviour as a bug to be fixed.

What the registry does at query time

Helper Role
get_es_path Prefixes resource_metadata. for DCT/SCHEMA/DOTS; other families use path verbatim.
get_es_field Appends .keyword for KEYWORD fields.
build_searchfield_aggs One terms aggregation per non-range KEYWORD facet, named after field.id, with a nested cardinality sub-aggregation on resource_id so counts are per resource, not per fragment.
extract_searchfield_facets Reads buckets by field.id and republishes them under field.key.
get_es_sort_field / resolve_sort_field TEMPORAL sorts on temporal.{range_start}; text, keyword and URL fields sort on the .sort sub-field (normalizer sortable), never on .keyword.

Adding a field

Add one entry to the relevant block. For a facetable Dublin Core subject:

dots_es/api/search_fields.py
SearchField(
    "dct:subject",
    "dublincore.subject",
    SearchFieldFamily.DCT,
    SearchFieldType.KEYWORD,
    facet=True,
),

With no other code change, this single declaration:

  1. lets dublincore.subject survive the extract_metadata filter;
  2. indexes it at resource_metadata.dublincore.subject, typed by the resource_metadata.* dynamic template;
  3. adds an aggregation named dct:subject on resource_metadata.dublincore.subject.keyword;
  4. publishes the facet to clients as dublinCore.subject;
  5. makes sort=dublinCore.subject resolve to resource_metadata.dublincore.subject.sort.

Reindex, but usually no --rebuild

Because string metadata is mapped by a dynamic template rather than by explicit properties, a new string field usually needs no update-conf --rebuild. It does need a reindex, since documents indexed earlier dropped the value.

For a temporal range facet, declare a second entry whose id ends in :range and which carries range_start / range_end, following the pattern of the existing temporal block.