MaxyDocs
View as markdown →

Visitor graph (Task 357)

Behavioural analytics that connect anonymous page visits to known :Person contacts. Replaces the anonymous click-through metric from Task 336 with a fully attributed graph.

What this gives the operator

  • A morning briefing surface: "who has been on the site overnight, and what did they look at?"
  • An engagement-ranked nurture queue, ordered by recency × depth × dwell.
  • A graph-backed click-through-rate report for property recommendations.
  • A full event timeline for any one session, for prep and diagnosis.
  • A signed-cookie that recognises a named visitor on later visits without re-clicking the marketing link.

Data model

NodeMeaning
:SessionOne browser tab session. Composite key (accountId, sessionId).
:AnonVisitorPre-identification browser identity. Merges into :Person on first signed-token click.
:PageViewOne page load. Carries referrer, path, optional dwellMs.
:ClickOne DOM click on a tagged element (data-track="<label>").
:ScrollMilestoneRoll-up of scroll depth — one node per :PageView, maxDepth ∈ {25,50,75,100}.
:PageURL metadata. Content-only, survives erasure.
:RecommendationMaterialised [property-recommended] log line for CTR computation.
EdgeDirection
VISITEDPerson → Session or AnonVisitor → Session
OWNS_VISITORPerson → AnonVisitor (cross-session merge)
HAS_EVENTSession → PageView / Click / ScrollMilestone
OF_PAGEPageView → Page
OF_LISTINGPageView → Listing
FOR_SESSIONRecommendation → Session

How identity gets resolved

The signed-token cookie mxy_v carries a :Person elementId, signed HMAC-SHA256 with a brand-local 32-byte secret (file at ~/.<brand>/credentials/visitor-token-secret, minted on first read). When the recommender's /listings/<slug>/click?session=<sk>&v=<token> URL is visited:

  1. The click handler verifies the HMAC on <token>.
  2. If valid, the same token value is written into mxy_v (Max-Age 30 days, SameSite=Lax, HttpOnly, Secure).
  3. On subsequent visits, POST /v/event reads the cookie, verifies it, and attributes every event to the bound :Person.

When a previously-anonymous browser binds for the first time, any :Session already attributed to the :AnonVisitor is re-attached to the :Person, and the merge fires [anonvisitor-merge] with the count of reattributed sessions.

Tools

All under the real-agent-buyers plugin, admin-side only:

ToolPurpose
visitor-recent-by-personRecent sessions for a known :Person (morning round, 1:1 prep).
visitor-recent-by-pageRecent visitors of a given listing slug or URL.
visitor-engagement-scoreEngagement-ranked :Person list (nurture queue).
visitor-recommendation-ctrGraph-backed CTR over a window, joined from :Recommendation and :Click nodes.
visitor-session-detailFull event timeline for one :Session.
visitor-event-ingestAdmin companion to POST /v/event for test harness work.
visitor-backfill-from-logsOne-shot importer: parses [property-recommended] and [property-card-click] log lines into the graph; used to recover late-arriving sessions and for ad-hoc forensics.
mint-visitor-tokenMints a signed token bound to a :Person for outbound URLs in morning-round, lead-nurturing, vendor-updates. Returns { token, expiryMs }; the agent appends &v=<token> to /listings/<slug>/click?session=<sk>. Same secret file as the UI server; both processes share it via the wx-create pattern. (Task 362)

Privacy

The full description is at /privacy on every brand domain. Highlights:

  • First-party cookie only, no third-party scripts.
  • Retention is erasure-on-request, not time-based; visit data persists until a :Person is erased.
  • Right-to-erasure cascades through contact-erase: :Session, every :HAS_EVENT child, and any owned :AnonVisitor are removed when the :Person is erased. :Page and :Listing are content metadata and intentionally preserved.

Verification

Quick checks the operator can run after deployment:

  1. Load a published listing page; grep [visitor-event] type=pageview in server.log within 1s.
  2. Scroll past 50%; grep [visitor-event] type=scroll depth=50.
  3. Click an element marked data-track="floorplan"; grep [visitor-event] type=click label=floorplan.
  4. Run visitor-backfill-from-logs over a log window where live writes were lost (process restart, etc.); the response reports recWritten and clickWritten counts. Subsequent runs over the same window are idempotent for :Recommendation and append-only for :Click.

Failure signals

SymptomWhat it meansWhere to look
[visitor-event] count drops to zero with no [v-event-error]Pixel silently failing on the brand domain (probably CSP, CORS, or origin mismatch).Check brand.json publishedSiteOrigins; check browser console on a published listing page.
[token-bind] reject reason=bad-sig spikesHMAC verify failing — either the secret rotated and old cookies are being rejected (expected during rotation) or the recommender is minting against a stale secret.Compare ~/.<brand>/credentials/visitor-token-secret across processes.
[anonvisitor-merge] never fires after first signed-token clickThe pixel isn't reading the cookie.Inspect the mxy_v cookie in DevTools; check CORS Access-Control-Allow-Credentials: true.
[v-event-error] reason=rate-limit for legitimate operator trafficOperator IP shares a NAT with high-volume crawlers.Adjust RATE_LIMIT in visitor-event.ts or whitelist the IP at the proxy.