Audit
Usage telemetry — what it records, what it feeds back into relevance, and the two ways to send it.
Audit events are how a Sinequa deployment learns what people search for and what they click. They feed the analytics dashboards, and — more importantly — the relevance models: a click recorded as a click is what makes RFM improve ranking over time.
Concept — RFM
RFM is Sinequa's Relevance Feedback Model: relevance learned from what users do — which results they click, which they like, which they mark as important. It is computed on the server from the audit events your interface sends, which is why recording a click accurately matters more than recording it at all.
Two ways to send them, and the first is almost always the right one.
| Use it for | |
|---|---|
| Riding a search | An event that belongs to a query — the search itself, a facet click |
Audit.notify() | An event with no request of its own — a document opened, a result copied |
Events that ride a request
Most calls take an audit argument. Sending the event with the request it describes is cheaper and more accurate than a separate call, because the server sees both together:
import { AuditEventType, createAtomicClient, filter } from "@sinequa/atomic";
import type { AuditEvents, Query } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
await client.initializeConfig();
await client.auth.login();
/** The search itself, audited as a text search. */
export function search(text: string) {
const audit: AuditEvents = {
type: AuditEventType.Search_Text,
detail: { text },
};
// The event travels with the query it describes — one request, not two.
return client.api.query.search({ name: "<your-query>", text, pageSize: 10 }, audit);
}
/** A facet click is a refinement, not a new search: say so. */
export function refine(query: Query, column: string, value: string) {
const audit: AuditEvents = {
type: AuditEventType.Search_Refine,
detail: { column, value },
};
return client.api.query.search(
{ ...query, filters: filter.eq(column, value) },
audit,
);
}import { Injectable, inject } from "@angular/core";
import { AuditEventType, filter } from "@sinequa/atomic";
import type { AuditEvents, Query } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
@Injectable({ providedIn: "root" })
export class AuditedSearchService {
private readonly client = inject(SINEQUA);
/** The event travels with the query it describes — one request, not two. */
search(text: string) {
const audit: AuditEvents = { type: AuditEventType.Search_Text, detail: { text } };
return this.client.api.query.search(
{ name: "<your-query>", text, pageSize: 10 },
audit,
);
}
/** A facet click is a refinement, not a new search: say so. */
refine(query: Query, column: string, value: string) {
const audit: AuditEvents = {
type: AuditEventType.Search_Refine,
detail: { column, value },
};
return this.client.api.query.search({ ...query, filters: filter.eq(column, value) }, audit);
}
}import { AuditEventType, filter } from "@sinequa/atomic";
import type { AuditEvents, Query } from "@sinequa/atomic";
import { ready, sinequa } from "./sinequa";
/** The event travels with the query it describes — one request, not two. */
export async function search(text: string) {
await ready;
const audit: AuditEvents = { type: AuditEventType.Search_Text, detail: { text } };
return sinequa.api.query.search({ name: "<your-query>", text, pageSize: 10 }, audit);
}
/** A facet click is a refinement, not a new search: say so. */
export async function refine(query: Query, column: string, value: string) {
await ready;
const audit: AuditEvents = {
type: AuditEventType.Search_Refine,
detail: { column, value },
};
return sinequa.api.query.search({ ...query, filters: filter.eq(column, value) }, audit);
}Standalone events
Audit.notify() is for what has no request of its own — the user opened a document, copied a link, expanded a
passage:
import { Audit, AuditEventType } from "@sinequa/atomic";
// Fire and forget: never await it in a click handler's critical path.
void Audit.notify({
type: AuditEventType.Click_ResultLink,
detail: { recordId: record.id, rank: record.rank },
});It is deliberately fire-and-forget
Audit.notify carries noRecovery, so a 401 on it is not treated as a session to repair. Telemetry
arriving after a sign-out used to trigger session recovery and put a login form in front of a user who had just
signed out — which is the one thing an audit call must never cause.
What the event carries
An AuditEvent is a type and an optional detail. AuditEventType is a large enum aligned with the server's
own list — Search_Text, Search_Refine, Click_ResultLink, RFM_Like — and the type also accepts a custom
string, so a deployment can record its own events.
The library adds two things to whatever you send:
- A session id — a GUID regenerated after ten minutes of inactivity, which is what groups events into a "session" in the analytics.
- The current URL, unless you pass
falseas the second argument.