AI briefing - visitor analytics

Guide for AI assistants analysing a lazysite's visitor traffic for trend reporting.

Who this is for

This briefs an AI assistant that the operator has asked to analyse visitor trends for this site. You get the data from the analyse_visitors tool (it needs the analytics capability). The operator directs the analysis - this doc tells you how to read the data and what you can honestly report.

How to get the data

Call analyse_visitors with an optional window (days, 1-365, default 30). It returns a sanitised JSON summary built from lazysite's first-party access log (recorded by the site itself, anonymised at write; the web-server access log is the fallback source when no first-party data exists). You never see the raw log, any filesystem path, or a visitor's IP address: the tool aggregates and anonymises before anything reaches you. Repeated calls are cheap - the data is cached and only new log lines are processed each time.

What the data means

The response has these fields:

window           { days, from, to }            the period covered
totals           { human_visits, unique_visitors, pageviews }
traffic_classes  { human|ai|bot|noise: { visits, share } }
by_day           [ { date, human, ai, bot, noise } ]   the trend, one row per day
top_pages        [ { key: "/path", count } ]   most-visited pages (people only)
referrers        { direct, internal, external: [ { key: host, count } ] }
status_codes     { "200": n, "404": n, ... }   people's responses
not_found        { plausible: [...], junk_count: n }   missing pages vs scanner noise
auth_refused     [ { key: "/path", count } ]   turned away, NOT missing
events           [ { t, class, path, status, visitor } ]   recent requests
events_capped    true if the event stream hit its size limit

A day rollup (day=YYYY-MM-DD) additionally carries:

registry_hits    n                              generated registries served
registry_by      { "sitemap.xml": n, ... }       which one, and how often

These are the engine's generated files - the sitemap and the feeds - fetched mostly by crawlers. They are counted beside pageviews and never inside it: a sitemap fetch is not a page view, and folding it in would inflate the one figure an operator reads as "people". Read them as interest from search engines and AI crawlers rather than as traffic.

auth_refused

Paths a visitor was turned away from - a page or file that exists and that an access rule refused. It is separate from not_found because the two need different actions: a 404 means write the page, a refusal means check who is meant to be able to read it.

It is its own field rather than a status-code slice because the status cannot carry it. An anonymous refusal is a 302 to the login page, identical in the log to every other redirect.

A file in this list that you believe is public is the finding. It means an access rule is refusing it - most often an ACL read list. Since 0.10.5 that list governs the public read path as well as the authoring channels, so an entry originally written to keep other editors out of a file now also keeps anonymous visitors out of it. That is usually what was wanted; occasionally it is not, and this is where it shows.

unique_visitors is approximate - it counts anonymised networks, not people. visitor in an event is a short, non-reversible token for the request's network, so you can group events into rough sessions/flows without identifying anyone. t is a Unix timestamp.

The traffic taxonomy

Every request is classified by a log-only heuristic (user-agent + path + status):

human
a real person's browser. This is the audience figure - use it for "visits".
ai
an AI assistant or model fetcher (GPTBot, ClaudeBot, PerplexityBot, ...). Track this to show how much AI-assistant interest the site draws.
bot
search crawlers and generic automation (Googlebot, curl, monitors).
noise
vulnerability scanners and probes (/wp-login.php, /.env, *.php on a Markdown site). Background abuse, not audience - usually report it only if it spikes.

When the operator asks about "traffic" or "visitors", they almost always mean the human class. Call out the AI share separately when it is interesting.

Trails: the order people went in

The aggregates answer how many took each step. They cannot answer in what order one visit went, because a transition count of 100 on an edge is not a hundred stored journeys. trails=YYYY-MM-DD returns the recorded visits for that day instead:

visits           how many visits the day HOLDS
returned         how many this reply contains
truncated        true when returned < visits - the reply is capped at 200
trails[]         one entry per visit
  entry          first page of the visit
  exit           last page
  depth          DISTINCT pages, so a reload is not another page
  steps[]        the ordered sequence, repeats included
    p            the path
    c            the visitor class AS IT WAS at the time
    gap          seconds until the next step; ABSENT on the last step

Two things to keep straight when you use it:

Trails are the most person-adjacent data here. They are pseudonymous and capped, they are still a single visitor's path, and the rules under "What you must NOT claim" apply to them with more force rather than less.

What you can report on

What you must NOT claim

Style

Lead with the answer to what the operator asked, backed by the specific numbers. Prefer a short narrative plus the few figures that matter over dumping the whole JSON. Flag a caveat when a number is approximate or the window is short.