You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* Go-No-Go GD-4 + RPA probe 3/3 验收task-117 + task-134
* 用法tsx scripts/go-no-go-check.ts [--mock]
*/
import "dotenv/config";
import {
computeRates,
type MetricsCounters,
} from "@/modules/metrics/collector";
import { METRIC_COUNTER } from "@/lib/constants/metrics";
import { validateProbeItems } from "@/lib/rpa/probe-validation";
import { executeProbeQuote } from "@/lib/rpa/run-probe";
const THRESHOLDS = {
api_success_rate: 99.5,
rpa_success_rate: 85,
realtime_rate: 90,
cache_hit_rate: 90,
p95_latency_ms: 30_000,
probe_success_count: 3,
} as const;
const PROBE_ROUNDS = 3;
type CheckResult = {
name: string;
value: number | string;
threshold: number | string;
pass: boolean;
operator: ">=" | "<" | "—";
skipped?: boolean;
};
function buildMockCounters(): MetricsCounters {
return {
[METRIC_COUNTER.POST_TOTAL]: 1000,
[METRIC_COUNTER.POST_DONE]: 996,
[METRIC_COUNTER.CACHE_HIT_L1]: 200,
[METRIC_COUNTER.CACHE_HIT_L2]: 720,
[METRIC_COUNTER.RPA_TRIGGERED]: 100,
[METRIC_COUNTER.RPA_DONE]: 88,
[METRIC_COUNTER.DONE_TOTAL]: 996,
[METRIC_COUNTER.DONE_REALTIME]: 910,
};
}
function isMockMode(): boolean {
return (
process.argv.includes("--mock") || process.env.RPA_MOCK_MODE === "true"
);
}
async function loadSnapshot(): Promise<{
rates: ReturnType<typeof computeRates>;
p95: number;
}> {
if (process.argv.includes("--mock")) {
const counters = buildMockCounters();
return { rates: computeRates(counters), p95: 12_000 };
}
const { getMetricsSnapshot } = await import("@/modules/metrics/collector");
const snapshot = await getMetricsSnapshot();
return {
rates: {
api_success_rate: snapshot.api_success_rate,
rpa_success_rate: snapshot.rpa_success_rate,
realtime_rate: snapshot.realtime_rate,
cache_hit_rate: snapshot.cache_hit_rate,
},
p95: snapshot.p95_latency_ms,
};
}
function runMetricChecks(
rates: ReturnType<typeof computeRates>,
p95: number,
): CheckResult[] {
return [
{
name: "API success rate",
value: rates.api_success_rate,
threshold: THRESHOLDS.api_success_rate,
pass: rates.api_success_rate >= THRESHOLDS.api_success_rate,
operator: ">=",
},
{
name: "RPA success rate",
value: rates.rpa_success_rate,
threshold: THRESHOLDS.rpa_success_rate,
pass: rates.rpa_success_rate >= THRESHOLDS.rpa_success_rate,
operator: ">=",
},
{
name: "Realtime rate",
value: rates.realtime_rate,
threshold: THRESHOLDS.realtime_rate,
pass: rates.realtime_rate >= THRESHOLDS.realtime_rate,
operator: ">=",
},
{
name: "Cache hit rate",
value: rates.cache_hit_rate,
threshold: THRESHOLDS.cache_hit_rate,
pass: rates.cache_hit_rate >= THRESHOLDS.cache_hit_rate,
operator: ">=",
},
{
name: "P95 latency (ms)",
value: p95,
threshold: THRESHOLDS.p95_latency_ms,
pass: p95 < THRESHOLDS.p95_latency_ms,
operator: "<",
},
];
}
/** PRD §12.7 CHG-P09真实 env 下 probe 连续 3 次 exit 0 */
async function runProbeGate(): Promise<CheckResult> {
if (isMockMode()) {
return {
name: "RPA probe 3/3",
value: "跳过",
threshold: PROBE_ROUNDS,
pass: true,
operator: "—",
skipped: true,
};
}
let success = 0;
for (let round = 1; round <= PROBE_ROUNDS; round++) {
try {
console.log(`[go-no-go] probe 第 ${round}/${PROBE_ROUNDS} 次…`);
const result = await executeProbeQuote();
const validation = validateProbeItems(result.items);
if (validation.ok) {
success += 1;
} else {
console.error(`[go-no-go] probe 失败: ${validation.message}`);
}
} catch (error) {
console.error(`[go-no-go] probe 异常:`, error);
}
}
return {
name: "RPA probe 3/3",
value: success,
threshold: THRESHOLDS.probe_success_count,
pass: success === PROBE_ROUNDS,
operator: ">=",
};
}
async function main(): Promise<void> {
const { rates, p95 } = await loadSnapshot();
const metricChecks = runMetricChecks(rates, p95);
const probeCheck = await runProbeGate();
const checks = [...metricChecks, probeCheck];
const required = checks.filter((c) => !c.skipped);
const allPass = required.every((c) => c.pass);
console.log("=== Go-No-Go 验收 ===");
for (const check of checks) {
if (check.skipped) {
console.log(`[SKIP] ${check.name}: mock 模式跳过 probe 门禁`);
continue;
}
const status = check.pass ? "PASS" : "FAIL";
console.log(
`[${status}] ${check.name}: ${check.value} (需 ${check.operator} ${check.threshold})`,
);
}
console.log(`\n总体判定: ${allPass ? "PASS" : "FAIL"}`);
if (!allPass) {
process.exit(1);
}
}
main().catch((error) => {
console.error("[go-no-go] 执行失败:", error);
process.exit(1);
});