{
  "docVersion": 1,
  "id": "template-clickstream-personalization",
  "name": "Clickstream — personalization",
  "purpose": "SDK events stream through a sessionizer into a feature store and top-K sketches; the recommendations API assembles responses from precomputed state only.",
  "nodes": {
    "app": {
      "type": "mobile",
      "label": "Apps + Web",
      "x": 0,
      "y": 240,
      "runtime": "SDK · batched events"
    },
    "col": {
      "type": "gateway",
      "label": "Collector",
      "x": 260,
      "y": 240,
      "runtime": "validate · enrich · geo",
      "patterns": [
        "stateless",
        "autoscale"
      ]
    },
    "log": {
      "type": "stream",
      "label": "Clickstream",
      "x": 520,
      "y": 240,
      "runtime": "keyed by user",
      "color": "purple",
      "patterns": [
        "sharded"
      ]
    },
    "sess": {
      "type": "worker",
      "label": "Sessionizer",
      "x": 780,
      "y": 100,
      "runtime": "session windows · 30min gap",
      "notes": "A session ends when the user goes quiet — windows keyed by user, closed by watermark, emitted as one summary event."
    },
    "topk": {
      "type": "worker",
      "label": "Trending (top-K)",
      "x": 780,
      "y": 380,
      "runtime": "count-min sketch · per category"
    },
    "fs": {
      "type": "featurestore",
      "label": "Feature Store",
      "x": 1040,
      "y": 100,
      "runtime": "online + offline",
      "color": "blue",
      "patterns": [
        "replicated"
      ],
      "notes": "One feature definition, two paths: stream-fresh online for serving, batch-corrected offline for training. Same code = no skew."
    },
    "recs": {
      "type": "bff",
      "label": "Recs API",
      "x": 1300,
      "y": 240,
      "runtime": "assemble, don't compute",
      "patterns": [
        "stateless",
        "autoscale"
      ]
    },
    "cache": {
      "type": "cache",
      "label": "Feature Cache",
      "x": 1300,
      "y": 20,
      "runtime": "hot users · 60s TTL"
    }
  },
  "edges": {
    "e1": {
      "from": "app",
      "to": "col",
      "fromSide": "right",
      "toSide": "left",
      "label": "events",
      "animated": true,
      "patterns": [
        "batch",
        "compressed"
      ]
    },
    "e2": {
      "from": "col",
      "to": "log",
      "fromSide": "right",
      "toSide": "left",
      "label": "append",
      "animated": true
    },
    "e3": {
      "from": "log",
      "to": "sess",
      "fromSide": "right",
      "toSide": "left",
      "label": "by user",
      "style": "dashed"
    },
    "e4": {
      "from": "log",
      "to": "topk",
      "fromSide": "right",
      "toSide": "left",
      "label": "by item",
      "style": "dashed"
    },
    "e5": {
      "from": "sess",
      "to": "fs",
      "fromSide": "right",
      "toSide": "left",
      "label": "session features",
      "patterns": [
        "idempotent"
      ]
    },
    "e6": {
      "from": "topk",
      "to": "recs",
      "fromSide": "right",
      "toSide": "left",
      "label": "trending now",
      "style": "dashed"
    },
    "e7": {
      "from": "fs",
      "to": "recs",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "user features · p99 5ms",
      "animated": true
    },
    "e8": {
      "from": "recs",
      "to": "cache",
      "fromSide": "top",
      "toSide": "bottom",
      "label": "hot keys",
      "patterns": [
        "cache-aside"
      ]
    },
    "e9": {
      "from": "recs",
      "to": "app",
      "fromSide": "bottom",
      "toSide": "bottom",
      "label": "personalized feed",
      "style": "dashed",
      "color": "green"
    }
  },
  "texts": {
    "t1": {
      "x": 0,
      "y": 460,
      "text": "request-time rule: the recs API reads precomputed\nstate — it never aggregates. every millisecond of\ncompute was spent BEFORE the user asked.",
      "color": "green",
      "size": 12
    }
  },
  "meta": {
    "createdAt": "2026-07-13T09:00:00.000Z",
    "modifiedAt": "2026-07-13T09:00:00.000Z"
  }
}