Spaces:
Running
Running
File size: 4,960 Bytes
21488bd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | # Behavioural analytics from OTEL
ra-mcp's OpenTelemetry isn't only for ops health β it's instrumented so you can
answer **product questions**: what people search for, which datasets and features
they use, and what they want but can't find. This page maps those questions to the
spans and metrics that answer them.
Enable it with `RA_MCP_OTEL_ENABLED=true` and point `OTEL_EXPORTER_OTLP_ENDPOINT`
at your collector (see [Observability](observability.md)). Traces carry the
high-cardinality *intent* (search terms, reference codes); metrics carry the
bounded *aggregates* (per-dataset counts, latencies) for dashboards and alerts.
## Where the signal lives (and why)
| Signal | Kind | Why |
|--------|------|-----|
| Search term, filter, reference code | **span attribute** | High-cardinality β belongs on sampled traces, never on a metric label (which must stay bounded). |
| Per-dataset counts, result distribution, latency | **metric** | Bounded labels (dataset name) β safe to aggregate into dashboards/SLOs. |
| Failures + stacktraces | **span status + correlated log** | `record_span_exception` sets `error.type` on the span and emits a trace-correlated ERROR log. |
## Questions you can answer
### "What are people searching for?"
Every search records the term on a span:
- **Datasets + PDF guides** β `search <dataset>` span, attribute **`db.query.text`** (the term), **`db.query.filter`** (filters applied, e.g. `gender = 'm'`), **`db.response.total_hits`** (how well the data answered). Covers all 13 datasets and the guide search (both go through the shared spine).
- **Live archive search** β `SearchOperations.search` span, attributes **`search.keyword`**, `search.total_hits`, `search.offset`.
Query your trace store for these span attributes to see the actual terms, grouped by dataset.
### "What do people open, view, and geocode?"
Searching is only half of intent β what users then *open* is the other half:
- **Viewer** β `view_document` / `viewer_navigate` spans, attributes **`view.reference_code`** (which archive document) and **`view.pages`**.
- **PDF viewer** β `display_pdf` span, attribute **`pdf.url`** (which guide/PDF).
- **Geocoding** β `tora search` spans, attributes **`geocode.place`** (+ `geocode.parish` / `geocode.county`) β which historical places people look up.
### "Which datasets / features are most used?"
- **`ra_mcp.lancedb.queries`** counter, by **`db.collection.name`** β per-dataset (and guide) search volume.
- **`ra_mcp.search.requests`** counter, by `search.type` β live transcribed vs metadata search volume.
- Tool popularity (which viewer, which tool) β the FastMCP `tools/call` spans (one per tool); derive per-tool counts in the collector (`spanmetricsconnector`).
### "What do people want but can't find?" (unmet demand)
This is the highest-value product signal:
- **`ra_mcp.lancedb.results`** histogram, by `db.collection.name` β its **zero bucket** is the rate of searches that returned nothing, per dataset.
- Then read the **`db.query.text`** of those zero-hit spans (`db.response.total_hits = 0`) to see the *actual* terms people searched for that your data doesn't answer β a direct backlog of data/feature gaps.
### "How engaged are they?"
- `search.offset` (span) β how deep users paginate.
- `ra_mcp.browse.pages` / `ra_mcp.browse.empty_pages` β pages read per browse, and blank-page rate.
- `db.response.total_hits` vs `db.response.returned_rows` β corpus richness vs page shown.
### "The research funnel: search β browse β view"
Search spans carry **`mcp.session.id`** (when the client sends it); correlate spans within a session/trace to see whether a search led to a browse and then a document/PDF view.
### "Which upstream is slow or failing?"
- **`ra_mcp.http.request.duration`** / **`ra_mcp.http.errors`** now carry **`server.address`** β per-upstream RED (Solr API vs IIIF vs OAI-PMH vs HF CDN), not one blurred series.
- LanceDB: **`ra_mcp.lancedb.query.duration`** + **`ra_mcp.lancedb.errors`** by dataset.
## Coverage at a glance
| Surface | Term captured | Volume metric | Result/latency metric | Failures |
|---------|:---:|:---:|:---:|:---:|
| Dataset search (Γ13) | β
span | β
by dataset | β
results + duration | β
span+log+counter |
| PDF guide search | β
span | β
| β
| β
|
| Live archive search | β
span | β
by type | β
results | β
|
| Browse | ref code on span | β
| pages / empty pages + duration | β
|
| Viewer open | β
`view.reference_code` + `view.pages` | via spanmetrics | β | FastMCP span |
| PDF open | β
`pdf.url` | via spanmetrics | β | FastMCP span |
| Geocoding (tora) | β
`geocode.place` (+parish/county) | via spanmetrics | β | β
span+log |
| HTR | tool span | via spanmetrics | β | β
log |
> Terms and reference codes are user intent, exposed only in traces (sampled, access-controlled), never in metric labels. Treat your trace/log backend accordingly.
|