@meyicloud/meyi-insight-server 1.1.1 → 1.1.3

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 (61) hide show
  1. package/dist/features/ai-usage/ai-usage.controller.d.ts +7 -0
  2. package/dist/features/ai-usage/ai-usage.controller.d.ts.map +1 -0
  3. package/dist/features/ai-usage/ai-usage.controller.js +32 -0
  4. package/dist/features/ai-usage/ai-usage.controller.js.map +1 -0
  5. package/dist/features/ai-usage/ai-usage.model.d.ts +24 -0
  6. package/dist/features/ai-usage/ai-usage.model.d.ts.map +1 -0
  7. package/dist/features/ai-usage/ai-usage.model.js +105 -0
  8. package/dist/features/ai-usage/ai-usage.model.js.map +1 -0
  9. package/dist/features/ai-usage/ai-usage.query.d.ts +13 -0
  10. package/dist/features/ai-usage/ai-usage.query.d.ts.map +1 -0
  11. package/dist/features/ai-usage/ai-usage.query.js +119 -0
  12. package/dist/features/ai-usage/ai-usage.query.js.map +1 -0
  13. package/dist/features/ai-usage/ai-usage.routes.d.ts +8 -0
  14. package/dist/features/ai-usage/ai-usage.routes.d.ts.map +1 -0
  15. package/dist/features/ai-usage/ai-usage.routes.js +14 -0
  16. package/dist/features/ai-usage/ai-usage.routes.js.map +1 -0
  17. package/dist/features/ai-usage/ai-usage.service.d.ts +9 -0
  18. package/dist/features/ai-usage/ai-usage.service.d.ts.map +1 -0
  19. package/dist/features/ai-usage/ai-usage.service.js +52 -0
  20. package/dist/features/ai-usage/ai-usage.service.js.map +1 -0
  21. package/dist/features/ai-usage/ai-usage.types.d.ts +107 -0
  22. package/dist/features/ai-usage/ai-usage.types.d.ts.map +1 -0
  23. package/dist/features/ai-usage/ai-usage.types.js +14 -0
  24. package/dist/features/ai-usage/ai-usage.types.js.map +1 -0
  25. package/dist/features/ai-usage/index.d.ts +5 -0
  26. package/dist/features/ai-usage/index.d.ts.map +1 -0
  27. package/dist/features/ai-usage/index.js +5 -0
  28. package/dist/features/ai-usage/index.js.map +1 -0
  29. package/dist/features/sources/cloudtrail/stackTemplates.d.ts +1 -1
  30. package/dist/features/sources/cloudtrail/stackTemplates.d.ts.map +1 -1
  31. package/dist/features/sources/cloudtrail/stackTemplates.js +17 -18
  32. package/dist/features/sources/cloudtrail/stackTemplates.js.map +1 -1
  33. package/dist/features/sources/cloudtrail/template.d.ts.map +1 -1
  34. package/dist/features/sources/cloudtrail/template.js +146 -21
  35. package/dist/features/sources/cloudtrail/template.js.map +1 -1
  36. package/dist/migrations/run.d.ts.map +1 -1
  37. package/dist/migrations/run.js +7 -1
  38. package/dist/migrations/run.js.map +1 -1
  39. package/dist/pipeline/NormalizationRuleEngine.js +4 -0
  40. package/dist/pipeline/NormalizationRuleEngine.js.map +1 -1
  41. package/dist/pipeline/NormalizationService.d.ts.map +1 -1
  42. package/dist/pipeline/NormalizationService.js +1 -0
  43. package/dist/pipeline/NormalizationService.js.map +1 -1
  44. package/dist/pipeline/defaults/pipelineDefaults.d.ts.map +1 -1
  45. package/dist/pipeline/defaults/pipelineDefaults.js +45 -9
  46. package/dist/pipeline/defaults/pipelineDefaults.js.map +1 -1
  47. package/dist/routes/index.d.ts +2 -0
  48. package/dist/routes/index.d.ts.map +1 -1
  49. package/dist/routes/index.js +6 -1
  50. package/dist/routes/index.js.map +1 -1
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +1 -0
  53. package/dist/server.js.map +1 -1
  54. package/dist/service.d.ts +2 -0
  55. package/dist/service.d.ts.map +1 -1
  56. package/dist/service.js +4 -0
  57. package/dist/service.js.map +1 -1
  58. package/dist/services/alert.service.d.ts.map +1 -1
  59. package/dist/services/alert.service.js +51 -31
  60. package/dist/services/alert.service.js.map +1 -1
  61. package/package.json +75 -75
@@ -0,0 +1,7 @@
1
+ import type { Request, Response } from "express";
2
+ import type { AiUsageService } from "./ai-usage.service.js";
3
+ export declare function createAiUsageController(service: AiUsageService): {
4
+ summary(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
5
+ list(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
6
+ };
7
+ //# sourceMappingURL=ai-usage.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.controller.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAU5D,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc;iBAExC,OAAO,OAAO,QAAQ;cAQzB,OAAO,OAAO,QAAQ;EASzC"}
@@ -0,0 +1,32 @@
1
+ import { resolveTenantId } from "../../tenancy.js";
2
+ import { parseUsageFilters } from "./ai-usage.query.js";
3
+ import { AiUsageError } from "./ai-usage.types.js";
4
+ function fail(res, e) {
5
+ if (e instanceof AiUsageError) {
6
+ return res.status(e.status).json({ ok: false, error: e.message });
7
+ }
8
+ return res.status(500).json({ ok: false, error: e?.message ?? "Internal error" });
9
+ }
10
+ export function createAiUsageController(service) {
11
+ return {
12
+ async summary(req, res) {
13
+ try {
14
+ const filters = parseUsageFilters(req.query);
15
+ return res.json({ ok: true, ...(await service.summary(resolveTenantId(req), filters)) });
16
+ }
17
+ catch (e) {
18
+ return fail(res, e);
19
+ }
20
+ },
21
+ async list(req, res) {
22
+ try {
23
+ const filters = parseUsageFilters(req.query);
24
+ return res.json({ ok: true, ...(await service.list(resolveTenantId(req), filters)) });
25
+ }
26
+ catch (e) {
27
+ return fail(res, e);
28
+ }
29
+ },
30
+ };
31
+ }
32
+ //# sourceMappingURL=ai-usage.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.controller.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,SAAS,IAAI,CAAC,GAAa,EAAE,CAAM;IACjC,IAAI,CAAC,YAAY,YAAY,EAAE,CAAC;QAC9B,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAuB;IAC7D,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,GAAY,EAAE,GAAa;YACvC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAgC,CAAC,CAAC;gBACxE,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3F,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAY,EAAE,GAAa;YACpC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAgC,CAAC,CAAC;gBACxE,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { UsageFilters } from "./ai-usage.types.js";
2
+ /**
3
+ * Read-only access to meyiconnect.ai_usage_log. Every query is scoped by
4
+ * tenant_id and the filter window; there are no business rules here.
5
+ */
6
+ export declare class AiUsageModel {
7
+ private db;
8
+ constructor(db: any);
9
+ private where;
10
+ totals(tenantId: string, f: UsageFilters): Promise<Record<string, unknown>>;
11
+ /** Per UTC day; days with no runs are absent (the service fills them). */
12
+ daily(tenantId: string, f: UsageFilters): Promise<Record<string, unknown>[]>;
13
+ byCallType(tenantId: string, f: UsageFilters): Promise<Record<string, unknown>[]>;
14
+ /**
15
+ * Per model, from each row's per-model lines. A line without "provider" is
16
+ * Bedrock (insight-ai only tags non-Bedrock lines). Cost sums priced lines.
17
+ */
18
+ byModel(tenantId: string, f: UsageFilters, limit?: number): Promise<Record<string, unknown>[]>;
19
+ list(tenantId: string, f: UsageFilters): Promise<{
20
+ rows: Record<string, unknown>[];
21
+ total: number;
22
+ }>;
23
+ }
24
+ //# sourceMappingURL=ai-usage.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.model.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAgBxD;;;GAGG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,GAAG;IAE3B,OAAO,CAAC,KAAK;IAOP,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAcjF,0EAA0E;IACpE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAa5E,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAavF;;;OAGG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAkB1F,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAiB3G"}
@@ -0,0 +1,105 @@
1
+ import { sql } from "drizzle-orm";
2
+ // Created by src/migrations/run.ts (and, identically, insight-cost-server's
3
+ // schema installer); written by insight-ai and insight-cost-server only.
4
+ const TABLE = sql.raw(`"meyiconnect"."ai_usage_log"`);
5
+ // A row's `models` is a JSON array by both writers' contract; anything else is
6
+ // treated as empty rather than letting jsonb_array_elements() fail the query.
7
+ const MODEL_LINES = sql.raw(`jsonb_array_elements(CASE WHEN jsonb_typeof(u.models) = 'array' THEN u.models ELSE '[]'::jsonb END)`);
8
+ function rowsOf(res) {
9
+ return res?.rows ?? (Array.isArray(res) ? res : []);
10
+ }
11
+ /**
12
+ * Read-only access to meyiconnect.ai_usage_log. Every query is scoped by
13
+ * tenant_id and the filter window; there are no business rules here.
14
+ */
15
+ export class AiUsageModel {
16
+ db;
17
+ constructor(db) {
18
+ this.db = db;
19
+ }
20
+ where(tenantId, f, opts = {}) {
21
+ const parts = [sql `u.tenant_id = ${tenantId}`, sql `u.created_at >= ${f.since}`];
22
+ if (f.callType)
23
+ parts.push(sql `u.call_type = ${f.callType}`);
24
+ if (opts.priced !== undefined)
25
+ parts.push(sql `u.priced = ${opts.priced}`);
26
+ return sql.join(parts, sql ` AND `);
27
+ }
28
+ async totals(tenantId, f) {
29
+ const res = await this.db.execute(sql `
30
+ SELECT count(*)::int AS runs,
31
+ count(*) FILTER (WHERE u.priced)::int AS priced_runs,
32
+ coalesce(sum(u.total_input_tokens), 0)::bigint AS input_tokens,
33
+ coalesce(sum(u.total_output_tokens), 0)::bigint AS output_tokens,
34
+ coalesce(sum(u.total_tokens), 0)::bigint AS total_tokens,
35
+ coalesce(sum(u.total_cost) FILTER (WHERE u.priced), 0) AS total_cost
36
+ FROM ${TABLE} u
37
+ WHERE ${this.where(tenantId, f)}
38
+ `);
39
+ return rowsOf(res)[0] ?? {};
40
+ }
41
+ /** Per UTC day; days with no runs are absent (the service fills them). */
42
+ async daily(tenantId, f) {
43
+ return rowsOf(await this.db.execute(sql `
44
+ SELECT to_char(u.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD') AS date,
45
+ count(*)::int AS runs,
46
+ coalesce(sum(u.total_tokens), 0)::bigint AS total_tokens,
47
+ coalesce(sum(u.total_cost) FILTER (WHERE u.priced), 0) AS total_cost
48
+ FROM ${TABLE} u
49
+ WHERE ${this.where(tenantId, f)}
50
+ GROUP BY 1
51
+ ORDER BY 1
52
+ `));
53
+ }
54
+ async byCallType(tenantId, f) {
55
+ return rowsOf(await this.db.execute(sql `
56
+ SELECT u.call_type,
57
+ count(*)::int AS runs,
58
+ coalesce(sum(u.total_tokens), 0)::bigint AS total_tokens,
59
+ coalesce(sum(u.total_cost) FILTER (WHERE u.priced), 0) AS total_cost
60
+ FROM ${TABLE} u
61
+ WHERE ${this.where(tenantId, f)}
62
+ GROUP BY u.call_type
63
+ ORDER BY total_tokens DESC
64
+ `));
65
+ }
66
+ /**
67
+ * Per model, from each row's per-model lines. A line without "provider" is
68
+ * Bedrock (insight-ai only tags non-Bedrock lines). Cost sums priced lines.
69
+ */
70
+ async byModel(tenantId, f, limit = 10) {
71
+ return rowsOf(await this.db.execute(sql `
72
+ SELECT coalesce(nullif(m->>'model_id', ''), 'unknown') AS model,
73
+ coalesce(nullif(m->>'provider', ''), 'bedrock') AS provider,
74
+ coalesce(sum((m->>'calls')::numeric), 0)::bigint AS calls,
75
+ coalesce(sum((m->>'input_tokens')::numeric), 0)::bigint AS input_tokens,
76
+ coalesce(sum((m->>'output_tokens')::numeric), 0)::bigint AS output_tokens,
77
+ coalesce(sum((m->>'total_cost')::numeric) FILTER (WHERE (m->>'priced')::boolean), 0) AS total_cost,
78
+ bool_or(coalesce((m->>'priced')::boolean, false)) AS priced
79
+ FROM ${TABLE} u, ${MODEL_LINES} AS m
80
+ WHERE ${this.where(tenantId, f)}
81
+ GROUP BY 1, 2
82
+ ORDER BY coalesce(sum((m->>'input_tokens')::numeric), 0)
83
+ + coalesce(sum((m->>'output_tokens')::numeric), 0) DESC
84
+ LIMIT ${limit}
85
+ `));
86
+ }
87
+ async list(tenantId, f) {
88
+ const where = this.where(tenantId, f, { priced: f.priced });
89
+ const [page, count] = await Promise.all([
90
+ this.db.execute(sql `
91
+ SELECT u.id, u.call_type, u.incident_id, u.incident_name, u.sop_id, u.report_id, u.models,
92
+ u.total_input_tokens, u.total_output_tokens, u.total_cache_read_tokens,
93
+ u.total_cache_write_tokens, u.total_tokens, u.total_cost, u.currency,
94
+ u.priced, u.reason, u.created_at
95
+ FROM ${TABLE} u
96
+ WHERE ${where}
97
+ ORDER BY u.created_at DESC
98
+ LIMIT ${f.limit} OFFSET ${(f.page - 1) * f.limit}
99
+ `),
100
+ this.db.execute(sql `SELECT count(*)::int AS total FROM ${TABLE} u WHERE ${where}`),
101
+ ]);
102
+ return { rows: rowsOf(page), total: Number(rowsOf(count)[0]?.total ?? 0) };
103
+ }
104
+ }
105
+ //# sourceMappingURL=ai-usage.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.model.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type UsageDay, type UsageFilters, type UsageRecord } from "./ai-usage.types.js";
2
+ /** Midnight UTC, (days - 1) days before *now* — so "7d" is today plus the six days before it. */
3
+ export declare function windowStart(days: number, now?: Date): Date;
4
+ /** Validates the query string; unknown values fall back to defaults, malformed filters are rejected. */
5
+ export declare function parseUsageFilters(query: Record<string, unknown>, now?: Date): UsageFilters;
6
+ /** pg returns bigint/numeric as strings; nulls stay null only where the caller asks. */
7
+ export declare function num(value: unknown): number;
8
+ export declare function cost(value: unknown): number | null;
9
+ /** One entry per day of the window, oldest first, with zeroes where nothing ran. */
10
+ export declare function fillDailySeries(rows: Array<Record<string, unknown>>, since: Date, days: number): UsageDay[];
11
+ /** A raw ai_usage_log row → the API shape. The busiest model names the row. */
12
+ export declare function toUsageRecord(row: Record<string, any>): UsageRecord;
13
+ //# sourceMappingURL=ai-usage.query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.query.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.query.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,YAAY,EAGjB,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAmB7B,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,IAAI,CAIhE;AAED,wGAAwG;AACxG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,OAAa,GAAG,YAAY,CAyBhG;AAED,wFAAwF;AACxF,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG1C;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIlD;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgB3G;AAUD,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,CA6BnE"}
@@ -0,0 +1,119 @@
1
+ import { AiUsageError, USAGE_RANGES, } from "./ai-usage.types.js";
2
+ const DEFAULT_RANGE = "30d";
3
+ const DEFAULT_LIMIT = 25;
4
+ const MAX_LIMIT = 100;
5
+ // call_type is free text written by other services; accept anything shaped
6
+ // like one (rca, sop, cost_report, …) rather than a list that goes stale.
7
+ const CALL_TYPE = /^[a-z][a-z0-9_]{0,31}$/;
8
+ function first(value) {
9
+ if (Array.isArray(value))
10
+ return first(value[0]);
11
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
12
+ }
13
+ function positiveInt(value, fallback) {
14
+ const n = Number(first(value));
15
+ return Number.isInteger(n) && n > 0 ? n : fallback;
16
+ }
17
+ /** Midnight UTC, (days - 1) days before *now* — so "7d" is today plus the six days before it. */
18
+ export function windowStart(days, now = new Date()) {
19
+ const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
20
+ start.setUTCDate(start.getUTCDate() - (days - 1));
21
+ return start;
22
+ }
23
+ /** Validates the query string; unknown values fall back to defaults, malformed filters are rejected. */
24
+ export function parseUsageFilters(query, now = new Date()) {
25
+ const rangeRaw = first(query.range);
26
+ if (rangeRaw && !(rangeRaw in USAGE_RANGES)) {
27
+ throw new AiUsageError(`range must be one of ${Object.keys(USAGE_RANGES).join(", ")}`);
28
+ }
29
+ const range = rangeRaw || DEFAULT_RANGE;
30
+ const days = USAGE_RANGES[range];
31
+ const callType = first(query.callType);
32
+ if (callType && !CALL_TYPE.test(callType))
33
+ throw new AiUsageError("callType is invalid");
34
+ const pricedRaw = first(query.priced);
35
+ if (pricedRaw && pricedRaw !== "true" && pricedRaw !== "false") {
36
+ throw new AiUsageError("priced must be true or false");
37
+ }
38
+ return {
39
+ range,
40
+ days,
41
+ since: windowStart(days, now),
42
+ callType,
43
+ priced: pricedRaw === undefined ? undefined : pricedRaw === "true",
44
+ page: positiveInt(query.page, 1),
45
+ limit: Math.min(positiveInt(query.limit, DEFAULT_LIMIT), MAX_LIMIT),
46
+ };
47
+ }
48
+ /** pg returns bigint/numeric as strings; nulls stay null only where the caller asks. */
49
+ export function num(value) {
50
+ const n = Number(value);
51
+ return Number.isFinite(n) ? n : 0;
52
+ }
53
+ export function cost(value) {
54
+ if (value === null || value === undefined || value === "")
55
+ return null;
56
+ const n = Number(value);
57
+ return Number.isFinite(n) ? Math.round(n * 1e6) / 1e6 : null;
58
+ }
59
+ /** One entry per day of the window, oldest first, with zeroes where nothing ran. */
60
+ export function fillDailySeries(rows, since, days) {
61
+ const byDate = new Map(rows.map((r) => [String(r.date), r]));
62
+ const out = [];
63
+ for (let i = 0; i < days; i++) {
64
+ const d = new Date(since);
65
+ d.setUTCDate(d.getUTCDate() + i);
66
+ const date = d.toISOString().slice(0, 10);
67
+ const row = byDate.get(date);
68
+ out.push({
69
+ date,
70
+ runs: num(row?.runs),
71
+ totalTokens: num(row?.total_tokens),
72
+ totalCost: cost(row?.total_cost) ?? 0,
73
+ });
74
+ }
75
+ return out;
76
+ }
77
+ function modelLines(models) {
78
+ if (Array.isArray(models))
79
+ return models.filter((m) => m && typeof m === "object");
80
+ if (typeof models === "string") {
81
+ try {
82
+ return modelLines(JSON.parse(models));
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ return [];
89
+ }
90
+ /** A raw ai_usage_log row → the API shape. The busiest model names the row. */
91
+ export function toUsageRecord(row) {
92
+ const lines = modelLines(row.models);
93
+ const busiest = [...lines].sort((a, b) => num(b.input_tokens) + num(b.output_tokens) - (num(a.input_tokens) + num(a.output_tokens)))[0];
94
+ const priced = Boolean(row.priced);
95
+ return {
96
+ id: String(row.id),
97
+ callType: String(row.call_type),
98
+ incidentId: row.incident_id ?? null,
99
+ incidentName: row.incident_name ?? null,
100
+ sopId: row.sop_id ?? null,
101
+ reportId: row.report_id ?? null,
102
+ model: busiest?.model_id || null,
103
+ // Only non-Bedrock lines carry "provider" from insight-ai; an absent one is Bedrock.
104
+ provider: busiest ? busiest.provider || "bedrock" : null,
105
+ modelCount: lines.length,
106
+ calls: lines.reduce((sum, m) => sum + num(m.calls), 0),
107
+ inputTokens: num(row.total_input_tokens),
108
+ outputTokens: num(row.total_output_tokens),
109
+ cacheReadTokens: num(row.total_cache_read_tokens),
110
+ cacheWriteTokens: num(row.total_cache_write_tokens),
111
+ totalTokens: num(row.total_tokens),
112
+ totalCost: priced ? cost(row.total_cost) : null,
113
+ currency: row.currency || "USD",
114
+ priced,
115
+ reason: row.reason ?? null,
116
+ createdAt: new Date(row.created_at).toISOString(),
117
+ };
118
+ }
119
+ //# sourceMappingURL=ai-usage.query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.query.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Router } from "express";
2
+ import type { AiUsageService } from "./ai-usage.service.js";
3
+ /**
4
+ * Read-only. Query: range=7d|30d|90d (default 30d), callType, and for the list
5
+ * priced=true|false, page, limit (max 100). Admin-only — gated in routes/index.ts.
6
+ */
7
+ export declare function createAiUsageRouter(service: AiUsageService): Router;
8
+ //# sourceMappingURL=ai-usage.routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.routes.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAQnE"}
@@ -0,0 +1,14 @@
1
+ import { Router } from "express";
2
+ import { createAiUsageController } from "./ai-usage.controller.js";
3
+ /**
4
+ * Read-only. Query: range=7d|30d|90d (default 30d), callType, and for the list
5
+ * priced=true|false, page, limit (max 100). Admin-only — gated in routes/index.ts.
6
+ */
7
+ export function createAiUsageRouter(service) {
8
+ const router = Router();
9
+ const ctrl = createAiUsageController(service);
10
+ router.get("/summary", ctrl.summary.bind(ctrl));
11
+ router.get("/", ctrl.list.bind(ctrl));
12
+ return router;
13
+ }
14
+ //# sourceMappingURL=ai-usage.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.routes.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAGnE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB;IACzD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAE9C,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEtC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { AiUsageModel } from "./ai-usage.model.js";
2
+ import type { UsageFilters, UsageList, UsageSummary } from "./ai-usage.types.js";
3
+ export declare class AiUsageService {
4
+ private model;
5
+ constructor(model: AiUsageModel);
6
+ summary(tenantId: string, f: UsageFilters): Promise<UsageSummary>;
7
+ list(tenantId: string, f: UsageFilters): Promise<UsageList>;
8
+ }
9
+ //# sourceMappingURL=ai-usage.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.service.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjF,qBAAa,cAAc;IACb,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,YAAY;IAEjC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAyCjE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;CAIlE"}
@@ -0,0 +1,52 @@
1
+ import { cost, fillDailySeries, num, toUsageRecord } from "./ai-usage.query.js";
2
+ export class AiUsageService {
3
+ model;
4
+ constructor(model) {
5
+ this.model = model;
6
+ }
7
+ async summary(tenantId, f) {
8
+ const [totals, daily, byCallType, byModel] = await Promise.all([
9
+ this.model.totals(tenantId, f),
10
+ this.model.daily(tenantId, f),
11
+ this.model.byCallType(tenantId, f),
12
+ this.model.byModel(tenantId, f),
13
+ ]);
14
+ const runs = num(totals.runs);
15
+ const pricedRuns = num(totals.priced_runs);
16
+ return {
17
+ range: f.range,
18
+ since: f.since.toISOString(),
19
+ currency: "USD",
20
+ totals: {
21
+ runs,
22
+ pricedRuns,
23
+ unpricedRuns: runs - pricedRuns,
24
+ inputTokens: num(totals.input_tokens),
25
+ outputTokens: num(totals.output_tokens),
26
+ totalTokens: num(totals.total_tokens),
27
+ totalCost: cost(totals.total_cost) ?? 0,
28
+ },
29
+ daily: fillDailySeries(daily, f.since, f.days),
30
+ byCallType: byCallType.map((r) => ({
31
+ callType: String(r.call_type),
32
+ runs: num(r.runs),
33
+ totalTokens: num(r.total_tokens),
34
+ totalCost: cost(r.total_cost) ?? 0,
35
+ })),
36
+ byModel: byModel.map((r) => ({
37
+ model: String(r.model),
38
+ provider: String(r.provider),
39
+ calls: num(r.calls),
40
+ inputTokens: num(r.input_tokens),
41
+ outputTokens: num(r.output_tokens),
42
+ totalCost: cost(r.total_cost) ?? 0,
43
+ priced: Boolean(r.priced),
44
+ })),
45
+ };
46
+ }
47
+ async list(tenantId, f) {
48
+ const { rows, total } = await this.model.list(tenantId, f);
49
+ return { items: rows.map(toUsageRecord), total, page: f.page, limit: f.limit };
50
+ }
51
+ }
52
+ //# sourceMappingURL=ai-usage.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.service.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * AI usage — one row of meyiconnect.ai_usage_log per AI run, written by
3
+ * insight-ai (call_type 'rca' | 'sop') and insight-cost-server ('cost_report').
4
+ * This feature only reads it.
5
+ */
6
+ export declare const USAGE_RANGES: {
7
+ readonly "7d": 7;
8
+ readonly "30d": 30;
9
+ readonly "90d": 90;
10
+ };
11
+ export type UsageRange = keyof typeof USAGE_RANGES;
12
+ export interface UsageFilters {
13
+ range: UsageRange;
14
+ /** Inclusive UTC start of the window: midnight, (days - 1) days before today. */
15
+ since: Date;
16
+ days: number;
17
+ callType?: string;
18
+ priced?: boolean;
19
+ page: number;
20
+ limit: number;
21
+ }
22
+ /** One per-model line from the row's `models` JSONB (both writers use this snake_case shape). */
23
+ export interface UsageModelLine {
24
+ model_id?: string;
25
+ provider?: string;
26
+ region?: string;
27
+ input_tokens?: number;
28
+ output_tokens?: number;
29
+ calls?: number;
30
+ priced?: boolean;
31
+ total_cost?: number | null;
32
+ reason?: string;
33
+ }
34
+ export interface UsageRecord {
35
+ id: string;
36
+ callType: string;
37
+ incidentId: string | null;
38
+ incidentName: string | null;
39
+ sopId: string | null;
40
+ reportId: string | null;
41
+ model: string | null;
42
+ provider: string | null;
43
+ modelCount: number;
44
+ calls: number;
45
+ inputTokens: number;
46
+ outputTokens: number;
47
+ cacheReadTokens: number;
48
+ cacheWriteTokens: number;
49
+ totalTokens: number;
50
+ /** null when the run could not be priced — never a fabricated 0. */
51
+ totalCost: number | null;
52
+ currency: string;
53
+ priced: boolean;
54
+ reason: string | null;
55
+ createdAt: string;
56
+ }
57
+ export interface UsageTotals {
58
+ runs: number;
59
+ pricedRuns: number;
60
+ unpricedRuns: number;
61
+ inputTokens: number;
62
+ outputTokens: number;
63
+ totalTokens: number;
64
+ /** Sum over priced runs only; unpricedRuns says how much is missing from it. */
65
+ totalCost: number;
66
+ }
67
+ export interface UsageDay {
68
+ date: string;
69
+ runs: number;
70
+ totalTokens: number;
71
+ totalCost: number;
72
+ }
73
+ export interface UsageByCallType {
74
+ callType: string;
75
+ runs: number;
76
+ totalTokens: number;
77
+ totalCost: number;
78
+ }
79
+ export interface UsageByModel {
80
+ model: string;
81
+ provider: string;
82
+ calls: number;
83
+ inputTokens: number;
84
+ outputTokens: number;
85
+ totalCost: number;
86
+ priced: boolean;
87
+ }
88
+ export interface UsageSummary {
89
+ range: UsageRange;
90
+ since: string;
91
+ currency: "USD";
92
+ totals: UsageTotals;
93
+ daily: UsageDay[];
94
+ byCallType: UsageByCallType[];
95
+ byModel: UsageByModel[];
96
+ }
97
+ export interface UsageList {
98
+ items: UsageRecord[];
99
+ total: number;
100
+ page: number;
101
+ limit: number;
102
+ }
103
+ export declare class AiUsageError extends Error {
104
+ status: number;
105
+ constructor(message: string, status?: number);
106
+ }
107
+ //# sourceMappingURL=ai-usage.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.types.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * AI usage — one row of meyiconnect.ai_usage_log per AI run, written by
3
+ * insight-ai (call_type 'rca' | 'sop') and insight-cost-server ('cost_report').
4
+ * This feature only reads it.
5
+ */
6
+ export const USAGE_RANGES = { "7d": 7, "30d": 30, "90d": 90 };
7
+ export class AiUsageError extends Error {
8
+ status;
9
+ constructor(message, status = 400) {
10
+ super(message);
11
+ this.status = status;
12
+ }
13
+ }
14
+ //# sourceMappingURL=ai-usage.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-usage.types.js","sourceRoot":"","sources":["../../../src/features/ai-usage/ai-usage.types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAW,CAAC;AA2FvE,MAAM,OAAO,YAAa,SAAQ,KAAK;IACD;IAApC,YAAY,OAAe,EAAS,SAAS,GAAG;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,WAAM,GAAN,MAAM,CAAM;IAEhD,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export * from "./ai-usage.types.js";
2
+ export { AiUsageModel } from "./ai-usage.model.js";
3
+ export { AiUsageService } from "./ai-usage.service.js";
4
+ export { createAiUsageRouter } from "./ai-usage.routes.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/features/ai-usage/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./ai-usage.types.js";
2
+ export { AiUsageModel } from "./ai-usage.model.js";
3
+ export { AiUsageService } from "./ai-usage.service.js";
4
+ export { createAiUsageRouter } from "./ai-usage.routes.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/features/ai-usage/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}