@contractspec/lib.personalization 1.57.0 → 1.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/adapter.d.ts +15 -19
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adapter.js +43 -38
  4. package/dist/analyzer.d.ts +15 -19
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +69 -51
  7. package/dist/browser/adapter.js +46 -0
  8. package/dist/browser/analyzer.js +72 -0
  9. package/dist/browser/docs/behavior-tracking.docblock.js +97 -0
  10. package/dist/browser/docs/index.js +271 -0
  11. package/dist/browser/docs/overlay-engine.docblock.js +98 -0
  12. package/dist/browser/docs/workflow-composition.docblock.js +83 -0
  13. package/dist/browser/index.js +555 -0
  14. package/dist/browser/store.js +75 -0
  15. package/dist/browser/tracker.js +94 -0
  16. package/dist/browser/types.js +0 -0
  17. package/dist/docs/behavior-tracking.docblock.d.ts +2 -6
  18. package/dist/docs/behavior-tracking.docblock.d.ts.map +1 -1
  19. package/dist/docs/behavior-tracking.docblock.js +95 -15
  20. package/dist/docs/index.d.ts +4 -3
  21. package/dist/docs/index.d.ts.map +1 -0
  22. package/dist/docs/index.js +272 -3
  23. package/dist/docs/overlay-engine.docblock.d.ts +2 -6
  24. package/dist/docs/overlay-engine.docblock.d.ts.map +1 -1
  25. package/dist/docs/overlay-engine.docblock.js +96 -15
  26. package/dist/docs/workflow-composition.docblock.d.ts +2 -6
  27. package/dist/docs/workflow-composition.docblock.d.ts.map +1 -1
  28. package/dist/docs/workflow-composition.docblock.js +81 -15
  29. package/dist/index.d.ts +7 -7
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +555 -6
  32. package/dist/node/adapter.js +46 -0
  33. package/dist/node/analyzer.js +72 -0
  34. package/dist/node/docs/behavior-tracking.docblock.js +97 -0
  35. package/dist/node/docs/index.js +271 -0
  36. package/dist/node/docs/overlay-engine.docblock.js +98 -0
  37. package/dist/node/docs/workflow-composition.docblock.js +83 -0
  38. package/dist/node/index.js +555 -0
  39. package/dist/node/store.js +75 -0
  40. package/dist/node/tracker.js +94 -0
  41. package/dist/node/types.js +0 -0
  42. package/dist/store.d.ts +14 -18
  43. package/dist/store.d.ts.map +1 -1
  44. package/dist/store.js +74 -57
  45. package/dist/tracker.d.ts +42 -46
  46. package/dist/tracker.d.ts.map +1 -1
  47. package/dist/tracker.js +93 -91
  48. package/dist/types.d.ts +48 -51
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +1 -0
  51. package/package.json +104 -37
  52. package/dist/adapter.js.map +0 -1
  53. package/dist/analyzer.js.map +0 -1
  54. package/dist/docs/behavior-tracking.docblock.js.map +0 -1
  55. package/dist/docs/overlay-engine.docblock.js.map +0 -1
  56. package/dist/docs/workflow-composition.docblock.js.map +0 -1
  57. package/dist/store.js.map +0 -1
  58. package/dist/tracker.js.map +0 -1
package/dist/adapter.d.ts CHANGED
@@ -1,22 +1,18 @@
1
- import { BehaviorInsights } from "./types.js";
2
- import { OverlaySpec } from "@contractspec/lib.overlay-engine";
3
-
4
- //#region src/adapter.d.ts
5
- interface OverlaySuggestionOptions {
6
- overlayId: string;
7
- tenantId: string;
8
- capability: string;
9
- userId?: string;
10
- role?: string;
11
- version?: string;
1
+ import type { OverlaySpec } from '@contractspec/lib.overlay-engine';
2
+ import type { BehaviorInsights } from './types';
3
+ export interface OverlaySuggestionOptions {
4
+ overlayId: string;
5
+ tenantId: string;
6
+ capability: string;
7
+ userId?: string;
8
+ role?: string;
9
+ version?: string;
12
10
  }
13
- interface WorkflowAdaptation {
14
- workflow: string;
15
- step: string;
16
- note: string;
11
+ export interface WorkflowAdaptation {
12
+ workflow: string;
13
+ step: string;
14
+ note: string;
17
15
  }
18
- declare function insightsToOverlaySuggestion(insights: BehaviorInsights, options: OverlaySuggestionOptions): OverlaySpec | null;
19
- declare function insightsToWorkflowAdaptations(insights: BehaviorInsights): WorkflowAdaptation[];
20
- //#endregion
21
- export { OverlaySuggestionOptions, WorkflowAdaptation, insightsToOverlaySuggestion, insightsToWorkflowAdaptations };
16
+ export declare function insightsToOverlaySuggestion(insights: BehaviorInsights, options: OverlaySuggestionOptions): OverlaySpec | null;
17
+ export declare function insightsToWorkflowAdaptations(insights: BehaviorInsights): WorkflowAdaptation[];
22
18
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","names":[],"sources":["../src/adapter.ts"],"mappings":";;;;UAMiB,wBAAA;EACf,SAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,kBAAA;EACf,QAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,iBAGc,2BAAA,CACd,QAAA,EAAU,gBAAA,EACV,OAAA,EAAS,wBAAA,GACR,WAAA;AAAA,iBAuCa,6BAAA,CACd,QAAA,EAAU,gBAAA,GACT,kBAAA"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACZ,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,wBAAwB,GAChC,WAAW,GAAG,IAAI,CAqCpB;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,gBAAgB,GACzB,kBAAkB,EAAE,CAMtB"}
package/dist/adapter.js CHANGED
@@ -1,42 +1,47 @@
1
- //#region src/adapter.ts
1
+ // @bun
2
+ // src/adapter.ts
2
3
  function insightsToOverlaySuggestion(insights, options) {
3
- const modifications = [];
4
- insights.suggestedHiddenFields.forEach((field) => {
5
- modifications.push({
6
- type: "hideField",
7
- field,
8
- reason: "Automatically hidden because usage is near zero"
9
- });
10
- });
11
- if (insights.frequentlyUsedFields.length) modifications.push({
12
- type: "reorderFields",
13
- fields: insights.frequentlyUsedFields
14
- });
15
- if (!modifications.length) return null;
16
- return {
17
- overlayId: options.overlayId,
18
- version: options.version ?? "1.0.0",
19
- appliesTo: {
20
- tenantId: options.tenantId,
21
- capability: options.capability,
22
- userId: options.userId,
23
- role: options.role
24
- },
25
- modifications,
26
- metadata: {
27
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
28
- automated: true
29
- }
30
- };
4
+ const modifications = [];
5
+ insights.suggestedHiddenFields.forEach((field) => {
6
+ modifications.push({
7
+ type: "hideField",
8
+ field,
9
+ reason: "Automatically hidden because usage is near zero"
10
+ });
11
+ });
12
+ if (insights.frequentlyUsedFields.length) {
13
+ modifications.push({
14
+ type: "reorderFields",
15
+ fields: insights.frequentlyUsedFields
16
+ });
17
+ }
18
+ if (!modifications.length) {
19
+ return null;
20
+ }
21
+ return {
22
+ overlayId: options.overlayId,
23
+ version: options.version ?? "1.0.0",
24
+ appliesTo: {
25
+ tenantId: options.tenantId,
26
+ capability: options.capability,
27
+ userId: options.userId,
28
+ role: options.role
29
+ },
30
+ modifications,
31
+ metadata: {
32
+ generatedAt: new Date().toISOString(),
33
+ automated: true
34
+ }
35
+ };
31
36
  }
32
37
  function insightsToWorkflowAdaptations(insights) {
33
- return insights.workflowBottlenecks.map((bottleneck) => ({
34
- workflow: bottleneck.workflow,
35
- step: bottleneck.step,
36
- note: `High drop rate (${Math.round(bottleneck.dropRate * 100)}%) detected`
37
- }));
38
+ return insights.workflowBottlenecks.map((bottleneck) => ({
39
+ workflow: bottleneck.workflow,
40
+ step: bottleneck.step,
41
+ note: `High drop rate (${Math.round(bottleneck.dropRate * 100)}%) detected`
42
+ }));
38
43
  }
39
-
40
- //#endregion
41
- export { insightsToOverlaySuggestion, insightsToWorkflowAdaptations };
42
- //# sourceMappingURL=adapter.js.map
44
+ export {
45
+ insightsToWorkflowAdaptations,
46
+ insightsToOverlaySuggestion
47
+ };
@@ -1,23 +1,19 @@
1
- import { BehaviorInsights } from "./types.js";
2
- import { BehaviorStore } from "./store.js";
3
-
4
- //#region src/analyzer.d.ts
5
- interface BehaviorAnalyzerOptions {
6
- fieldInactivityThreshold?: number;
7
- minSamples?: number;
1
+ import type { BehaviorStore } from './store';
2
+ import type { BehaviorInsights } from './types';
3
+ export interface BehaviorAnalyzerOptions {
4
+ fieldInactivityThreshold?: number;
5
+ minSamples?: number;
8
6
  }
9
- interface AnalyzeParams {
10
- tenantId: string;
11
- userId?: string;
12
- role?: string;
13
- windowMs?: number;
7
+ export interface AnalyzeParams {
8
+ tenantId: string;
9
+ userId?: string;
10
+ role?: string;
11
+ windowMs?: number;
14
12
  }
15
- declare class BehaviorAnalyzer {
16
- private readonly store;
17
- private readonly options;
18
- constructor(store: BehaviorStore, options?: BehaviorAnalyzerOptions);
19
- analyze(params: AnalyzeParams): Promise<BehaviorInsights>;
13
+ export declare class BehaviorAnalyzer {
14
+ private readonly store;
15
+ private readonly options;
16
+ constructor(store: BehaviorStore, options?: BehaviorAnalyzerOptions);
17
+ analyze(params: AnalyzeParams): Promise<BehaviorInsights>;
20
18
  }
21
- //#endregion
22
- export { AnalyzeParams, BehaviorAnalyzer, BehaviorAnalyzerOptions };
23
19
  //# sourceMappingURL=analyzer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","names":[],"sources":["../src/analyzer.ts"],"mappings":";;;;UAGiB,uBAAA;EACf,wBAAA;EACA,UAAA;AAAA;AAAA,UAGe,aAAA;EACf,QAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,cAKW,gBAAA;EAAA,iBAEQ,KAAA;EAAA,iBACA,OAAA;cADA,KAAA,EAAO,aAAA,EACP,OAAA,GAAS,uBAAA;EAGtB,OAAA,CAAQ,MAAA,EAAQ,aAAA,GAAgB,OAAA,CAAQ,gBAAA;AAAA"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAkC,MAAM,SAAS,CAAC;AAEhF,MAAM,WAAW,uBAAuB;IACtC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,uBAA4B;IAGlD,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAchE"}
package/dist/analyzer.js CHANGED
@@ -1,55 +1,73 @@
1
- //#region src/analyzer.ts
2
- const DEFAULT_THRESHOLD = 3;
3
- var BehaviorAnalyzer = class {
4
- constructor(store, options = {}) {
5
- this.store = store;
6
- this.options = options;
7
- }
8
- async analyze(params) {
9
- const query = {
10
- tenantId: params.tenantId,
11
- userId: params.userId,
12
- role: params.role
13
- };
14
- if (params.windowMs) query.since = Date.now() - params.windowMs;
15
- return buildInsights(await this.store.summarize(query), this.options);
16
- }
17
- };
1
+ // @bun
2
+ // src/analyzer.ts
3
+ var DEFAULT_THRESHOLD = 3;
4
+
5
+ class BehaviorAnalyzer {
6
+ store;
7
+ options;
8
+ constructor(store, options = {}) {
9
+ this.store = store;
10
+ this.options = options;
11
+ }
12
+ async analyze(params) {
13
+ const query = {
14
+ tenantId: params.tenantId,
15
+ userId: params.userId,
16
+ role: params.role
17
+ };
18
+ if (params.windowMs) {
19
+ query.since = Date.now() - params.windowMs;
20
+ }
21
+ const summary = await this.store.summarize(query);
22
+ return buildInsights(summary, this.options);
23
+ }
24
+ }
18
25
  function buildInsights(summary, options) {
19
- const threshold = options.fieldInactivityThreshold ?? DEFAULT_THRESHOLD;
20
- const minSamples = options.minSamples ?? 10;
21
- const ignoredFields = [];
22
- const frequentlyUsedFields = [];
23
- for (const [field, count] of Object.entries(summary.fieldCounts)) {
24
- if (count <= threshold) ignoredFields.push(field);
25
- if (count >= threshold * 4) frequentlyUsedFields.push(field);
26
- }
27
- const workflowBottlenecks = Object.entries(summary.workflowStepCounts).flatMap(([workflow, steps]) => {
28
- const total = Object.values(steps).reduce((acc, value) => acc + value, 0);
29
- if (!total || total < minSamples) return [];
30
- return Object.entries(steps).filter(([, count]) => count / total < .4).map(([step, count]) => ({
31
- workflow,
32
- step,
33
- dropRate: 1 - count / total
34
- }));
35
- });
36
- const layoutPreference = detectLayout(summary);
37
- return {
38
- unusedFields: ignoredFields,
39
- suggestedHiddenFields: ignoredFields.slice(0, 5),
40
- frequentlyUsedFields: frequentlyUsedFields.slice(0, 10),
41
- workflowBottlenecks,
42
- layoutPreference
43
- };
26
+ const threshold = options.fieldInactivityThreshold ?? DEFAULT_THRESHOLD;
27
+ const minSamples = options.minSamples ?? 10;
28
+ const ignoredFields = [];
29
+ const frequentlyUsedFields = [];
30
+ for (const [field, count] of Object.entries(summary.fieldCounts)) {
31
+ if (count <= threshold) {
32
+ ignoredFields.push(field);
33
+ }
34
+ if (count >= threshold * 4) {
35
+ frequentlyUsedFields.push(field);
36
+ }
37
+ }
38
+ const workflowBottlenecks = Object.entries(summary.workflowStepCounts).flatMap(([workflow, steps]) => {
39
+ const total = Object.values(steps).reduce((acc, value) => acc + value, 0);
40
+ if (!total || total < minSamples) {
41
+ return [];
42
+ }
43
+ return Object.entries(steps).filter(([, count]) => count / total < 0.4).map(([step, count]) => ({
44
+ workflow,
45
+ step,
46
+ dropRate: 1 - count / total
47
+ }));
48
+ });
49
+ const layoutPreference = detectLayout(summary);
50
+ return {
51
+ unusedFields: ignoredFields,
52
+ suggestedHiddenFields: ignoredFields.slice(0, 5),
53
+ frequentlyUsedFields: frequentlyUsedFields.slice(0, 10),
54
+ workflowBottlenecks,
55
+ layoutPreference
56
+ };
44
57
  }
45
58
  function detectLayout(summary) {
46
- const fieldCount = Object.keys(summary.fieldCounts).length;
47
- if (!fieldCount) return;
48
- if (fieldCount >= 15) return "table";
49
- if (fieldCount >= 8) return "grid";
50
- return "form";
59
+ const fieldCount = Object.keys(summary.fieldCounts).length;
60
+ if (!fieldCount) {
61
+ return;
62
+ }
63
+ if (fieldCount >= 15) {
64
+ return "table";
65
+ }
66
+ if (fieldCount >= 8) {
67
+ return "grid";
68
+ }
69
+ return "form";
51
70
  }
52
-
53
- //#endregion
54
- export { BehaviorAnalyzer };
55
- //# sourceMappingURL=analyzer.js.map
71
+ export {
72
+ BehaviorAnalyzer
73
+ };
@@ -0,0 +1,46 @@
1
+ // src/adapter.ts
2
+ function insightsToOverlaySuggestion(insights, options) {
3
+ const modifications = [];
4
+ insights.suggestedHiddenFields.forEach((field) => {
5
+ modifications.push({
6
+ type: "hideField",
7
+ field,
8
+ reason: "Automatically hidden because usage is near zero"
9
+ });
10
+ });
11
+ if (insights.frequentlyUsedFields.length) {
12
+ modifications.push({
13
+ type: "reorderFields",
14
+ fields: insights.frequentlyUsedFields
15
+ });
16
+ }
17
+ if (!modifications.length) {
18
+ return null;
19
+ }
20
+ return {
21
+ overlayId: options.overlayId,
22
+ version: options.version ?? "1.0.0",
23
+ appliesTo: {
24
+ tenantId: options.tenantId,
25
+ capability: options.capability,
26
+ userId: options.userId,
27
+ role: options.role
28
+ },
29
+ modifications,
30
+ metadata: {
31
+ generatedAt: new Date().toISOString(),
32
+ automated: true
33
+ }
34
+ };
35
+ }
36
+ function insightsToWorkflowAdaptations(insights) {
37
+ return insights.workflowBottlenecks.map((bottleneck) => ({
38
+ workflow: bottleneck.workflow,
39
+ step: bottleneck.step,
40
+ note: `High drop rate (${Math.round(bottleneck.dropRate * 100)}%) detected`
41
+ }));
42
+ }
43
+ export {
44
+ insightsToWorkflowAdaptations,
45
+ insightsToOverlaySuggestion
46
+ };
@@ -0,0 +1,72 @@
1
+ // src/analyzer.ts
2
+ var DEFAULT_THRESHOLD = 3;
3
+
4
+ class BehaviorAnalyzer {
5
+ store;
6
+ options;
7
+ constructor(store, options = {}) {
8
+ this.store = store;
9
+ this.options = options;
10
+ }
11
+ async analyze(params) {
12
+ const query = {
13
+ tenantId: params.tenantId,
14
+ userId: params.userId,
15
+ role: params.role
16
+ };
17
+ if (params.windowMs) {
18
+ query.since = Date.now() - params.windowMs;
19
+ }
20
+ const summary = await this.store.summarize(query);
21
+ return buildInsights(summary, this.options);
22
+ }
23
+ }
24
+ function buildInsights(summary, options) {
25
+ const threshold = options.fieldInactivityThreshold ?? DEFAULT_THRESHOLD;
26
+ const minSamples = options.minSamples ?? 10;
27
+ const ignoredFields = [];
28
+ const frequentlyUsedFields = [];
29
+ for (const [field, count] of Object.entries(summary.fieldCounts)) {
30
+ if (count <= threshold) {
31
+ ignoredFields.push(field);
32
+ }
33
+ if (count >= threshold * 4) {
34
+ frequentlyUsedFields.push(field);
35
+ }
36
+ }
37
+ const workflowBottlenecks = Object.entries(summary.workflowStepCounts).flatMap(([workflow, steps]) => {
38
+ const total = Object.values(steps).reduce((acc, value) => acc + value, 0);
39
+ if (!total || total < minSamples) {
40
+ return [];
41
+ }
42
+ return Object.entries(steps).filter(([, count]) => count / total < 0.4).map(([step, count]) => ({
43
+ workflow,
44
+ step,
45
+ dropRate: 1 - count / total
46
+ }));
47
+ });
48
+ const layoutPreference = detectLayout(summary);
49
+ return {
50
+ unusedFields: ignoredFields,
51
+ suggestedHiddenFields: ignoredFields.slice(0, 5),
52
+ frequentlyUsedFields: frequentlyUsedFields.slice(0, 10),
53
+ workflowBottlenecks,
54
+ layoutPreference
55
+ };
56
+ }
57
+ function detectLayout(summary) {
58
+ const fieldCount = Object.keys(summary.fieldCounts).length;
59
+ if (!fieldCount) {
60
+ return;
61
+ }
62
+ if (fieldCount >= 15) {
63
+ return "table";
64
+ }
65
+ if (fieldCount >= 8) {
66
+ return "grid";
67
+ }
68
+ return "form";
69
+ }
70
+ export {
71
+ BehaviorAnalyzer
72
+ };
@@ -0,0 +1,97 @@
1
+ // src/docs/behavior-tracking.docblock.ts
2
+ import { registerDocBlocks } from "@contractspec/lib.contracts/docs";
3
+ var personalization_behavior_tracking_DocBlocks = [
4
+ {
5
+ id: "docs.personalization.behavior-tracking",
6
+ title: "Behavior Tracking",
7
+ summary: "`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",
8
+ kind: "reference",
9
+ visibility: "public",
10
+ route: "/docs/personalization/behavior-tracking",
11
+ tags: ["personalization", "behavior-tracking"],
12
+ body: `# Behavior Tracking
13
+
14
+ \`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
15
+
16
+ ## Tracker
17
+
18
+ \`\`\`ts
19
+ import { createBehaviorTracker } from '@contractspec/lib.personalization';
20
+ import { InMemoryBehaviorStore } from '@contractspec/lib.personalization/store';
21
+
22
+ const tracker = createBehaviorTracker({
23
+ store: new InMemoryBehaviorStore(),
24
+ context: { tenantId: ctx.tenant.id, userId: ctx.identity.userId },
25
+ autoFlushIntervalMs: 5000,
26
+ });
27
+
28
+ tracker.trackFieldAccess({ operation: 'billing.createOrder', field: 'internalNotes' });
29
+ tracker.trackFeatureUsage({ feature: 'workflow-editor', action: 'opened' });
30
+ tracker.trackWorkflowStep({ workflow: 'invoice-approval', step: 'review', status: 'entered' });
31
+ \`\`\`
32
+
33
+ All events are buffered and flushed either when the buffer hits 25 entries or when \`autoFlushIntervalMs\` elapses. Tracked metrics flow to OpenTelemetry via the meter/counter built into the tracker.
34
+
35
+ ## Analyzer
36
+
37
+ \`\`\`ts
38
+ import { BehaviorAnalyzer } from '@contractspec/lib.personalization/analyzer';
39
+
40
+ const analyzer = new BehaviorAnalyzer(store, { fieldInactivityThreshold: 2 });
41
+ const insights = await analyzer.analyze({ tenantId: 'acme', userId: 'manager-42', windowMs: 7 * 24 * 60 * 60 * 1000 });
42
+
43
+ /*
44
+ {
45
+ unusedFields: ['internalNotes'],
46
+ suggestedHiddenFields: ['internalNotes'],
47
+ frequentlyUsedFields: ['customerReference', 'items'],
48
+ workflowBottlenecks: [{ workflow: 'invoice-approval', step: 'finance-review', dropRate: 0.6 }],
49
+ layoutPreference: 'table'
50
+ }
51
+ */
52
+ \`\`\`
53
+
54
+ Use the analyzer output with the overlay adapter to generate suggestions automatically.
55
+
56
+ ## Adapter
57
+
58
+ \`\`\`ts
59
+ import { insightsToOverlaySuggestion } from '@contractspec/lib.personalization/adapter';
60
+
61
+ const overlay = insightsToOverlaySuggestion(insights, {
62
+ overlayId: 'acme-order-form',
63
+ tenantId: 'acme',
64
+ capability: 'billing.createOrder',
65
+ });
66
+ \`\`\`
67
+
68
+ When the adapter returns an overlay spec, pass it to the overlay engine to register or sign it.
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+ `
92
+ }
93
+ ];
94
+ registerDocBlocks(personalization_behavior_tracking_DocBlocks);
95
+ export {
96
+ personalization_behavior_tracking_DocBlocks
97
+ };