@qcobro/common 1.12.4 → 1.14.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/billing/evaluate.d.ts +36 -0
- package/dist/billing/evaluate.d.ts.map +1 -0
- package/dist/billing/evaluate.js +136 -0
- package/dist/billing/evaluate.js.map +1 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/ledger.d.ts +70 -0
- package/dist/billing/ledger.d.ts.map +1 -0
- package/dist/billing/ledger.js +59 -0
- package/dist/billing/ledger.js.map +1 -0
- package/dist/billing/money.d.ts +26 -0
- package/dist/billing/money.d.ts.map +1 -0
- package/dist/billing/money.js +58 -0
- package/dist/billing/money.js.map +1 -0
- package/dist/billing/pricing.d.ts +58 -0
- package/dist/billing/pricing.d.ts.map +1 -0
- package/dist/billing/pricing.js +68 -0
- package/dist/billing/pricing.js.map +1 -0
- package/dist/billing/proration.d.ts +8 -0
- package/dist/billing/proration.d.ts.map +1 -0
- package/dist/billing/proration.js +17 -0
- package/dist/billing/proration.js.map +1 -0
- package/dist/billing/rates.d.ts +108 -0
- package/dist/billing/rates.d.ts.map +1 -0
- package/dist/billing/rates.js +56 -0
- package/dist/billing/rates.js.map +1 -0
- package/dist/bin/engineEval.d.ts +67 -0
- package/dist/bin/engineEval.d.ts.map +1 -0
- package/dist/bin/engineEval.js +290 -0
- package/dist/bin/engineEval.js.map +1 -0
- package/dist/config.d.ts +141 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -2
- package/dist/config.js.map +1 -1
- package/dist/evaluation/evaluate.d.ts +4 -0
- package/dist/evaluation/evaluate.d.ts.map +1 -0
- package/dist/evaluation/evaluate.js +504 -0
- package/dist/evaluation/evaluate.js.map +1 -0
- package/dist/evaluation/index.d.ts +3 -0
- package/dist/evaluation/index.d.ts.map +1 -0
- package/dist/evaluation/index.js +3 -0
- package/dist/evaluation/index.js.map +1 -0
- package/dist/evaluation/scorecard.d.ts +84 -0
- package/dist/evaluation/scorecard.d.ts.map +1 -0
- package/dist/evaluation/scorecard.js +29 -0
- package/dist/evaluation/scorecard.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/billing.d.ts +74 -0
- package/dist/schemas/billing.d.ts.map +1 -0
- package/dist/schemas/billing.js +68 -0
- package/dist/schemas/billing.js.map +1 -0
- package/dist/schemas/engineEvents.d.ts +407 -0
- package/dist/schemas/engineEvents.d.ts.map +1 -0
- package/dist/schemas/engineEvents.js +179 -0
- package/dist/schemas/engineEvents.js.map +1 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/types/billing.d.ts +134 -0
- package/dist/types/billing.d.ts.map +1 -0
- package/dist/types/billing.js +8 -0
- package/dist/types/billing.js.map +1 -0
- package/dist/types/engine.d.ts +13 -2
- package/dist/types/engine.d.ts.map +1 -1
- package/dist/types/engine.js +12 -1
- package/dist/types/engine.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/pacing.d.ts +17 -0
- package/dist/utils/pacing.d.ts.map +1 -0
- package/dist/utils/pacing.js +18 -0
- package/dist/utils/pacing.js.map +1 -0
- package/dist/utils/time.d.ts +16 -0
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +17 -0
- package/dist/utils/time.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The billable-meter catalog and per-meter rate schemas.
|
|
4
|
+
*
|
|
5
|
+
* The catalog is closed-world: exactly seven meters, one per dispatchable
|
|
6
|
+
* channel/mode. Message meters bill per message; voice meters bill answered
|
|
7
|
+
* duration under a telecom increment pair. Each meter kind has its own strict
|
|
8
|
+
* schema so a rate object with fields from the wrong kind (e.g. `increments`
|
|
9
|
+
* on `sms`) fails validation instead of being silently ignored.
|
|
10
|
+
*
|
|
11
|
+
* The two WhatsApp voice meters are reserved: they must be priced in every
|
|
12
|
+
* plan, but no dispatch path produces them until Fonoster ships that transport.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MESSAGE_METERS: readonly ["sms", "email", "whatsappMessage"];
|
|
15
|
+
export declare const VOICE_METERS: readonly ["voicePrerecorded", "voiceAi", "whatsappVoicePrerecorded", "whatsappVoiceAi"];
|
|
16
|
+
export declare const BILLING_METERS: readonly ["sms", "email", "whatsappMessage", "voicePrerecorded", "voiceAi", "whatsappVoicePrerecorded", "whatsappVoiceAi"];
|
|
17
|
+
export type MessageMeter = (typeof MESSAGE_METERS)[number];
|
|
18
|
+
export type VoiceMeter = (typeof VOICE_METERS)[number];
|
|
19
|
+
export type BillingMeter = (typeof BILLING_METERS)[number];
|
|
20
|
+
export declare const billingMeterSchema: z.ZodEnum<{
|
|
21
|
+
sms: "sms";
|
|
22
|
+
email: "email";
|
|
23
|
+
whatsappMessage: "whatsappMessage";
|
|
24
|
+
voicePrerecorded: "voicePrerecorded";
|
|
25
|
+
voiceAi: "voiceAi";
|
|
26
|
+
whatsappVoicePrerecorded: "whatsappVoicePrerecorded";
|
|
27
|
+
whatsappVoiceAi: "whatsappVoiceAi";
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Telecom billing-increment notation `"initial/subsequent"` in seconds — the
|
|
31
|
+
* industry standard (15/15 rounds every call up to 15s blocks; 60/6 bills a
|
|
32
|
+
* full first minute then 6s steps).
|
|
33
|
+
*/
|
|
34
|
+
export declare const incrementNotationSchema: z.ZodString;
|
|
35
|
+
/** A message meter's rate: decimal currency units per message. */
|
|
36
|
+
export declare const messageRateSchema: z.ZodObject<{
|
|
37
|
+
perMessage: z.ZodNumber;
|
|
38
|
+
}, z.core.$strict>;
|
|
39
|
+
export type MessageRate = z.infer<typeof messageRateSchema>;
|
|
40
|
+
/** A voice meter's rate: decimal currency units per minute + increment pair. */
|
|
41
|
+
export declare const voiceRateSchema: z.ZodObject<{
|
|
42
|
+
perMinute: z.ZodNumber;
|
|
43
|
+
increments: z.ZodString;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
export type VoiceRate = z.infer<typeof voiceRateSchema>;
|
|
46
|
+
/** A complete rate card: all seven meters, each with its kind's schema. */
|
|
47
|
+
export declare const ratesSchema: z.ZodObject<{
|
|
48
|
+
sms: z.ZodObject<{
|
|
49
|
+
perMessage: z.ZodNumber;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
email: z.ZodObject<{
|
|
52
|
+
perMessage: z.ZodNumber;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
whatsappMessage: z.ZodObject<{
|
|
55
|
+
perMessage: z.ZodNumber;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
voicePrerecorded: z.ZodObject<{
|
|
58
|
+
perMinute: z.ZodNumber;
|
|
59
|
+
increments: z.ZodString;
|
|
60
|
+
}, z.core.$strict>;
|
|
61
|
+
voiceAi: z.ZodObject<{
|
|
62
|
+
perMinute: z.ZodNumber;
|
|
63
|
+
increments: z.ZodString;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
whatsappVoicePrerecorded: z.ZodObject<{
|
|
66
|
+
perMinute: z.ZodNumber;
|
|
67
|
+
increments: z.ZodString;
|
|
68
|
+
}, z.core.$strict>;
|
|
69
|
+
whatsappVoiceAi: z.ZodObject<{
|
|
70
|
+
perMinute: z.ZodNumber;
|
|
71
|
+
increments: z.ZodString;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
export type Rates = z.infer<typeof ratesSchema>;
|
|
75
|
+
/**
|
|
76
|
+
* Per-workspace enterprise overrides: any subset of the rate card, validated
|
|
77
|
+
* with the same per-meter schemas. Stored on workspace billing state (DB),
|
|
78
|
+
* never in configuration.
|
|
79
|
+
*/
|
|
80
|
+
export declare const rateOverridesSchema: z.ZodObject<{
|
|
81
|
+
sms: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
perMessage: z.ZodNumber;
|
|
83
|
+
}, z.core.$strict>>;
|
|
84
|
+
email: z.ZodOptional<z.ZodObject<{
|
|
85
|
+
perMessage: z.ZodNumber;
|
|
86
|
+
}, z.core.$strict>>;
|
|
87
|
+
whatsappMessage: z.ZodOptional<z.ZodObject<{
|
|
88
|
+
perMessage: z.ZodNumber;
|
|
89
|
+
}, z.core.$strict>>;
|
|
90
|
+
voicePrerecorded: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
perMinute: z.ZodNumber;
|
|
92
|
+
increments: z.ZodString;
|
|
93
|
+
}, z.core.$strict>>;
|
|
94
|
+
voiceAi: z.ZodOptional<z.ZodObject<{
|
|
95
|
+
perMinute: z.ZodNumber;
|
|
96
|
+
increments: z.ZodString;
|
|
97
|
+
}, z.core.$strict>>;
|
|
98
|
+
whatsappVoicePrerecorded: z.ZodOptional<z.ZodObject<{
|
|
99
|
+
perMinute: z.ZodNumber;
|
|
100
|
+
increments: z.ZodString;
|
|
101
|
+
}, z.core.$strict>>;
|
|
102
|
+
whatsappVoiceAi: z.ZodOptional<z.ZodObject<{
|
|
103
|
+
perMinute: z.ZodNumber;
|
|
104
|
+
increments: z.ZodString;
|
|
105
|
+
}, z.core.$strict>>;
|
|
106
|
+
}, z.core.$strict>;
|
|
107
|
+
export type RateOverrides = z.infer<typeof rateOverridesSchema>;
|
|
108
|
+
//# sourceMappingURL=rates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rates.d.ts","sourceRoot":"","sources":["../../src/billing/rates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,cAAc,8CAA+C,CAAC;AAC3E,eAAO,MAAM,YAAY,yFAKf,CAAC;AACX,eAAO,MAAM,cAAc,4HAAgD,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,eAAO,MAAM,kBAAkB;;;;;;;;EAAyB,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,aAKjC,CAAC;AAEJ,kEAAkE;AAClE,eAAO,MAAM,iBAAiB;;kBAE5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,gFAAgF;AAChF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,2EAA2E;AAC3E,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;kBAQtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAwB,CAAC;AACzD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The billable-meter catalog and per-meter rate schemas.
|
|
4
|
+
*
|
|
5
|
+
* The catalog is closed-world: exactly seven meters, one per dispatchable
|
|
6
|
+
* channel/mode. Message meters bill per message; voice meters bill answered
|
|
7
|
+
* duration under a telecom increment pair. Each meter kind has its own strict
|
|
8
|
+
* schema so a rate object with fields from the wrong kind (e.g. `increments`
|
|
9
|
+
* on `sms`) fails validation instead of being silently ignored.
|
|
10
|
+
*
|
|
11
|
+
* The two WhatsApp voice meters are reserved: they must be priced in every
|
|
12
|
+
* plan, but no dispatch path produces them until Fonoster ships that transport.
|
|
13
|
+
*/
|
|
14
|
+
export const MESSAGE_METERS = ["sms", "email", "whatsappMessage"];
|
|
15
|
+
export const VOICE_METERS = [
|
|
16
|
+
"voicePrerecorded",
|
|
17
|
+
"voiceAi",
|
|
18
|
+
"whatsappVoicePrerecorded",
|
|
19
|
+
"whatsappVoiceAi"
|
|
20
|
+
];
|
|
21
|
+
export const BILLING_METERS = [...MESSAGE_METERS, ...VOICE_METERS];
|
|
22
|
+
export const billingMeterSchema = z.enum(BILLING_METERS);
|
|
23
|
+
/**
|
|
24
|
+
* Telecom billing-increment notation `"initial/subsequent"` in seconds — the
|
|
25
|
+
* industry standard (15/15 rounds every call up to 15s blocks; 60/6 bills a
|
|
26
|
+
* full first minute then 6s steps).
|
|
27
|
+
*/
|
|
28
|
+
export const incrementNotationSchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[1-9]\d*\/[1-9]\d*$/, 'increments must be "initial/subsequent" in positive whole seconds (e.g. "15/15")');
|
|
31
|
+
/** A message meter's rate: decimal currency units per message. */
|
|
32
|
+
export const messageRateSchema = z.strictObject({
|
|
33
|
+
perMessage: z.number().positive()
|
|
34
|
+
});
|
|
35
|
+
/** A voice meter's rate: decimal currency units per minute + increment pair. */
|
|
36
|
+
export const voiceRateSchema = z.strictObject({
|
|
37
|
+
perMinute: z.number().positive(),
|
|
38
|
+
increments: incrementNotationSchema
|
|
39
|
+
});
|
|
40
|
+
/** A complete rate card: all seven meters, each with its kind's schema. */
|
|
41
|
+
export const ratesSchema = z.strictObject({
|
|
42
|
+
sms: messageRateSchema,
|
|
43
|
+
email: messageRateSchema,
|
|
44
|
+
whatsappMessage: messageRateSchema,
|
|
45
|
+
voicePrerecorded: voiceRateSchema,
|
|
46
|
+
voiceAi: voiceRateSchema,
|
|
47
|
+
whatsappVoicePrerecorded: voiceRateSchema,
|
|
48
|
+
whatsappVoiceAi: voiceRateSchema
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Per-workspace enterprise overrides: any subset of the rate card, validated
|
|
52
|
+
* with the same per-meter schemas. Stored on workspace billing state (DB),
|
|
53
|
+
* never in configuration.
|
|
54
|
+
*/
|
|
55
|
+
export const rateOverridesSchema = ratesSchema.partial();
|
|
56
|
+
//# sourceMappingURL=rates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rates.js","sourceRoot":"","sources":["../../src/billing/rates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,iBAAiB,CAAU,CAAC;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,SAAS;IACT,0BAA0B;IAC1B,iBAAiB;CACT,CAAC;AACX,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,YAAY,CAAU,CAAC;AAM5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,EAAE;KACR,KAAK,CACJ,sBAAsB,EACtB,kFAAkF,CACnF,CAAC;AAEJ,kEAAkE;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC9C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IAC5C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,uBAAuB;CACpC,CAAC,CAAC;AAGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,YAAY,CAAC;IACxC,GAAG,EAAE,iBAAiB;IACtB,KAAK,EAAE,iBAAiB;IACxB,eAAe,EAAE,iBAAiB;IAClC,gBAAgB,EAAE,eAAe;IACjC,OAAO,EAAE,eAAe;IACxB,wBAAwB,EAAE,eAAe;IACzC,eAAe,EAAE,eAAe;CACjC,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type EvaluationParameters, type Scorecard } from "../evaluation/scorecard.js";
|
|
3
|
+
/**
|
|
4
|
+
* `engine-eval` — the npx-runnable judge client (engine-scorecard capability,
|
|
5
|
+
* requirement "A deployment can be evaluated with npx"). Fetches the flight-
|
|
6
|
+
* recorder stream + engine parameters from a deployment's
|
|
7
|
+
* `GET /api/engine/events`, runs the pure `evaluate` from this same package
|
|
8
|
+
* locally, and prints the resulting scorecard. No repository checkout,
|
|
9
|
+
* database access, or config file is required — only the API URL and a
|
|
10
|
+
* workspace API key pair. All imports below are relative (this file ships
|
|
11
|
+
* inside `@qcobro/common`; it must not import its own package by name).
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_URL = "https://api.qcobro.com";
|
|
14
|
+
export interface CliOptions {
|
|
15
|
+
url: string;
|
|
16
|
+
from?: string;
|
|
17
|
+
to?: string;
|
|
18
|
+
accessKeyId?: string;
|
|
19
|
+
accessKeySecret?: string;
|
|
20
|
+
latencyP95?: number;
|
|
21
|
+
maxErrorRate?: number;
|
|
22
|
+
livenessTicks?: number;
|
|
23
|
+
json: boolean;
|
|
24
|
+
help: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare const USAGE = "Usage: engine-eval [options]\n\nEvaluate a QCobro deployment's campaigns-engine event stream against the\nbuilt-in invariant catalog (safety, performance, liveness) and print a\nscorecard. Exits non-zero when the overall verdict is fail.\n\nOptions:\n --url <url> Deployment API base URL (default: https://api.qcobro.com)\n --from <iso> Range start (ISO datetime)\n --to <iso> Range end (ISO datetime)\n (omit both to evaluate today, operator's local timezone)\n --access-key-id <id> Workspace API key id (or QCOBRO_ACCESS_KEY_ID)\n --access-key-secret <secret> Workspace API key secret (or QCOBRO_ACCESS_KEY_SECRET)\n --latency-p95 <ms> Override PERF-2 threshold (dispatch latency p95, ms)\n --max-error-rate <0..1> Override PERF-3 threshold (max per-channel error rate)\n --liveness-ticks <n> Override LIVE-1 threshold (ticks to first attempt)\n --json Print the scorecard as JSON instead of text\n --help Show this help\n\nExit codes: 0 = pass, 1 = fail, 2 = usage/network/auth error.\n";
|
|
27
|
+
/** Raised for usage, network, or auth failures — mapped to exit code 2. */
|
|
28
|
+
export declare class CliError extends Error {
|
|
29
|
+
}
|
|
30
|
+
/** Parses argv into typed options. Throws `CliError` on malformed flags. */
|
|
31
|
+
export declare function parseCliArgs(argv: string[]): CliOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the from/to range. When BOTH flags are omitted, defaults to today
|
|
34
|
+
* in the operator's local timezone (the machine running the CLI): local
|
|
35
|
+
* midnight through now. When either flag is given, both are passed through
|
|
36
|
+
* unchanged (no default is applied to just one side).
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveRange(from: string | undefined, to: string | undefined, now: Date): {
|
|
39
|
+
from?: string;
|
|
40
|
+
to?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Resolves the API key pair from flags, falling back to environment variables. */
|
|
43
|
+
export declare function resolveCredentials(opts: Pick<CliOptions, "accessKeyId" | "accessKeySecret">, env: Record<string, string | undefined>): {
|
|
44
|
+
accessKeyId: string;
|
|
45
|
+
accessKeySecret: string;
|
|
46
|
+
};
|
|
47
|
+
/** Builds only the threshold overrides the operator actually passed. */
|
|
48
|
+
export declare function buildThresholdOverrides(opts: Pick<CliOptions, "latencyP95" | "maxErrorRate" | "livenessTicks">): Partial<EvaluationParameters["thresholds"]>;
|
|
49
|
+
/**
|
|
50
|
+
* Renders rows as aligned columns: each column as wide as its widest cell,
|
|
51
|
+
* numeric-looking cells right-aligned, two spaces between columns. Width is
|
|
52
|
+
* computed from the data, so alignment holds for any campaign name or count.
|
|
53
|
+
*/
|
|
54
|
+
export declare function alignColumns(rows: string[][], indent?: string): string[];
|
|
55
|
+
/** Renders the scorecard as a plain-ASCII, human-readable summary. */
|
|
56
|
+
export declare function formatScorecard(scorecard: Scorecard, ctx: {
|
|
57
|
+
url: string;
|
|
58
|
+
from?: string;
|
|
59
|
+
to?: string;
|
|
60
|
+
}): string;
|
|
61
|
+
/**
|
|
62
|
+
* Entry point. Exported (rather than only run inline) so it can be exercised
|
|
63
|
+
* directly if ever needed; the module also self-invokes when run via `node`
|
|
64
|
+
* or `npx` (see the guard at the bottom of the file).
|
|
65
|
+
*/
|
|
66
|
+
export declare function main(argv?: string[], env?: Record<string, string | undefined>, now?: Date, stdout?: (s: string) => void, stderr?: (s: string) => void): Promise<number>;
|
|
67
|
+
//# sourceMappingURL=engineEval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engineEval.d.ts","sourceRoot":"","sources":["../../src/bin/engineEval.ts"],"names":[],"mappings":";AAGA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,2BAA2B,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,eAAO,MAAM,KAAK,opCAoBjB,CAAC;AAEF,2EAA2E;AAC3E,qBAAa,QAAS,SAAQ,KAAK;CAAG;AAwBtC,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAmBvD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,GAAG,EAAE,IAAI,GACR;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAMhC;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,iBAAiB,CAAC,EACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAUlD;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,GAAG,cAAc,GAAG,eAAe,CAAC,GACtE,OAAO,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAM7C;AA6CD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,SAAO,GAAG,MAAM,EAAE,CAqBtE;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,MAAM,CA4ER;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,GAAG,GAAE,IAAiB,EACtB,MAAM,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAqC,EAC5D,MAAM,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAqC,GAC3D,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { evaluate } from "../evaluation/evaluate.js";
|
|
4
|
+
import { evaluationParametersSchema } from "../evaluation/scorecard.js";
|
|
5
|
+
/**
|
|
6
|
+
* `engine-eval` — the npx-runnable judge client (engine-scorecard capability,
|
|
7
|
+
* requirement "A deployment can be evaluated with npx"). Fetches the flight-
|
|
8
|
+
* recorder stream + engine parameters from a deployment's
|
|
9
|
+
* `GET /api/engine/events`, runs the pure `evaluate` from this same package
|
|
10
|
+
* locally, and prints the resulting scorecard. No repository checkout,
|
|
11
|
+
* database access, or config file is required — only the API URL and a
|
|
12
|
+
* workspace API key pair. All imports below are relative (this file ships
|
|
13
|
+
* inside `@qcobro/common`; it must not import its own package by name).
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_URL = "https://api.qcobro.com";
|
|
16
|
+
export const USAGE = `Usage: engine-eval [options]
|
|
17
|
+
|
|
18
|
+
Evaluate a QCobro deployment's campaigns-engine event stream against the
|
|
19
|
+
built-in invariant catalog (safety, performance, liveness) and print a
|
|
20
|
+
scorecard. Exits non-zero when the overall verdict is fail.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--url <url> Deployment API base URL (default: ${DEFAULT_URL})
|
|
24
|
+
--from <iso> Range start (ISO datetime)
|
|
25
|
+
--to <iso> Range end (ISO datetime)
|
|
26
|
+
(omit both to evaluate today, operator's local timezone)
|
|
27
|
+
--access-key-id <id> Workspace API key id (or QCOBRO_ACCESS_KEY_ID)
|
|
28
|
+
--access-key-secret <secret> Workspace API key secret (or QCOBRO_ACCESS_KEY_SECRET)
|
|
29
|
+
--latency-p95 <ms> Override PERF-2 threshold (dispatch latency p95, ms)
|
|
30
|
+
--max-error-rate <0..1> Override PERF-3 threshold (max per-channel error rate)
|
|
31
|
+
--liveness-ticks <n> Override LIVE-1 threshold (ticks to first attempt)
|
|
32
|
+
--json Print the scorecard as JSON instead of text
|
|
33
|
+
--help Show this help
|
|
34
|
+
|
|
35
|
+
Exit codes: 0 = pass, 1 = fail, 2 = usage/network/auth error.
|
|
36
|
+
`;
|
|
37
|
+
/** Raised for usage, network, or auth failures — mapped to exit code 2. */
|
|
38
|
+
export class CliError extends Error {
|
|
39
|
+
}
|
|
40
|
+
const OPTION_SPEC = {
|
|
41
|
+
url: { type: "string" },
|
|
42
|
+
from: { type: "string" },
|
|
43
|
+
to: { type: "string" },
|
|
44
|
+
"access-key-id": { type: "string" },
|
|
45
|
+
"access-key-secret": { type: "string" },
|
|
46
|
+
"latency-p95": { type: "string" },
|
|
47
|
+
"max-error-rate": { type: "string" },
|
|
48
|
+
"liveness-ticks": { type: "string" },
|
|
49
|
+
json: { type: "boolean", default: false },
|
|
50
|
+
help: { type: "boolean", default: false }
|
|
51
|
+
};
|
|
52
|
+
function parseNumberFlag(flag, raw) {
|
|
53
|
+
if (raw === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const value = Number(raw);
|
|
56
|
+
if (!Number.isFinite(value)) {
|
|
57
|
+
throw new CliError(`--${flag} must be a number, got "${raw}"`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
/** Parses argv into typed options. Throws `CliError` on malformed flags. */
|
|
62
|
+
export function parseCliArgs(argv) {
|
|
63
|
+
let values;
|
|
64
|
+
try {
|
|
65
|
+
({ values } = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: false }));
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
throw new CliError(`Invalid arguments: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
url: values.url ?? DEFAULT_URL,
|
|
72
|
+
from: values.from,
|
|
73
|
+
to: values.to,
|
|
74
|
+
accessKeyId: values["access-key-id"],
|
|
75
|
+
accessKeySecret: values["access-key-secret"],
|
|
76
|
+
latencyP95: parseNumberFlag("latency-p95", values["latency-p95"]),
|
|
77
|
+
maxErrorRate: parseNumberFlag("max-error-rate", values["max-error-rate"]),
|
|
78
|
+
livenessTicks: parseNumberFlag("liveness-ticks", values["liveness-ticks"]),
|
|
79
|
+
json: values.json ?? false,
|
|
80
|
+
help: values.help ?? false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the from/to range. When BOTH flags are omitted, defaults to today
|
|
85
|
+
* in the operator's local timezone (the machine running the CLI): local
|
|
86
|
+
* midnight through now. When either flag is given, both are passed through
|
|
87
|
+
* unchanged (no default is applied to just one side).
|
|
88
|
+
*/
|
|
89
|
+
export function resolveRange(from, to, now) {
|
|
90
|
+
if (from === undefined && to === undefined) {
|
|
91
|
+
const localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
|
92
|
+
return { from: localMidnight.toISOString(), to: now.toISOString() };
|
|
93
|
+
}
|
|
94
|
+
return { from, to };
|
|
95
|
+
}
|
|
96
|
+
/** Resolves the API key pair from flags, falling back to environment variables. */
|
|
97
|
+
export function resolveCredentials(opts, env) {
|
|
98
|
+
const accessKeyId = opts.accessKeyId ?? env.QCOBRO_ACCESS_KEY_ID;
|
|
99
|
+
const accessKeySecret = opts.accessKeySecret ?? env.QCOBRO_ACCESS_KEY_SECRET;
|
|
100
|
+
if (!accessKeyId || !accessKeySecret) {
|
|
101
|
+
throw new CliError("Missing API key credentials: pass --access-key-id/--access-key-secret or set " +
|
|
102
|
+
"QCOBRO_ACCESS_KEY_ID/QCOBRO_ACCESS_KEY_SECRET");
|
|
103
|
+
}
|
|
104
|
+
return { accessKeyId, accessKeySecret };
|
|
105
|
+
}
|
|
106
|
+
/** Builds only the threshold overrides the operator actually passed. */
|
|
107
|
+
export function buildThresholdOverrides(opts) {
|
|
108
|
+
const thresholds = {};
|
|
109
|
+
if (opts.latencyP95 !== undefined)
|
|
110
|
+
thresholds.dispatchLatencyP95Ms = opts.latencyP95;
|
|
111
|
+
if (opts.maxErrorRate !== undefined)
|
|
112
|
+
thresholds.maxErrorRate = opts.maxErrorRate;
|
|
113
|
+
if (opts.livenessTicks !== undefined)
|
|
114
|
+
thresholds.livenessTicks = opts.livenessTicks;
|
|
115
|
+
return thresholds;
|
|
116
|
+
}
|
|
117
|
+
async function fetchEvents(url, range, accessKeyId, accessKeySecret) {
|
|
118
|
+
let endpoint;
|
|
119
|
+
try {
|
|
120
|
+
endpoint = new URL("/api/engine/events", url);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new CliError(`Invalid --url "${url}"`);
|
|
124
|
+
}
|
|
125
|
+
if (range.from)
|
|
126
|
+
endpoint.searchParams.set("from", range.from);
|
|
127
|
+
if (range.to)
|
|
128
|
+
endpoint.searchParams.set("to", range.to);
|
|
129
|
+
const auth = Buffer.from(`${accessKeyId}:${accessKeySecret}`).toString("base64");
|
|
130
|
+
let res;
|
|
131
|
+
try {
|
|
132
|
+
res = await fetch(endpoint, { headers: { Authorization: `Basic ${auth}` } });
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
throw new CliError(`Could not reach ${endpoint.origin}: ${err instanceof Error ? err.message : String(err)}`);
|
|
136
|
+
}
|
|
137
|
+
if (res.status === 401) {
|
|
138
|
+
throw new CliError("Authentication failed: invalid API key credentials");
|
|
139
|
+
}
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
throw new CliError(`Request failed: ${res.status} ${res.statusText}`);
|
|
142
|
+
}
|
|
143
|
+
return (await res.json());
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Renders rows as aligned columns: each column as wide as its widest cell,
|
|
147
|
+
* numeric-looking cells right-aligned, two spaces between columns. Width is
|
|
148
|
+
* computed from the data, so alignment holds for any campaign name or count.
|
|
149
|
+
*/
|
|
150
|
+
export function alignColumns(rows, indent = " ") {
|
|
151
|
+
const widths = [];
|
|
152
|
+
for (const row of rows) {
|
|
153
|
+
row.forEach((cell, i) => {
|
|
154
|
+
widths[i] = Math.max(widths[i] ?? 0, cell.length);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const numeric = /^[\d,.%]+$/;
|
|
158
|
+
return rows.map((row) => (indent +
|
|
159
|
+
row
|
|
160
|
+
.map((cell, i) => {
|
|
161
|
+
const w = widths[i];
|
|
162
|
+
// Last column flows free; numbers right-align under their header.
|
|
163
|
+
if (i === row.length - 1)
|
|
164
|
+
return cell;
|
|
165
|
+
return numeric.test(cell) ? cell.padStart(w) : cell.padEnd(w);
|
|
166
|
+
})
|
|
167
|
+
.join(" ")).trimEnd());
|
|
168
|
+
}
|
|
169
|
+
/** Renders the scorecard as a plain-ASCII, human-readable summary. */
|
|
170
|
+
export function formatScorecard(scorecard, ctx) {
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push("QCobro engine-eval");
|
|
173
|
+
lines.push(`url: ${ctx.url}`);
|
|
174
|
+
lines.push(`range: ${ctx.from ?? "(unbounded)"} .. ${ctx.to ?? "(unbounded)"}`);
|
|
175
|
+
lines.push(`totals: events=${scorecard.totals.events} ticks=${scorecard.totals.ticks} ` +
|
|
176
|
+
`campaigns=${scorecard.totals.campaigns} accountsConsidered=${scorecard.totals.accountsConsidered}`);
|
|
177
|
+
if (scorecard.gaps.length === 0) {
|
|
178
|
+
lines.push("gaps: none");
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
lines.push(`gaps: ${scorecard.gaps.length} tick(s) started but never completed`);
|
|
182
|
+
for (const g of scorecard.gaps)
|
|
183
|
+
lines.push(` - tick ${g.tickId} at ${g.at}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push(`VERDICT: ${scorecard.verdict.toUpperCase()}`);
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push("INVARIANTS");
|
|
189
|
+
lines.push(...alignColumns(scorecard.invariants.map((inv) => [
|
|
190
|
+
`[${inv.verdict === "pass" ? "PASS" : "FAIL"}]`,
|
|
191
|
+
inv.id,
|
|
192
|
+
inv.name,
|
|
193
|
+
inv.scope,
|
|
194
|
+
inv.metric ?? ""
|
|
195
|
+
])));
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push("BY CAMPAIGN");
|
|
198
|
+
// Names come from the stream's campaign.evaluated events; streams recorded
|
|
199
|
+
// before names existed fall back to the id.
|
|
200
|
+
const campaignName = new Map(scorecard.campaigns.map((c) => [c.campaignId, c.name]));
|
|
201
|
+
const displayName = (id) => campaignName.get(id) ?? id;
|
|
202
|
+
if (scorecard.campaigns.length === 0) {
|
|
203
|
+
lines.push(" (no campaigns in range)");
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
lines.push(...alignColumns([
|
|
207
|
+
["campaign", "ticks", "considered", "dispatched", "failed", "suppressed", "violations"],
|
|
208
|
+
...scorecard.campaigns.map((c) => [
|
|
209
|
+
c.name ?? c.campaignId,
|
|
210
|
+
String(c.ticksSeen),
|
|
211
|
+
String(c.considered),
|
|
212
|
+
String(c.dispatched),
|
|
213
|
+
String(c.failed),
|
|
214
|
+
String(c.suppressed),
|
|
215
|
+
Object.entries(c.violations)
|
|
216
|
+
.map(([id, count]) => `${id}:${count}`)
|
|
217
|
+
.join(" ") || "-"
|
|
218
|
+
])
|
|
219
|
+
]));
|
|
220
|
+
}
|
|
221
|
+
const violating = scorecard.invariants.filter((inv) => inv.violations.length > 0);
|
|
222
|
+
if (violating.length > 0) {
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("VIOLATIONS");
|
|
225
|
+
for (const inv of violating) {
|
|
226
|
+
lines.push(` ${inv.id} ${inv.name}`);
|
|
227
|
+
for (const v of inv.violations) {
|
|
228
|
+
const scope = [
|
|
229
|
+
v.campaignId ? `campaign "${displayName(v.campaignId)}"` : undefined,
|
|
230
|
+
v.portfolioAccountId ? `account ${v.portfolioAccountId}` : undefined
|
|
231
|
+
]
|
|
232
|
+
.filter((s) => Boolean(s))
|
|
233
|
+
.join(", ");
|
|
234
|
+
lines.push(` - ${scope ? scope + ": " : ""}${v.detail} [events: ${v.eventIds.join(", ")}]`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return lines.join("\n");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Entry point. Exported (rather than only run inline) so it can be exercised
|
|
242
|
+
* directly if ever needed; the module also self-invokes when run via `node`
|
|
243
|
+
* or `npx` (see the guard at the bottom of the file).
|
|
244
|
+
*/
|
|
245
|
+
export async function main(argv = process.argv.slice(2), env = process.env, now = new Date(), stdout = (s) => process.stdout.write(s), stderr = (s) => process.stderr.write(s)) {
|
|
246
|
+
let opts;
|
|
247
|
+
try {
|
|
248
|
+
opts = parseCliArgs(argv);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
stderr(`engine-eval: ${err instanceof Error ? err.message : String(err)}\n\n${USAGE}`);
|
|
252
|
+
return 2;
|
|
253
|
+
}
|
|
254
|
+
if (opts.help) {
|
|
255
|
+
stdout(USAGE);
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const { accessKeyId, accessKeySecret } = resolveCredentials(opts, env);
|
|
260
|
+
const range = resolveRange(opts.from, opts.to, now);
|
|
261
|
+
const { events, parameters, truncated } = await fetchEvents(opts.url, range, accessKeyId, accessKeySecret);
|
|
262
|
+
if (truncated) {
|
|
263
|
+
stderr("engine-eval: warning — the server truncated the event stream for this range; " +
|
|
264
|
+
"verdicts may miss violations. Narrow --from/--to and re-run.\n");
|
|
265
|
+
}
|
|
266
|
+
const evaluationParameters = evaluationParametersSchema.parse({
|
|
267
|
+
tickSeconds: parameters.tickSeconds,
|
|
268
|
+
ratesPerMinute: parameters.ratesPerMinute,
|
|
269
|
+
thresholds: buildThresholdOverrides(opts)
|
|
270
|
+
});
|
|
271
|
+
const scorecard = evaluate(events, evaluationParameters);
|
|
272
|
+
if (opts.json) {
|
|
273
|
+
stdout(JSON.stringify(scorecard, null, 2) + "\n");
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
stdout(formatScorecard(scorecard, { url: opts.url, from: range.from, to: range.to }) + "\n");
|
|
277
|
+
}
|
|
278
|
+
return scorecard.verdict === "pass" ? 0 : 1;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
282
|
+
stderr(`engine-eval: ${message}\n`);
|
|
283
|
+
return 2;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Only run when this file is the process entry point (not when imported by tests).
|
|
287
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
288
|
+
main().then((code) => process.exit(code));
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=engineEval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engineEval.js","sourceRoot":"","sources":["../../src/bin/engineEval.ts"],"names":[],"mappings":"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"}
|