@paysentry/observe 1.0.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.
- package/dist/alerts.d.ts +139 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +266 -0
- package/dist/alerts.js.map +1 -0
- package/dist/analytics.d.ts +91 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +216 -0
- package/dist/analytics.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/tracker.d.ts +97 -0
- package/dist/tracker.d.ts.map +1 -0
- package/dist/tracker.js +185 -0
- package/dist/tracker.js.map +1 -0
- package/package.json +28 -0
package/dist/alerts.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { AgentTransaction, AgentId, SpendAlert, AlertHandler, AlertSeverity, AlertType, Logger } from '@paysentry/core';
|
|
2
|
+
import type { SpendTracker } from './tracker.js';
|
|
3
|
+
/** Configuration for a single alert rule */
|
|
4
|
+
export interface AlertRule {
|
|
5
|
+
/** Unique rule identifier */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/** Human-readable name */
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** Alert type */
|
|
10
|
+
readonly type: AlertType;
|
|
11
|
+
/** Alert severity */
|
|
12
|
+
readonly severity: AlertSeverity;
|
|
13
|
+
/** Whether the rule is active */
|
|
14
|
+
readonly enabled: boolean;
|
|
15
|
+
/** Rule configuration */
|
|
16
|
+
readonly config: AlertRuleConfig;
|
|
17
|
+
}
|
|
18
|
+
/** Configuration specific to each alert type */
|
|
19
|
+
export type AlertRuleConfig = BudgetThresholdConfig | LargeTransactionConfig | RateSpikeConfig | NewRecipientConfig | AnomalyConfig;
|
|
20
|
+
/** Alert when cumulative spend exceeds a threshold */
|
|
21
|
+
export interface BudgetThresholdConfig {
|
|
22
|
+
readonly type: 'budget_threshold';
|
|
23
|
+
/** Agent to monitor (undefined = all agents) */
|
|
24
|
+
readonly agentId?: AgentId;
|
|
25
|
+
/** Threshold amount */
|
|
26
|
+
readonly threshold: number;
|
|
27
|
+
/** Currency */
|
|
28
|
+
readonly currency: string;
|
|
29
|
+
/** Time window in milliseconds */
|
|
30
|
+
readonly windowMs: number;
|
|
31
|
+
/** Alert at what percentage of threshold (e.g., 0.8 = 80%) */
|
|
32
|
+
readonly alertAtPercent: number;
|
|
33
|
+
}
|
|
34
|
+
/** Alert on any single transaction above a threshold */
|
|
35
|
+
export interface LargeTransactionConfig {
|
|
36
|
+
readonly type: 'large_transaction';
|
|
37
|
+
/** Amount threshold */
|
|
38
|
+
readonly threshold: number;
|
|
39
|
+
/** Currency */
|
|
40
|
+
readonly currency: string;
|
|
41
|
+
}
|
|
42
|
+
/** Alert on transaction frequency spikes */
|
|
43
|
+
export interface RateSpikeConfig {
|
|
44
|
+
readonly type: 'rate_spike';
|
|
45
|
+
/** Agent to monitor (undefined = all agents) */
|
|
46
|
+
readonly agentId?: AgentId;
|
|
47
|
+
/** Maximum transactions per window */
|
|
48
|
+
readonly maxTransactions: number;
|
|
49
|
+
/** Window size in milliseconds */
|
|
50
|
+
readonly windowMs: number;
|
|
51
|
+
}
|
|
52
|
+
/** Alert when an agent transacts with a new recipient */
|
|
53
|
+
export interface NewRecipientConfig {
|
|
54
|
+
readonly type: 'new_recipient';
|
|
55
|
+
/** Agent to monitor (undefined = all agents) */
|
|
56
|
+
readonly agentId?: AgentId;
|
|
57
|
+
}
|
|
58
|
+
/** Alert on statistical anomalies in spending patterns */
|
|
59
|
+
export interface AnomalyConfig {
|
|
60
|
+
readonly type: 'anomaly';
|
|
61
|
+
/** Agent to monitor (undefined = all agents) */
|
|
62
|
+
readonly agentId?: AgentId;
|
|
63
|
+
/** Standard deviations above mean to trigger alert */
|
|
64
|
+
readonly stdDevThreshold: number;
|
|
65
|
+
/** Minimum number of transactions before anomaly detection activates */
|
|
66
|
+
readonly minSampleSize: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* SpendAlerts monitors the transaction stream and fires alerts when
|
|
70
|
+
* configured conditions are met. It supports budget thresholds, large
|
|
71
|
+
* transaction detection, rate spike detection, new recipient alerts,
|
|
72
|
+
* and statistical anomaly detection.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const alerts = new SpendAlerts(tracker);
|
|
77
|
+
*
|
|
78
|
+
* alerts.addRule({
|
|
79
|
+
* id: 'daily-budget',
|
|
80
|
+
* name: 'Daily USDC Budget',
|
|
81
|
+
* type: 'budget_threshold',
|
|
82
|
+
* severity: 'warning',
|
|
83
|
+
* enabled: true,
|
|
84
|
+
* config: {
|
|
85
|
+
* type: 'budget_threshold',
|
|
86
|
+
* threshold: 100,
|
|
87
|
+
* currency: 'USDC',
|
|
88
|
+
* windowMs: 86400000, // 24 hours
|
|
89
|
+
* alertAtPercent: 0.8,
|
|
90
|
+
* },
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* alerts.onAlert((alert) => {
|
|
94
|
+
* console.log(`[${alert.severity}] ${alert.message}`);
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Check a new transaction against all rules
|
|
98
|
+
* await alerts.evaluate(transaction);
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare class SpendAlerts {
|
|
102
|
+
private readonly tracker;
|
|
103
|
+
private readonly rules;
|
|
104
|
+
private readonly handlers;
|
|
105
|
+
private readonly knownRecipients;
|
|
106
|
+
private readonly logger?;
|
|
107
|
+
constructor(tracker: SpendTracker, options?: {
|
|
108
|
+
logger?: Logger;
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Add an alert rule.
|
|
112
|
+
*/
|
|
113
|
+
addRule(rule: AlertRule): void;
|
|
114
|
+
/**
|
|
115
|
+
* Remove an alert rule by ID.
|
|
116
|
+
*/
|
|
117
|
+
removeRule(id: string): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Register an alert handler. Called whenever an alert fires.
|
|
120
|
+
*/
|
|
121
|
+
onAlert(handler: AlertHandler): void;
|
|
122
|
+
/**
|
|
123
|
+
* Evaluate a transaction against all active alert rules.
|
|
124
|
+
* Fires alerts to all registered handlers if conditions are met.
|
|
125
|
+
*/
|
|
126
|
+
evaluate(tx: AgentTransaction): Promise<SpendAlert[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Get all configured rules.
|
|
129
|
+
*/
|
|
130
|
+
getRules(): AlertRule[];
|
|
131
|
+
private checkRule;
|
|
132
|
+
private checkBudgetThreshold;
|
|
133
|
+
private checkLargeTransaction;
|
|
134
|
+
private checkRateSpike;
|
|
135
|
+
private checkNewRecipient;
|
|
136
|
+
private checkAnomaly;
|
|
137
|
+
private createAlert;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=alerts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"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"}
|
package/dist/alerts.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SpendAlerts — Threshold alerts and anomaly detection
|
|
3
|
+
// Watches the transaction stream and fires alerts when conditions are met
|
|
4
|
+
// =============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* SpendAlerts monitors the transaction stream and fires alerts when
|
|
7
|
+
* configured conditions are met. It supports budget thresholds, large
|
|
8
|
+
* transaction detection, rate spike detection, new recipient alerts,
|
|
9
|
+
* and statistical anomaly detection.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const alerts = new SpendAlerts(tracker);
|
|
14
|
+
*
|
|
15
|
+
* alerts.addRule({
|
|
16
|
+
* id: 'daily-budget',
|
|
17
|
+
* name: 'Daily USDC Budget',
|
|
18
|
+
* type: 'budget_threshold',
|
|
19
|
+
* severity: 'warning',
|
|
20
|
+
* enabled: true,
|
|
21
|
+
* config: {
|
|
22
|
+
* type: 'budget_threshold',
|
|
23
|
+
* threshold: 100,
|
|
24
|
+
* currency: 'USDC',
|
|
25
|
+
* windowMs: 86400000, // 24 hours
|
|
26
|
+
* alertAtPercent: 0.8,
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* alerts.onAlert((alert) => {
|
|
31
|
+
* console.log(`[${alert.severity}] ${alert.message}`);
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Check a new transaction against all rules
|
|
35
|
+
* await alerts.evaluate(transaction);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class SpendAlerts {
|
|
39
|
+
tracker;
|
|
40
|
+
rules = new Map();
|
|
41
|
+
handlers = [];
|
|
42
|
+
knownRecipients = new Map();
|
|
43
|
+
logger;
|
|
44
|
+
constructor(tracker, options) {
|
|
45
|
+
this.tracker = tracker;
|
|
46
|
+
this.logger = options?.logger;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Add an alert rule.
|
|
50
|
+
*/
|
|
51
|
+
addRule(rule) {
|
|
52
|
+
this.rules.set(rule.id, rule);
|
|
53
|
+
this.logger?.info(`[SpendAlerts] Added rule: ${rule.name} (${rule.id})`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove an alert rule by ID.
|
|
57
|
+
*/
|
|
58
|
+
removeRule(id) {
|
|
59
|
+
const removed = this.rules.delete(id);
|
|
60
|
+
if (removed) {
|
|
61
|
+
this.logger?.info(`[SpendAlerts] Removed rule: ${id}`);
|
|
62
|
+
}
|
|
63
|
+
return removed;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Register an alert handler. Called whenever an alert fires.
|
|
67
|
+
*/
|
|
68
|
+
onAlert(handler) {
|
|
69
|
+
this.handlers.push(handler);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Evaluate a transaction against all active alert rules.
|
|
73
|
+
* Fires alerts to all registered handlers if conditions are met.
|
|
74
|
+
*/
|
|
75
|
+
async evaluate(tx) {
|
|
76
|
+
const alerts = [];
|
|
77
|
+
for (const rule of this.rules.values()) {
|
|
78
|
+
if (!rule.enabled)
|
|
79
|
+
continue;
|
|
80
|
+
const alert = this.checkRule(rule, tx);
|
|
81
|
+
if (alert) {
|
|
82
|
+
alerts.push(alert);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Fire all alerts to handlers
|
|
86
|
+
for (const alert of alerts) {
|
|
87
|
+
this.logger?.warn(`[SpendAlerts] Alert fired: ${alert.message}`);
|
|
88
|
+
for (const handler of this.handlers) {
|
|
89
|
+
try {
|
|
90
|
+
await handler(alert);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
this.logger?.error(`[SpendAlerts] Handler error: ${err}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return alerts;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get all configured rules.
|
|
101
|
+
*/
|
|
102
|
+
getRules() {
|
|
103
|
+
return [...this.rules.values()];
|
|
104
|
+
}
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Private rule evaluation
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
checkRule(rule, tx) {
|
|
109
|
+
switch (rule.config.type) {
|
|
110
|
+
case 'budget_threshold':
|
|
111
|
+
return this.checkBudgetThreshold(rule, tx, rule.config);
|
|
112
|
+
case 'large_transaction':
|
|
113
|
+
return this.checkLargeTransaction(rule, tx, rule.config);
|
|
114
|
+
case 'rate_spike':
|
|
115
|
+
return this.checkRateSpike(rule, tx, rule.config);
|
|
116
|
+
case 'new_recipient':
|
|
117
|
+
return this.checkNewRecipient(rule, tx, rule.config);
|
|
118
|
+
case 'anomaly':
|
|
119
|
+
return this.checkAnomaly(rule, tx, rule.config);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
checkBudgetThreshold(rule, tx, config) {
|
|
123
|
+
if (config.agentId && tx.agentId !== config.agentId)
|
|
124
|
+
return null;
|
|
125
|
+
if (tx.currency !== config.currency)
|
|
126
|
+
return null;
|
|
127
|
+
const windowStart = new Date(Date.now() - config.windowMs).toISOString();
|
|
128
|
+
const recentTxs = this.tracker.query({
|
|
129
|
+
agentId: config.agentId,
|
|
130
|
+
currency: config.currency,
|
|
131
|
+
status: 'completed',
|
|
132
|
+
after: windowStart,
|
|
133
|
+
});
|
|
134
|
+
const currentSpend = recentTxs.reduce((sum, t) => sum + t.amount, 0);
|
|
135
|
+
const projectedSpend = currentSpend + tx.amount;
|
|
136
|
+
const thresholdAmount = config.threshold * config.alertAtPercent;
|
|
137
|
+
if (projectedSpend >= thresholdAmount) {
|
|
138
|
+
const percent = Math.round((projectedSpend / config.threshold) * 100);
|
|
139
|
+
return this.createAlert(rule, tx, {
|
|
140
|
+
message: `Budget ${percent}% utilized: $${projectedSpend.toFixed(2)} of $${config.threshold} ${config.currency} limit`,
|
|
141
|
+
data: {
|
|
142
|
+
currentSpend,
|
|
143
|
+
projectedSpend,
|
|
144
|
+
threshold: config.threshold,
|
|
145
|
+
percentUsed: percent,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
checkLargeTransaction(rule, tx, config) {
|
|
152
|
+
if (tx.currency !== config.currency)
|
|
153
|
+
return null;
|
|
154
|
+
if (tx.amount < config.threshold)
|
|
155
|
+
return null;
|
|
156
|
+
return this.createAlert(rule, tx, {
|
|
157
|
+
message: `Large transaction detected: $${tx.amount} ${tx.currency} to ${tx.recipient}`,
|
|
158
|
+
data: {
|
|
159
|
+
amount: tx.amount,
|
|
160
|
+
threshold: config.threshold,
|
|
161
|
+
recipient: tx.recipient,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
checkRateSpike(rule, tx, config) {
|
|
166
|
+
if (config.agentId && tx.agentId !== config.agentId)
|
|
167
|
+
return null;
|
|
168
|
+
const windowStart = new Date(Date.now() - config.windowMs).toISOString();
|
|
169
|
+
const recentTxs = this.tracker.query({
|
|
170
|
+
agentId: config.agentId,
|
|
171
|
+
after: windowStart,
|
|
172
|
+
});
|
|
173
|
+
// +1 for the current transaction being evaluated
|
|
174
|
+
const txCount = recentTxs.length + 1;
|
|
175
|
+
if (txCount > config.maxTransactions) {
|
|
176
|
+
return this.createAlert(rule, tx, {
|
|
177
|
+
message: `Transaction rate spike: ${txCount} transactions in ${config.windowMs / 1000}s window (limit: ${config.maxTransactions})`,
|
|
178
|
+
data: {
|
|
179
|
+
transactionCount: txCount,
|
|
180
|
+
maxTransactions: config.maxTransactions,
|
|
181
|
+
windowMs: config.windowMs,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
checkNewRecipient(rule, tx, config) {
|
|
188
|
+
if (config.agentId && tx.agentId !== config.agentId)
|
|
189
|
+
return null;
|
|
190
|
+
const agentKey = config.agentId ?? '__all__';
|
|
191
|
+
let known = this.knownRecipients.get(agentKey);
|
|
192
|
+
if (!known) {
|
|
193
|
+
// Initialize from existing transactions
|
|
194
|
+
known = new Set();
|
|
195
|
+
const existingTxs = config.agentId
|
|
196
|
+
? this.tracker.getByAgent(config.agentId)
|
|
197
|
+
: this.tracker.query({});
|
|
198
|
+
for (const existingTx of existingTxs) {
|
|
199
|
+
known.add(existingTx.recipient);
|
|
200
|
+
}
|
|
201
|
+
this.knownRecipients.set(agentKey, known);
|
|
202
|
+
}
|
|
203
|
+
if (!known.has(tx.recipient)) {
|
|
204
|
+
known.add(tx.recipient);
|
|
205
|
+
return this.createAlert(rule, tx, {
|
|
206
|
+
message: `New recipient detected: ${tx.recipient} (agent: ${tx.agentId})`,
|
|
207
|
+
data: {
|
|
208
|
+
recipient: tx.recipient,
|
|
209
|
+
agentId: tx.agentId,
|
|
210
|
+
knownRecipientCount: known.size,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
checkAnomaly(rule, tx, config) {
|
|
217
|
+
if (config.agentId && tx.agentId !== config.agentId)
|
|
218
|
+
return null;
|
|
219
|
+
const historicalTxs = config.agentId
|
|
220
|
+
? this.tracker.getByAgent(config.agentId)
|
|
221
|
+
: this.tracker.query({});
|
|
222
|
+
const completedAmounts = historicalTxs
|
|
223
|
+
.filter((t) => t.status === 'completed' && t.currency === tx.currency)
|
|
224
|
+
.map((t) => t.amount);
|
|
225
|
+
if (completedAmounts.length < config.minSampleSize)
|
|
226
|
+
return null;
|
|
227
|
+
const mean = completedAmounts.reduce((s, a) => s + a, 0) / completedAmounts.length;
|
|
228
|
+
const variance = completedAmounts.reduce((s, a) => s + Math.pow(a - mean, 2), 0) / completedAmounts.length;
|
|
229
|
+
const stdDev = Math.sqrt(variance);
|
|
230
|
+
if (stdDev === 0)
|
|
231
|
+
return null;
|
|
232
|
+
const zScore = (tx.amount - mean) / stdDev;
|
|
233
|
+
if (zScore > config.stdDevThreshold) {
|
|
234
|
+
return this.createAlert(rule, tx, {
|
|
235
|
+
message: `Anomalous transaction amount: $${tx.amount} ${tx.currency} (${zScore.toFixed(1)} standard deviations above mean of $${mean.toFixed(2)})`,
|
|
236
|
+
data: {
|
|
237
|
+
amount: tx.amount,
|
|
238
|
+
mean: Math.round(mean * 100) / 100,
|
|
239
|
+
stdDev: Math.round(stdDev * 100) / 100,
|
|
240
|
+
zScore: Math.round(zScore * 100) / 100,
|
|
241
|
+
sampleSize: completedAmounts.length,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Alert factory
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
createAlert(rule, tx, overrides) {
|
|
251
|
+
return {
|
|
252
|
+
type: rule.type,
|
|
253
|
+
severity: rule.severity,
|
|
254
|
+
message: overrides.message,
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
agentId: tx.agentId,
|
|
257
|
+
transactionId: tx.id,
|
|
258
|
+
data: {
|
|
259
|
+
...overrides.data,
|
|
260
|
+
ruleId: rule.id,
|
|
261
|
+
ruleName: rule.name,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentTransaction, AgentId, TimeWindow } from '@paysentry/core';
|
|
2
|
+
import type { SpendTracker } from './tracker.js';
|
|
3
|
+
/** Aggregated spend summary for a single entity */
|
|
4
|
+
export interface SpendSummary {
|
|
5
|
+
/** Total amount spent */
|
|
6
|
+
readonly totalAmount: number;
|
|
7
|
+
/** Number of transactions */
|
|
8
|
+
readonly transactionCount: number;
|
|
9
|
+
/** Average transaction amount */
|
|
10
|
+
readonly averageAmount: number;
|
|
11
|
+
/** Largest single transaction */
|
|
12
|
+
readonly maxAmount: number;
|
|
13
|
+
/** Smallest single transaction */
|
|
14
|
+
readonly minAmount: number;
|
|
15
|
+
/** Currency (summaries are per-currency) */
|
|
16
|
+
readonly currency: string;
|
|
17
|
+
}
|
|
18
|
+
/** Spend breakdown by time period */
|
|
19
|
+
export interface TimeSeriesPoint {
|
|
20
|
+
/** ISO 8601 period start */
|
|
21
|
+
readonly periodStart: string;
|
|
22
|
+
/** ISO 8601 period end */
|
|
23
|
+
readonly periodEnd: string;
|
|
24
|
+
/** Total spend in this period */
|
|
25
|
+
readonly totalAmount: number;
|
|
26
|
+
/** Number of transactions */
|
|
27
|
+
readonly transactionCount: number;
|
|
28
|
+
}
|
|
29
|
+
/** Complete analytics snapshot for an agent */
|
|
30
|
+
export interface AgentAnalytics {
|
|
31
|
+
/** Agent identifier */
|
|
32
|
+
readonly agentId: AgentId;
|
|
33
|
+
/** Spend by currency */
|
|
34
|
+
readonly spendByCurrency: ReadonlyMap<string, SpendSummary>;
|
|
35
|
+
/** Spend by service */
|
|
36
|
+
readonly spendByService: ReadonlyMap<string, SpendSummary>;
|
|
37
|
+
/** Spend by protocol */
|
|
38
|
+
readonly spendByProtocol: ReadonlyMap<string, SpendSummary>;
|
|
39
|
+
/** Top recipients by total spend */
|
|
40
|
+
readonly topRecipients: readonly {
|
|
41
|
+
recipient: string;
|
|
42
|
+
totalAmount: number;
|
|
43
|
+
count: number;
|
|
44
|
+
}[];
|
|
45
|
+
/** Time series data */
|
|
46
|
+
readonly timeSeries: readonly TimeSeriesPoint[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* SpendAnalytics provides computed insights from raw transaction data.
|
|
50
|
+
* It reads from a SpendTracker and produces aggregated analytics.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const analytics = new SpendAnalytics(tracker);
|
|
55
|
+
* const agentReport = analytics.getAgentAnalytics('agent-1' as AgentId, 'daily');
|
|
56
|
+
* console.log(agentReport.spendByCurrency);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class SpendAnalytics {
|
|
60
|
+
private readonly tracker;
|
|
61
|
+
constructor(tracker: SpendTracker);
|
|
62
|
+
/**
|
|
63
|
+
* Compute a spend summary from a set of transactions.
|
|
64
|
+
*/
|
|
65
|
+
summarize(transactions: readonly AgentTransaction[], currency: string): SpendSummary;
|
|
66
|
+
/**
|
|
67
|
+
* Get comprehensive analytics for a specific agent.
|
|
68
|
+
*
|
|
69
|
+
* @param agentId - Agent to analyze
|
|
70
|
+
* @param timeWindow - Granularity for time series data
|
|
71
|
+
* @param since - Only consider transactions after this ISO 8601 timestamp
|
|
72
|
+
*/
|
|
73
|
+
getAgentAnalytics(agentId: AgentId, timeWindow?: TimeWindow, since?: string): AgentAnalytics;
|
|
74
|
+
/**
|
|
75
|
+
* Get the total spend across all agents within a time range.
|
|
76
|
+
*/
|
|
77
|
+
getTotalSpend(currency: string, since?: string, until?: string): SpendSummary;
|
|
78
|
+
/**
|
|
79
|
+
* Get a leaderboard of agents by total spend.
|
|
80
|
+
*/
|
|
81
|
+
getAgentLeaderboard(currency: string, limit?: number): {
|
|
82
|
+
agentId: AgentId;
|
|
83
|
+
totalAmount: number;
|
|
84
|
+
count: number;
|
|
85
|
+
}[];
|
|
86
|
+
private groupBy;
|
|
87
|
+
private buildTimeSeries;
|
|
88
|
+
private getPeriodKey;
|
|
89
|
+
private getNextPeriod;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gBAAgB,EAChB,OAAO,EAGP,UAAU,EACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,yBAAyB;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,iCAAiC;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,kCAAkC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,0BAA0B;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAE1B,wBAAwB;IACxB,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE5D,uBAAuB;IACvB,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE3D,wBAAwB;IACxB,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE5D,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,SAAS;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAE7F,uBAAuB;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAElD;;OAEG;IACH,SAAS,CAAC,YAAY,EAAE,SAAS,gBAAgB,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY;IA2BpF;;;;;;OAMG;IACH,iBAAiB,CACf,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,UAAoB,EAChC,KAAK,CAAC,EAAE,MAAM,GACb,cAAc;IA6DjB;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY;IAU7E;;OAEG;IACH,mBAAmB,CACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAW,GACjB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE;IAuB7D,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,aAAa;CAoBtB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SpendAnalytics — Per-agent, per-service, per-time breakdowns
|
|
3
|
+
// Turns raw transaction data into actionable spending intelligence
|
|
4
|
+
// =============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* SpendAnalytics provides computed insights from raw transaction data.
|
|
7
|
+
* It reads from a SpendTracker and produces aggregated analytics.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const analytics = new SpendAnalytics(tracker);
|
|
12
|
+
* const agentReport = analytics.getAgentAnalytics('agent-1' as AgentId, 'daily');
|
|
13
|
+
* console.log(agentReport.spendByCurrency);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class SpendAnalytics {
|
|
17
|
+
tracker;
|
|
18
|
+
constructor(tracker) {
|
|
19
|
+
this.tracker = tracker;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compute a spend summary from a set of transactions.
|
|
23
|
+
*/
|
|
24
|
+
summarize(transactions, currency) {
|
|
25
|
+
const filtered = transactions.filter((tx) => tx.currency === currency && tx.status === 'completed');
|
|
26
|
+
if (filtered.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
totalAmount: 0,
|
|
29
|
+
transactionCount: 0,
|
|
30
|
+
averageAmount: 0,
|
|
31
|
+
maxAmount: 0,
|
|
32
|
+
minAmount: 0,
|
|
33
|
+
currency,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const amounts = filtered.map((tx) => tx.amount);
|
|
37
|
+
const total = amounts.reduce((sum, a) => sum + a, 0);
|
|
38
|
+
return {
|
|
39
|
+
totalAmount: Math.round(total * 1e6) / 1e6,
|
|
40
|
+
transactionCount: filtered.length,
|
|
41
|
+
averageAmount: Math.round((total / filtered.length) * 1e6) / 1e6,
|
|
42
|
+
maxAmount: Math.max(...amounts),
|
|
43
|
+
minAmount: Math.min(...amounts),
|
|
44
|
+
currency,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get comprehensive analytics for a specific agent.
|
|
49
|
+
*
|
|
50
|
+
* @param agentId - Agent to analyze
|
|
51
|
+
* @param timeWindow - Granularity for time series data
|
|
52
|
+
* @param since - Only consider transactions after this ISO 8601 timestamp
|
|
53
|
+
*/
|
|
54
|
+
getAgentAnalytics(agentId, timeWindow = 'daily', since) {
|
|
55
|
+
let transactions = this.tracker.getByAgent(agentId);
|
|
56
|
+
if (since) {
|
|
57
|
+
transactions = transactions.filter((tx) => tx.createdAt >= since);
|
|
58
|
+
}
|
|
59
|
+
// Spend by currency
|
|
60
|
+
const currencies = new Set(transactions.map((tx) => tx.currency));
|
|
61
|
+
const spendByCurrency = new Map();
|
|
62
|
+
for (const currency of currencies) {
|
|
63
|
+
spendByCurrency.set(currency, this.summarize(transactions, currency));
|
|
64
|
+
}
|
|
65
|
+
// Spend by service
|
|
66
|
+
const spendByService = new Map();
|
|
67
|
+
const serviceGroups = this.groupBy(transactions, (tx) => tx.service ?? 'unknown');
|
|
68
|
+
for (const [service, txs] of serviceGroups) {
|
|
69
|
+
const currencies2 = new Set(txs.map((tx) => tx.currency));
|
|
70
|
+
for (const currency of currencies2) {
|
|
71
|
+
const key = `${service}:${currency}`;
|
|
72
|
+
spendByService.set(key, this.summarize(txs, currency));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Spend by protocol
|
|
76
|
+
const spendByProtocol = new Map();
|
|
77
|
+
const protocolGroups = this.groupBy(transactions, (tx) => tx.protocol);
|
|
78
|
+
for (const [protocol, txs] of protocolGroups) {
|
|
79
|
+
const currencies2 = new Set(txs.map((tx) => tx.currency));
|
|
80
|
+
for (const currency of currencies2) {
|
|
81
|
+
spendByProtocol.set(`${protocol}:${currency}`, this.summarize(txs, currency));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Top recipients
|
|
85
|
+
const recipientMap = new Map();
|
|
86
|
+
for (const tx of transactions.filter((t) => t.status === 'completed')) {
|
|
87
|
+
const entry = recipientMap.get(tx.recipient) ?? { totalAmount: 0, count: 0 };
|
|
88
|
+
entry.totalAmount += tx.amount;
|
|
89
|
+
entry.count++;
|
|
90
|
+
recipientMap.set(tx.recipient, entry);
|
|
91
|
+
}
|
|
92
|
+
const topRecipients = [...recipientMap.entries()]
|
|
93
|
+
.map(([recipient, data]) => ({ recipient, ...data }))
|
|
94
|
+
.sort((a, b) => b.totalAmount - a.totalAmount)
|
|
95
|
+
.slice(0, 10);
|
|
96
|
+
// Time series
|
|
97
|
+
const timeSeries = this.buildTimeSeries(transactions, timeWindow);
|
|
98
|
+
return {
|
|
99
|
+
agentId,
|
|
100
|
+
spendByCurrency,
|
|
101
|
+
spendByService,
|
|
102
|
+
spendByProtocol,
|
|
103
|
+
topRecipients,
|
|
104
|
+
timeSeries,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the total spend across all agents within a time range.
|
|
109
|
+
*/
|
|
110
|
+
getTotalSpend(currency, since, until) {
|
|
111
|
+
const all = this.tracker.query({
|
|
112
|
+
currency,
|
|
113
|
+
status: 'completed',
|
|
114
|
+
after: since,
|
|
115
|
+
before: until,
|
|
116
|
+
});
|
|
117
|
+
return this.summarize(all, currency);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get a leaderboard of agents by total spend.
|
|
121
|
+
*/
|
|
122
|
+
getAgentLeaderboard(currency, limit = 10) {
|
|
123
|
+
const agentMap = new Map();
|
|
124
|
+
for (const agentId of this.tracker.agents) {
|
|
125
|
+
const txs = this.tracker.getByAgent(agentId).filter((tx) => tx.currency === currency && tx.status === 'completed');
|
|
126
|
+
if (txs.length > 0) {
|
|
127
|
+
const total = txs.reduce((sum, tx) => sum + tx.amount, 0);
|
|
128
|
+
agentMap.set(agentId, { totalAmount: total, count: txs.length });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return [...agentMap.entries()]
|
|
132
|
+
.map(([agentId, data]) => ({ agentId: agentId, ...data }))
|
|
133
|
+
.sort((a, b) => b.totalAmount - a.totalAmount)
|
|
134
|
+
.slice(0, limit);
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Private helpers
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
groupBy(items, keyFn) {
|
|
140
|
+
const groups = new Map();
|
|
141
|
+
for (const item of items) {
|
|
142
|
+
const key = keyFn(item);
|
|
143
|
+
let group = groups.get(key);
|
|
144
|
+
if (!group) {
|
|
145
|
+
group = [];
|
|
146
|
+
groups.set(key, group);
|
|
147
|
+
}
|
|
148
|
+
group.push(item);
|
|
149
|
+
}
|
|
150
|
+
return groups;
|
|
151
|
+
}
|
|
152
|
+
buildTimeSeries(transactions, window) {
|
|
153
|
+
if (transactions.length === 0)
|
|
154
|
+
return [];
|
|
155
|
+
const completed = transactions.filter((tx) => tx.status === 'completed');
|
|
156
|
+
if (completed.length === 0)
|
|
157
|
+
return [];
|
|
158
|
+
const buckets = new Map();
|
|
159
|
+
for (const tx of completed) {
|
|
160
|
+
const key = this.getPeriodKey(tx.createdAt, window);
|
|
161
|
+
const bucket = buckets.get(key) ?? { total: 0, count: 0 };
|
|
162
|
+
bucket.total += tx.amount;
|
|
163
|
+
bucket.count++;
|
|
164
|
+
buckets.set(key, bucket);
|
|
165
|
+
}
|
|
166
|
+
return [...buckets.entries()]
|
|
167
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
168
|
+
.map(([key, data]) => ({
|
|
169
|
+
periodStart: key,
|
|
170
|
+
periodEnd: this.getNextPeriod(key, window),
|
|
171
|
+
totalAmount: Math.round(data.total * 1e6) / 1e6,
|
|
172
|
+
transactionCount: data.count,
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
getPeriodKey(isoDate, window) {
|
|
176
|
+
const date = new Date(isoDate);
|
|
177
|
+
switch (window) {
|
|
178
|
+
case 'per_transaction':
|
|
179
|
+
return isoDate;
|
|
180
|
+
case 'hourly':
|
|
181
|
+
return date.toISOString().slice(0, 13) + ':00:00.000Z';
|
|
182
|
+
case 'daily':
|
|
183
|
+
return date.toISOString().slice(0, 10) + 'T00:00:00.000Z';
|
|
184
|
+
case 'weekly': {
|
|
185
|
+
const day = date.getDay();
|
|
186
|
+
const mondayOffset = day === 0 ? -6 : 1 - day;
|
|
187
|
+
const monday = new Date(date);
|
|
188
|
+
monday.setDate(date.getDate() + mondayOffset);
|
|
189
|
+
return monday.toISOString().slice(0, 10) + 'T00:00:00.000Z';
|
|
190
|
+
}
|
|
191
|
+
case 'monthly':
|
|
192
|
+
return date.toISOString().slice(0, 7) + '-01T00:00:00.000Z';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
getNextPeriod(periodStart, window) {
|
|
196
|
+
const date = new Date(periodStart);
|
|
197
|
+
switch (window) {
|
|
198
|
+
case 'per_transaction':
|
|
199
|
+
return periodStart;
|
|
200
|
+
case 'hourly':
|
|
201
|
+
date.setHours(date.getHours() + 1);
|
|
202
|
+
break;
|
|
203
|
+
case 'daily':
|
|
204
|
+
date.setDate(date.getDate() + 1);
|
|
205
|
+
break;
|
|
206
|
+
case 'weekly':
|
|
207
|
+
date.setDate(date.getDate() + 7);
|
|
208
|
+
break;
|
|
209
|
+
case 'monthly':
|
|
210
|
+
date.setMonth(date.getMonth() + 1);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
return date.toISOString();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { SpendTracker } from './tracker.js';
|
|
2
|
+
export type { TransactionFilter } from './tracker.js';
|
|
3
|
+
export { SpendAnalytics } from './analytics.js';
|
|
4
|
+
export type { SpendSummary, TimeSeriesPoint, AgentAnalytics } from './analytics.js';
|
|
5
|
+
export { SpendAlerts } from './alerts.js';
|
|
6
|
+
export type { AlertRule, AlertRuleConfig, BudgetThresholdConfig, LargeTransactionConfig, RateSpikeConfig, NewRecipientConfig, AnomalyConfig, } from './alerts.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEpF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EACV,SAAS,EACT,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// @paysentry/observe — Public API
|
|
3
|
+
// Payment observability for AI agents
|
|
4
|
+
// =============================================================================
|
|
5
|
+
export { SpendTracker } from './tracker.js';
|
|
6
|
+
export { SpendAnalytics } from './analytics.js';
|
|
7
|
+
export { SpendAlerts } from './alerts.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,kCAAkC;AAClC,sCAAsC;AACtC,gFAAgF;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AgentTransaction, AgentId, TransactionId, TransactionStatus, ServiceId, PaymentProtocol, Logger } from '@paysentry/core';
|
|
2
|
+
/** Filter criteria for querying transactions */
|
|
3
|
+
export interface TransactionFilter {
|
|
4
|
+
/** Filter by agent ID */
|
|
5
|
+
readonly agentId?: AgentId;
|
|
6
|
+
/** Filter by recipient (exact match) */
|
|
7
|
+
readonly recipient?: string;
|
|
8
|
+
/** Filter by service */
|
|
9
|
+
readonly service?: ServiceId;
|
|
10
|
+
/** Filter by protocol */
|
|
11
|
+
readonly protocol?: PaymentProtocol;
|
|
12
|
+
/** Filter by status */
|
|
13
|
+
readonly status?: TransactionStatus;
|
|
14
|
+
/** Filter by currency */
|
|
15
|
+
readonly currency?: string;
|
|
16
|
+
/** Minimum amount (inclusive) */
|
|
17
|
+
readonly minAmount?: number;
|
|
18
|
+
/** Maximum amount (inclusive) */
|
|
19
|
+
readonly maxAmount?: number;
|
|
20
|
+
/** ISO 8601 — transactions created after this time */
|
|
21
|
+
readonly after?: string;
|
|
22
|
+
/** ISO 8601 — transactions created before this time */
|
|
23
|
+
readonly before?: string;
|
|
24
|
+
/** Maximum number of results */
|
|
25
|
+
readonly limit?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* SpendTracker records, stores, and queries agent transactions.
|
|
29
|
+
* It maintains in-memory indices for fast lookups by agent, service,
|
|
30
|
+
* and time range.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const tracker = new SpendTracker();
|
|
35
|
+
* tracker.record(transaction);
|
|
36
|
+
*
|
|
37
|
+
* const agentTxs = tracker.getByAgent('agent-1' as AgentId);
|
|
38
|
+
* const recentTxs = tracker.query({ after: '2026-01-01T00:00:00Z', limit: 50 });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class SpendTracker {
|
|
42
|
+
/** Primary storage: id -> transaction */
|
|
43
|
+
private readonly transactions;
|
|
44
|
+
/** Index: agentId -> set of transaction IDs */
|
|
45
|
+
private readonly byAgent;
|
|
46
|
+
/** Index: service -> set of transaction IDs */
|
|
47
|
+
private readonly byService;
|
|
48
|
+
/** Index: recipient -> set of transaction IDs */
|
|
49
|
+
private readonly byRecipient;
|
|
50
|
+
/** Chronologically ordered transaction IDs */
|
|
51
|
+
private readonly chronological;
|
|
52
|
+
private readonly logger?;
|
|
53
|
+
constructor(options?: {
|
|
54
|
+
logger?: Logger;
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Record a transaction. If a transaction with the same ID already exists,
|
|
58
|
+
* it will be updated (useful for status changes).
|
|
59
|
+
*/
|
|
60
|
+
record(tx: AgentTransaction): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get a single transaction by ID.
|
|
63
|
+
*/
|
|
64
|
+
get(id: TransactionId): AgentTransaction | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Get all transactions for a specific agent, newest first.
|
|
67
|
+
*/
|
|
68
|
+
getByAgent(agentId: AgentId): AgentTransaction[];
|
|
69
|
+
/**
|
|
70
|
+
* Get all transactions for a specific service, newest first.
|
|
71
|
+
*/
|
|
72
|
+
getByService(serviceId: ServiceId): AgentTransaction[];
|
|
73
|
+
/**
|
|
74
|
+
* Get all transactions for a specific recipient, newest first.
|
|
75
|
+
*/
|
|
76
|
+
getByRecipient(recipient: string): AgentTransaction[];
|
|
77
|
+
/**
|
|
78
|
+
* Query transactions with flexible filtering.
|
|
79
|
+
* Results are returned newest first.
|
|
80
|
+
*/
|
|
81
|
+
query(filter: TransactionFilter): AgentTransaction[];
|
|
82
|
+
/**
|
|
83
|
+
* Get the total number of recorded transactions.
|
|
84
|
+
*/
|
|
85
|
+
get size(): number;
|
|
86
|
+
/**
|
|
87
|
+
* Get all unique agent IDs that have transactions.
|
|
88
|
+
*/
|
|
89
|
+
get agents(): AgentId[];
|
|
90
|
+
/**
|
|
91
|
+
* Get all unique recipients that have received payments.
|
|
92
|
+
*/
|
|
93
|
+
get recipients(): string[];
|
|
94
|
+
private addToIndex;
|
|
95
|
+
private resolveIds;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gBAAgB,EAChB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,MAAM,EACP,MAAM,iBAAiB,CAAC;AAEzB,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAE3B,wCAAwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,wBAAwB;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAE7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAEpC,uBAAuB;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAEpC,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,gCAAgC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;IACvB,yCAAyC;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;IAEhF,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IAEtE,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8C;IAExE,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8C;IAE1E,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuB;IAErD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;gBAErB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAIzC;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI;IAyBlC;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS;IAIpD;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,EAAE;IAMhD;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,gBAAgB,EAAE;IAMtD;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAMrD;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,gBAAgB,EAAE;IAwCpD;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,EAAE,CAEtB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,EAAE,CAEzB;IAMD,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,UAAU;CAQnB"}
|
package/dist/tracker.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SpendTracker — Records and indexes all agent transactions
|
|
3
|
+
// The foundation of the Observe pillar. Every transaction goes through here.
|
|
4
|
+
// =============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* SpendTracker records, stores, and queries agent transactions.
|
|
7
|
+
* It maintains in-memory indices for fast lookups by agent, service,
|
|
8
|
+
* and time range.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const tracker = new SpendTracker();
|
|
13
|
+
* tracker.record(transaction);
|
|
14
|
+
*
|
|
15
|
+
* const agentTxs = tracker.getByAgent('agent-1' as AgentId);
|
|
16
|
+
* const recentTxs = tracker.query({ after: '2026-01-01T00:00:00Z', limit: 50 });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class SpendTracker {
|
|
20
|
+
/** Primary storage: id -> transaction */
|
|
21
|
+
transactions = new Map();
|
|
22
|
+
/** Index: agentId -> set of transaction IDs */
|
|
23
|
+
byAgent = new Map();
|
|
24
|
+
/** Index: service -> set of transaction IDs */
|
|
25
|
+
byService = new Map();
|
|
26
|
+
/** Index: recipient -> set of transaction IDs */
|
|
27
|
+
byRecipient = new Map();
|
|
28
|
+
/** Chronologically ordered transaction IDs */
|
|
29
|
+
chronological = [];
|
|
30
|
+
logger;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.logger = options?.logger;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Record a transaction. If a transaction with the same ID already exists,
|
|
36
|
+
* it will be updated (useful for status changes).
|
|
37
|
+
*/
|
|
38
|
+
record(tx) {
|
|
39
|
+
const isUpdate = this.transactions.has(tx.id);
|
|
40
|
+
this.transactions.set(tx.id, tx);
|
|
41
|
+
if (!isUpdate) {
|
|
42
|
+
// Index on first insert only
|
|
43
|
+
this.addToIndex(this.byAgent, tx.agentId, tx.id);
|
|
44
|
+
this.addToIndex(this.byRecipient, tx.recipient, tx.id);
|
|
45
|
+
if (tx.service) {
|
|
46
|
+
this.addToIndex(this.byService, tx.service, tx.id);
|
|
47
|
+
}
|
|
48
|
+
this.chronological.push(tx.id);
|
|
49
|
+
this.logger?.info(`[SpendTracker] Recorded transaction ${tx.id}`, {
|
|
50
|
+
agent: tx.agentId,
|
|
51
|
+
amount: tx.amount,
|
|
52
|
+
currency: tx.currency,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.logger?.info(`[SpendTracker] Updated transaction ${tx.id}`, {
|
|
57
|
+
status: tx.status,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get a single transaction by ID.
|
|
63
|
+
*/
|
|
64
|
+
get(id) {
|
|
65
|
+
return this.transactions.get(id);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all transactions for a specific agent, newest first.
|
|
69
|
+
*/
|
|
70
|
+
getByAgent(agentId) {
|
|
71
|
+
const ids = this.byAgent.get(agentId);
|
|
72
|
+
if (!ids)
|
|
73
|
+
return [];
|
|
74
|
+
return this.resolveIds(ids).reverse();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get all transactions for a specific service, newest first.
|
|
78
|
+
*/
|
|
79
|
+
getByService(serviceId) {
|
|
80
|
+
const ids = this.byService.get(serviceId);
|
|
81
|
+
if (!ids)
|
|
82
|
+
return [];
|
|
83
|
+
return this.resolveIds(ids).reverse();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all transactions for a specific recipient, newest first.
|
|
87
|
+
*/
|
|
88
|
+
getByRecipient(recipient) {
|
|
89
|
+
const ids = this.byRecipient.get(recipient);
|
|
90
|
+
if (!ids)
|
|
91
|
+
return [];
|
|
92
|
+
return this.resolveIds(ids).reverse();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Query transactions with flexible filtering.
|
|
96
|
+
* Results are returned newest first.
|
|
97
|
+
*/
|
|
98
|
+
query(filter) {
|
|
99
|
+
let results;
|
|
100
|
+
// Start with the most selective index
|
|
101
|
+
if (filter.agentId) {
|
|
102
|
+
results = this.getByAgent(filter.agentId);
|
|
103
|
+
}
|
|
104
|
+
else if (filter.service) {
|
|
105
|
+
results = this.getByService(filter.service);
|
|
106
|
+
}
|
|
107
|
+
else if (filter.recipient) {
|
|
108
|
+
results = this.getByRecipient(filter.recipient);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Full scan in reverse chronological order
|
|
112
|
+
results = [...this.chronological]
|
|
113
|
+
.reverse()
|
|
114
|
+
.map((id) => this.transactions.get(id))
|
|
115
|
+
.filter((tx) => tx !== undefined);
|
|
116
|
+
}
|
|
117
|
+
// Apply additional filters
|
|
118
|
+
results = results.filter((tx) => {
|
|
119
|
+
if (filter.agentId && tx.agentId !== filter.agentId)
|
|
120
|
+
return false;
|
|
121
|
+
if (filter.recipient && tx.recipient !== filter.recipient)
|
|
122
|
+
return false;
|
|
123
|
+
if (filter.service && tx.service !== filter.service)
|
|
124
|
+
return false;
|
|
125
|
+
if (filter.protocol && tx.protocol !== filter.protocol)
|
|
126
|
+
return false;
|
|
127
|
+
if (filter.status && tx.status !== filter.status)
|
|
128
|
+
return false;
|
|
129
|
+
if (filter.currency && tx.currency !== filter.currency)
|
|
130
|
+
return false;
|
|
131
|
+
if (filter.minAmount !== undefined && tx.amount < filter.minAmount)
|
|
132
|
+
return false;
|
|
133
|
+
if (filter.maxAmount !== undefined && tx.amount > filter.maxAmount)
|
|
134
|
+
return false;
|
|
135
|
+
if (filter.after && tx.createdAt < filter.after)
|
|
136
|
+
return false;
|
|
137
|
+
if (filter.before && tx.createdAt > filter.before)
|
|
138
|
+
return false;
|
|
139
|
+
return true;
|
|
140
|
+
});
|
|
141
|
+
if (filter.limit && results.length > filter.limit) {
|
|
142
|
+
results = results.slice(0, filter.limit);
|
|
143
|
+
}
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get the total number of recorded transactions.
|
|
148
|
+
*/
|
|
149
|
+
get size() {
|
|
150
|
+
return this.transactions.size;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get all unique agent IDs that have transactions.
|
|
154
|
+
*/
|
|
155
|
+
get agents() {
|
|
156
|
+
return [...this.byAgent.keys()];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get all unique recipients that have received payments.
|
|
160
|
+
*/
|
|
161
|
+
get recipients() {
|
|
162
|
+
return [...this.byRecipient.keys()];
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Private helpers
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
addToIndex(index, key, txId) {
|
|
168
|
+
let set = index.get(key);
|
|
169
|
+
if (!set) {
|
|
170
|
+
set = new Set();
|
|
171
|
+
index.set(key, set);
|
|
172
|
+
}
|
|
173
|
+
set.add(txId);
|
|
174
|
+
}
|
|
175
|
+
resolveIds(ids) {
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const id of ids) {
|
|
178
|
+
const tx = this.transactions.get(id);
|
|
179
|
+
if (tx)
|
|
180
|
+
results.push(tx);
|
|
181
|
+
}
|
|
182
|
+
return results;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@paysentry/observe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Payment observability for AI agents — track what agents spend, where, and why",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"clean": "tsc --build --clean"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@paysentry/core": "1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"files": ["dist"],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/paysentry/paysentry",
|
|
26
|
+
"directory": "packages/observe"
|
|
27
|
+
}
|
|
28
|
+
}
|