diff --git a/src/client.ts b/src/client.ts index 7aab539..6be5745 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,6 +1,7 @@ import { HttpClient } from './core/http-client.js' import type { RequestInterceptor, ResponseInterceptor } from './core/types.js' import { AlertsResource } from './resources/alerts.js' +import { BehaviorsResource } from './resources/behaviors.js' import { ComplianceResource } from './resources/compliance.js' import { ContextResource } from './resources/context.js' import { EventsResource } from './resources/events.js' @@ -53,6 +54,7 @@ export interface ThinkFleetMemoryOptions { export class ThinkFleetMemory { readonly memory: MemoryResource readonly lattice: LatticeResource + readonly behaviors: BehaviorsResource readonly context: ContextResource readonly events: EventsResource readonly alerts: AlertsResource @@ -82,6 +84,7 @@ export class ThinkFleetMemory { this.memory = new MemoryResource(http) this.lattice = new LatticeResource(http) + this.behaviors = new BehaviorsResource(http) this.context = new ContextResource(http) this.events = new EventsResource(http) this.alerts = new AlertsResource(http) diff --git a/src/index.ts b/src/index.ts index ef63ba2..bd29ffb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,6 +84,12 @@ export type { ConsentStatus, } from './resources/consent.js' export { LatticeResource } from './resources/lattice.js' +export { BehaviorsResource } from './resources/behaviors.js' +export type { + DiscoverParams, + DiscoveredBehavior, + DiscoverResult, +} from './resources/behaviors.js' export { HealthResource } from './resources/health.js' export { FinancialResource } from './resources/financial.js' diff --git a/src/resources/behaviors.ts b/src/resources/behaviors.ts new file mode 100644 index 0000000..4fb29a6 --- /dev/null +++ b/src/resources/behaviors.ts @@ -0,0 +1,89 @@ +import type { HttpClient } from '../core/http-client.js' +import type { RequestOptions } from '../core/types.js' +import type { Subject } from '../types/lattice.js' + +/** Tuning for a discovery run. All optional; the engine clamps to safe ranges. */ +export interface DiscoverParams { + /** Minimum similarity [0,1] for a subject to join a cluster. Higher = tighter, + * more numerous clusters. Default 0.75. */ + simThreshold?: number + /** A cluster smaller than this is noise, not a behavior. Default 3. */ + minClusterSize?: number + /** Drop clusters whose cohesion (mean intra-cluster similarity) is below this. + * Default 0.6. */ + minStability?: number + /** Cap on member subjects returned per behavior. Default 50. */ + maxMembers?: number +} + +/** + * One emergent behavior — a cohesive cluster of subjects the engine grouped + * together because they behave alike, with the statistics that justify treating + * it as real. Behaviors are discovered from the data, not chosen from a fixed + * menu. + */ +export interface DiscoveredBehavior { + /** Rule-based description (RFM band + frequency + dominant pattern + entity). + * Upgraded to an LLM-authored name in a later engine release. */ + label: string + /** Fraction of the analyzed cohort in this cluster, [0,1] — how common it is. */ + prevalence: number + /** Cohesion: mean pairwise similarity within the cluster, [0,1] — how tightly + * these subjects actually behave alike. */ + stability: number + /** Total subjects in the cluster (may exceed memberSubjects length when capped + * by maxMembers). */ + size: number + /** Up to maxMembers subjects, medoid first — provenance for who exhibits it. */ + memberSubjects: Subject[] + /** Human-readable signals behind the label (e.g. "pattern: recurring_event"). */ + exemplarEvidence: string[] +} + +export interface DiscoverResult { + /** Discovered behaviors, sorted by prevalence then stability (most common, + * most cohesive first). Empty when there isn't enough signal — discovery + * abstains structurally rather than inventing weak behaviors. */ + behaviors: DiscoveredBehavior[] + /** Total subjects analyzed (the prevalence denominator). */ + subjectsAnalyzed: number + generatedAt: string + durationMs: number +} + +/** + * Behaviors — emergent behavior discovery. + * + * Where `tf.lattice.predict` answers "what will this subject do?" and + * `getProfile` answers "who is this subject?", `discover` answers a project-wide + * question: **"what behaviors exist in my data that nobody defined?"** It + * clusters subjects by their feature vectors and surfaces the dense, cohesive + * groups as behaviors — each with prevalence, stability, members, and + * explainable evidence. + * + * @example + * ```ts + * const { behaviors } = await tf.behaviors.discover() + * for (const b of behaviors) { + * console.log(`${b.label} — ${(b.prevalence * 100).toFixed(0)}% of subjects, ` + * + `stability ${b.stability.toFixed(2)}, ${b.size} members`) + * console.log(' evidence:', b.exemplarEvidence.join(', ')) + * } + * ``` + */ +export class BehaviorsResource { + constructor(private readonly http: HttpClient) {} + + /** + * Discover emergent behaviors across the project. Returns clusters of + * like-behaving subjects, sorted most-common-and-cohesive first. An empty + * result means the engine abstained — not enough signal to assert any + * behavior — never "there are no behaviors". + */ + async discover( + params: DiscoverParams = {}, + options?: RequestOptions, + ): Promise { + return this.http.post('/lattice/discover', params, options) + } +}