Skip to content

User Representation

The visitor digital twin: who a visitor is and what they care about, learned from a short survey and from how they behave, then kept current through the visit.

A visitor never states their interests outright. They show them: what they open, how long they linger, what they skip, what they search, how they answer a survey. This area is about turning that behavior into a faithful, explainable model the recommender can use, the Adaptive layer of the DAC architecture.

The model has two halves: stable attributes from a short survey (demographics, goals, persona, personal involvement) that bootstrap it before any behavior exists, and dynamic state learned from interaction (inferred engagement, attention, and a sense of which themes resonate).

What the model actually holds

Concretely, the recommender keeps one record per visitor, UserSignals. It is a small, readable summary, not raw event logs:

Part of the model What it is Where it comes from
tag_affinity the visitor's interests, as weighted facet:label tags (e.g. theme_what:forced labor at 1.0) survey answers + the tags of content they engage with
tag_aversion themes to steer away from content they disliked
taste_vector a single point in "meaning space" standing for their overall taste the average of the embeddings of stories they liked
recency_vector the meaning of the last story they read their most recent view
positives / negatives which items they liked or disliked, with a decayed strength inferred engagement (dwell, completion, revisits, rating)
demographics age, gender, nationality, province the survey (stored for inspection)
behavior summary stats (how deep, how complete, how fast) engagement, used for explanations, not scoring

Tags capture interests by concept; the vectors capture them by meaning, so the model can match content that is related even when it is tagged differently.

In reality

Here is one visitor's record after the surveys and a few views, as returned by GET /api/usermodel. The two vectors are 384 numbers each (from the sentence embedding model), shown truncated:

{
  "user_id": "7QF2",
  "tag_affinity": {
    "theme_what:forced labor": 1.0,
    "theme_how.type_of_stores:personal stories": 0.83,
    "person_who.age_group:age 55-64": 0.5,
    "place_where.camp_areas:barrack 56": 0.41
  },
  "tag_aversion": {
    "theme_what:daily life": 1.0
  },
  "taste_vector":   [0.021, -0.118, 0.077, "…381 more…"],
  "recency_vector": [0.044, -0.090, 0.100, "…381 more…"],
  "positives": { "5567": 0.82, "6012": 0.44 },
  "negatives": { "5901": 0.30 },
  "viewed":       ["5567", "5901", "6012"],
  "recent_views": ["6012", "5901", "5567"],
  "behavior": {
    "n_views": 3, "n_positive": 2, "n_negative": 1,
    "avg_dwell_ratio": 0.78, "completion_rate": 0.67,
    "revisit_rate": 0.33, "depth": 0.67
  },
  "demographics": {
    "age": "55_64", "gender": "female",
    "nationality": "france", "personal_connection": "descendant"
  }
}

Read it top to bottom: this visitor said they care about forced labor and personal stories (the strongest tag_affinity weights), skipped a "daily life" story (so it became an aversion), liked two items and disliked one, and reads fairly thoroughly (avg_dwell_ratio 0.78). That is everything the recommender needs, in one small record.

Taste and recency vectors, in plain terms

The two vectors are the one part of the model that is not tags, so they deserve a word.

An embedding is a list of numbers a language model produces from a piece of text to capture its meaning. Picture a map where every story is a pin: stories about similar things sit close together, unrelated ones sit far apart. The individual numbers are not meant to be read by a human; only how close two pins are matters. (Here each pin has 384 coordinates instead of 2, but the idea is the same.)

   meaning "map" (a flat sketch of the 384-dimensional space)

     ● ●  forced-labor stories        ★ = taste vector
    ●  ● ●        ★                        (average pin of the
     ●  ●                                   stories this visitor liked)
   ......................................
        ○ ○  daily-life stories
      ○   ○
  • The taste vector is the average position of all the pins the visitor liked: a single point standing for their overall taste. To judge a new story, the recommender measures how close that story's pin is to this point. Closer means more like what they enjoy.
  • The recency vector is simply the pin of the last story they opened. It powers the "more like the one you just read" nudge, which can point somewhere different from their long-run taste.

This is why the model can recommend a relevant story even when it happens to be tagged differently: closeness on the map is about meaning, not about sharing an exact tag.

How it is used

When the app asks for recommendations, each part of the model feeds a scorer that rates every candidate story, and the scores are combined into one ranking (tag matching leads):

flowchart LR
  E["visitor events<br>+ survey"] --> M["UserSignals<br>(the user model)"]
  M -->|tag_affinity| T["tag match"]
  M -->|taste_vector| S["semantic similarity"]
  M -->|recency_vector| R["more like the last read"]
  M -->|tag_aversion| A["penalty"]
  T & S & R & A --> Rank["ranked recommendations"]

So the survey bootstraps the model on day one, behavior sharpens it through the visit, and every recommendation is traceable back to the parts of the model that produced it. The end-to-end story, from opening the app to the Nth recommendation, is in the Pipeline Walkthrough.

What you will find here

  • Event catalog


    The shared vocabulary of visitor actions and the canonical event schema every source maps to.

  • Behavioral pipeline


    How events travel from the app through RudderStack to the user model, with analytics kept off the hot path.

API for this area

Visitor events enter through the recsys ingest webhook on the AI Engine API (POST /api/ingest); the materialized user model is readable at GET /api/usermodel.

The Bergen-Belsen Panoramic Display, where visitor behavior was gathered

Where the data comes from: the Bergen-Belsen Panoramic Display at the MEMORISE exhibition, which gathered the survey and interaction data behind the user model.