Skip to content

Tag system (taxonomy)

How content and visitors are described with the same set of tags, so the recommender can match one to the other. This is the shared vocabulary behind score_tag and the whole survey to content bridge.

The tag model: facet:label

Every tag, whether it sits on a piece of content or in a visitor's persona, is written as facet:label:

  • facet is the category. Some facets are simple (theme_what), others are dotted to show a sub-category (place_where.camp_areas, person_who.age_group).
  • label is the value within that category (Forced Labor, Barracks, age 55-64).

A visitor is matched to content when the two carry the same facet:label. Matching is done on a canonical form of the key, so casing, accents, separators, and common spelling variants do not break a match (see Normalization). Example key: theme_what:Forced Labor.

The facets (categories)

Tags are organized into a few top-level dimensions, each with one or more facets:

Dimension Facet(s) What it captures
Theme (what) theme_what The historical theme of the item (the largest facet, with a two-level hierarchy, below).
Medium (what) medium_what The medium: photograph, artwork, object, document, film, testimony, text, map.
Place (where) place_where.camp_areas Areas inside the camp (barracks, watch tower, infirmary, ...).
place_where.transit_destinations Where a person arrived from or was deported to (Arrived from: …, Deported to: …).
Person (who) person_who.reason_for_imprisonment Why the person was interned (Jewish, political prisoner, Roma and Sinti, PoW, ...).
person_who.age_group Age band (child, age 25-34, elderly, ...).
person_who.gender_and_age Male, female, non-binary.
person_who.mothertongue Dutch, German, Polish, Hungarian, Yiddish, Frisian, English.
person_who.province_netherlands NL province of origin (Noord-Holland, Drenthe, ...).
person_who.city_village_country Origin place (From: Germany, Born in: Haren), plus the International rollup.
Time (when) time_when.time_period Camp period (Refugee camp, Transit camp, Schattenberg period, ...).
Theme (how) theme_how.ai_engine_themes Storyline themes (Resistance, Jewish Culture & Religion, Sinti & Roma, Perpetrators, ...).
theme_how.type_of_stores Kind of story (Personal Stories, Historical Context, Anecdotes, ...).
Language (how) language_how.tone Sentiment/tone keyword of the text (Somber (tone), reflective (tone), ...).

The theme hierarchy (theme_what)

theme_what is two levels deep. Each main theme has a set of subtags (finer concepts). Content tagged with a subtag is also matched to its parent main theme, so a coarse survey answer ("Forced Labor") still matches finely-tagged content ("construction forced labor"), while the granular label is preserved.

The 25 main themes:

Adaptation and Survival · Aid and Protection · Awareness and Reflection · Brutality and Killings · Camp Procedures · Cultural and Creative Activities · Daily Life · Deaths and Mortality · Deportation and Transport · Discrimination · DP Camps · Family · Flight and Migration · Food and Hunger · Forced Labor · Guarding · Health and Diseases · Liberation · Living Conditions · Jewish Life · Political Activities · Religion · Sanitary Conditions · Social Relations · Thoughts and Feelings

Example, the Forced Labor main theme and some of its subtags:

theme_what:Forced Labor            (main theme)
├─ forced labor activities/conditions
├─ agricultural / armament / construction / factory / medical / textile forced labor
├─ forced labor battalion experiences
├─ work detail
└─ workshops

A content item tagged construction forced labor therefore carries two matchable tags: the granular theme_what:construction forced labor and the rollup theme_what:Forced Labor.

How a raw content tag becomes a facet:label

Curators (and the AR app) tag content with plain text strings that have no category attached, for example Forced Labour, age 25-34, or Deported to: Auschwitz. Before a tag can be matched, the system has to work out which facet it belongs to. assign_facet does this by trying a short list of rules in order, stopping at the first one that matches:

  1. Subtag: the string is one of the fine-grained theme subtags. It becomes a theme_what tag, and its parent main theme is added as well (the "rollup"), so both the specific and the general tag can match.
  2. Rollup: the string is a grouping label such as International (the catch-all for non-core countries).
  3. Exact: the string is a tag listed verbatim in the taxonomy.
  4. Pattern: the string matches a shape rather than a fixed value: an age band (age 25-34), a barrack number (barrack 56), or a tone keyword ending in (tone).
  5. Prefix: the string starts with a known location prefix such as From:, Deported to:, or Arrived from:.
  6. Default: nothing matched, so the string is kept as a theme_what tag with its own text as the label.

Worked examples (note the results are lower-cased, the canonical form used for matching):

Raw tag from Omeka Matched by Resulting facet:label
Forced Labour Exact (after the spelling fix labourlabor) theme_what:forced labor
construction forced labor Subtag of Forced Labor theme_what:construction forced labor and theme_what:forced labor
age 25-34 Pattern (age band) person_who.age_group:age 25-34
Deported to: Auschwitz Prefix (Deported to:) place_where.transit_destinations:deported to: auschwitz
Somber (tone) Pattern ((tone) suffix) language_how.tone:somber (tone)
AiARLocationBarrack56 decoded first, then Pattern place_where.camp_areas:barrack 56

The last row is the AR app's doing: it emits camelCase machine tags like AiARLocationBarrack56, which are decoded to the curator's wording (barrack 56) before the rules run, so app-tagged and hand-tagged content end up identical.

Normalization (matching)

Matching two tags is ultimately a plain string comparison, but only after both are reduced to the same canonical form. Otherwise trivially different spellings of the same thing would fail to match. The label part of every tag is put through the same steps, on the content side and the visitor side alike:

  1. smart quotes become plain ASCII quotes (commander’s house becomes commander's house);
  2. accents are stripped (café becomes cafe);
  3. everything is lower-cased (Forced Labor becomes forced labor);
  4. British spellings and common typos are unified (labour becomes labor, theatre becomes theater);
  5. separators (spaces, hyphens, underscores) are collapsed to a single space (Roll-call becomes roll call);
  6. a small list of known aliases is applied, for Dutch/English duplicates and wording variants (verzet becomes resistance).

The facet part is only lower-cased, since it is a fixed machine key, not free text.

So the survey answer Forced Labour and the content tag forced labor both reduce to the key theme_what:forced labor, and match.

Source of truth and code

  • Data: ai-engine/src/memorise_taxonomy/data/tags.json (dimensions, main tags, subtags).
  • Code: the memorise_taxonomy package (normalize_label, normalize_key, assign_facet, to_tags).
  • Where it is used: survey answers become persona tags in survey_affinity; matching happens in score_tag.