ЮИ - Упростить карточки автопрогонов до названия даты id режима и типа
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
interface AgentSemanticSpecSummary {
|
||||
scenario_id: string;
|
||||
domain: string | null;
|
||||
title: string | null;
|
||||
semantic_tags: string[];
|
||||
steps_total: number;
|
||||
}
|
||||
|
||||
interface AgentSemanticInvariantSummary {
|
||||
direct_answer_ok: boolean | null;
|
||||
temporal_honesty_ok: boolean | null;
|
||||
selected_object_continuity_ok: boolean | null;
|
||||
truth_gate_ok: boolean | null;
|
||||
human_answer_quality_ok: boolean | null;
|
||||
meta_context_integrity_ok: boolean | null;
|
||||
}
|
||||
|
||||
export interface AgentSemanticAcceptanceSummary {
|
||||
scenario_id: string;
|
||||
output_dir: string;
|
||||
relative_output_dir: string;
|
||||
final_status: string | null;
|
||||
final_status_reason: string | null;
|
||||
review_overall_status: string | null;
|
||||
acceptance_gate_passed: boolean | null;
|
||||
critical_path_green: boolean | null;
|
||||
unresolved_p0_count: number | null;
|
||||
unresolved_p1_count: number | null;
|
||||
unresolved_p2_count: number | null;
|
||||
steps_total: number | null;
|
||||
steps_passed: number | null;
|
||||
steps_with_warning: number | null;
|
||||
steps_failed: number | null;
|
||||
updated_at: string | null;
|
||||
invariants: AgentSemanticInvariantSummary;
|
||||
}
|
||||
|
||||
function toRecord(value: unknown): Record<string, unknown> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function toStringSafe(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
function toBooleanSafe(value: unknown): boolean | null {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const lowered = value.trim().toLowerCase();
|
||||
if (["1", "true", "yes", "on"].includes(lowered)) return true;
|
||||
if (["0", "false", "no", "off"].includes(lowered)) return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function toNumberSafe(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readJsonFile(filePath: string): unknown {
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
||||
}
|
||||
|
||||
function normalizeSemanticTags(rawSteps: unknown): string[] {
|
||||
if (!Array.isArray(rawSteps)) {
|
||||
return [];
|
||||
}
|
||||
const tags = new Set<string>();
|
||||
for (const rawStep of rawSteps) {
|
||||
const step = toRecord(rawStep);
|
||||
if (!step || !Array.isArray(step.semantic_tags)) {
|
||||
continue;
|
||||
}
|
||||
for (const rawTag of step.semantic_tags) {
|
||||
const tag = toStringSafe(rawTag);
|
||||
if (tag) {
|
||||
tags.add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(tags).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function readAgentSemanticSpecSummaryFromFile(sourceSpecFile: string | null | undefined): AgentSemanticSpecSummary | null {
|
||||
const normalizedPath = toStringSafe(sourceSpecFile);
|
||||
if (!normalizedPath || !fs.existsSync(normalizedPath)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = readJsonFile(normalizedPath);
|
||||
const record = toRecord(parsed);
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const scenarioId = toStringSafe(record.scenario_id);
|
||||
if (!scenarioId) {
|
||||
return null;
|
||||
}
|
||||
const semanticTags = normalizeSemanticTags(record.steps);
|
||||
return {
|
||||
scenario_id: scenarioId,
|
||||
domain: toStringSafe(record.domain),
|
||||
title: toStringSafe(record.title),
|
||||
semantic_tags: semanticTags,
|
||||
steps_total: Array.isArray(record.steps) ? record.steps.length : 0
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildInvariantSummary(rawValue: unknown): AgentSemanticInvariantSummary {
|
||||
const record = toRecord(rawValue);
|
||||
return {
|
||||
direct_answer_ok: toBooleanSafe(record?.direct_answer_ok),
|
||||
temporal_honesty_ok: toBooleanSafe(record?.temporal_honesty_ok),
|
||||
selected_object_continuity_ok: toBooleanSafe(record?.selected_object_continuity_ok),
|
||||
truth_gate_ok: toBooleanSafe(record?.truth_gate_ok),
|
||||
human_answer_quality_ok: toBooleanSafe(record?.human_answer_quality_ok),
|
||||
meta_context_integrity_ok: toBooleanSafe(record?.meta_context_integrity_ok)
|
||||
};
|
||||
}
|
||||
|
||||
export function findLatestAgentSemanticAcceptanceSummary(input: {
|
||||
artifactsRootDir: string;
|
||||
repoRootDir: string;
|
||||
scenarioId: string | null | undefined;
|
||||
}): AgentSemanticAcceptanceSummary | null {
|
||||
const scenarioId = toStringSafe(input.scenarioId);
|
||||
if (!scenarioId) {
|
||||
return null;
|
||||
}
|
||||
if (!fs.existsSync(input.artifactsRootDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidates = fs
|
||||
.readdirSync(input.artifactsRootDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && entry.name.startsWith(`${scenarioId}_`))
|
||||
.map((entry) => {
|
||||
const outputDir = path.resolve(input.artifactsRootDir, entry.name);
|
||||
const packStatePath = path.resolve(outputDir, "pack_state.json");
|
||||
if (!fs.existsSync(packStatePath)) {
|
||||
return null;
|
||||
}
|
||||
const stats = fs.statSync(packStatePath);
|
||||
return {
|
||||
outputDir,
|
||||
packStatePath,
|
||||
mtimeMs: stats.mtimeMs
|
||||
};
|
||||
})
|
||||
.filter((item): item is { outputDir: string; packStatePath: string; mtimeMs: number } => item !== null)
|
||||
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
||||
|
||||
const latest = candidates[0];
|
||||
if (!latest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = readJsonFile(latest.packStatePath);
|
||||
const packState = toRecord(parsed);
|
||||
if (!packState) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
scenario_id: scenarioId,
|
||||
output_dir: latest.outputDir,
|
||||
relative_output_dir: path.relative(input.repoRootDir, latest.outputDir).replace(/\\/g, "/"),
|
||||
final_status: toStringSafe(packState.final_status),
|
||||
final_status_reason: toStringSafe(packState.final_status_reason),
|
||||
review_overall_status: toStringSafe(packState.review_overall_status),
|
||||
acceptance_gate_passed: toBooleanSafe(packState.acceptance_gate_passed),
|
||||
critical_path_green: toBooleanSafe(packState.critical_path_green),
|
||||
unresolved_p0_count: toNumberSafe(packState.unresolved_p0_count),
|
||||
unresolved_p1_count: toNumberSafe(packState.unresolved_p1_count),
|
||||
unresolved_p2_count: toNumberSafe(packState.unresolved_p2_count),
|
||||
steps_total: toNumberSafe(packState.steps_total),
|
||||
steps_passed: toNumberSafe(packState.steps_passed),
|
||||
steps_with_warning: toNumberSafe(packState.steps_with_warning),
|
||||
steps_failed: toNumberSafe(packState.steps_failed),
|
||||
updated_at: toStringSafe(packState.updated_at),
|
||||
invariants: buildInvariantSummary(packState.invariants)
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user