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:
- Subtag: the string is one of the fine-grained theme subtags. It becomes a
theme_whattag, and its parent main theme is added as well (the "rollup"), so both the specific and the general tag can match. - Rollup: the string is a grouping label such as
International(the catch-all for non-core countries). - Exact: the string is a tag listed verbatim in the taxonomy.
- 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). - Prefix: the string starts with a known location prefix such as
From:,Deported to:, orArrived from:. - Default: nothing matched, so the string is kept as a
theme_whattag 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 labour → labor) |
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:
- smart quotes become plain ASCII quotes (
commander’s housebecomescommander's house); - accents are stripped (
cafébecomescafe); - everything is lower-cased (
Forced Laborbecomesforced labor); - British spellings and common typos are unified (
labourbecomeslabor,theatrebecomestheater); - separators (spaces, hyphens, underscores) are collapsed to a single space (
Roll-callbecomesroll call); - a small list of known aliases is applied, for Dutch/English duplicates and wording variants (
verzetbecomesresistance).
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_taxonomypackage (normalize_label,normalize_key,assign_facet,to_tags). - Where it is used: survey answers become persona tags in
survey_affinity; matching happens inscore_tag.