mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 15:04:37 +01:00
F: cost_known: a trial whose harness captured no billing says so; totals never hide it
cost_usd stays a finite number. Trial and eval records gain an optional cost_known boolean (absent means known, so old records still parse; the validator accepts only a boolean). Session-ledger rows record whether the session's terminal result carried total_cost_usd; PTY and Codex sessions never do, and SDK captures without a terminal result do not. A trial is cost-known only when it has eval records and every record and session captured billing, so JUnit-only PTY cases (125 of 298 trials in run 37198445662 recorded 0) now read cost unknown. The census headline prints the known sum plus the unknown count, never a bare total over unknown costs, and no total when every cost is unknown; the history import keeps the flag. PTY transcripts are not priced: test/helpers/pricing.ts estimates exclude cache writes, so they cannot give the billed cost.
This commit is contained in:
1 parent
6d6c7726ec
commit
9b64b900c0
11 files changed
+136
-14
No files matched your search
@@ -265,7 +265,7 @@ export function backfillEvalFiles(files: string[], run?: { run_id: string; sha?:
|
||||
tier: caseTier(id, registry), kind: registry.kinds[id]!, trial: 1, panel: { n: 1, k: 1 }, attempt: 1,
|
||||
outcome, ...(outcome === 'failed' ? { failure_class: failureClassOf(entry) } : {}),
|
||||
exit_reason: entry.exit_reason, error: sanitizeTrialError(entry.error),
|
||||
duration_ms: Math.max(0, entry.duration_ms || 0), cost_usd: Math.max(0, entry.cost_usd || 0),
|
||||
duration_ms: Math.max(0, entry.duration_ms || 0), cost_usd: Math.max(0, entry.cost_usd || 0), ...(entry.cost_known === false ? { cost_known: false } : {}),
|
||||
model: entry.model, cli_version: result.claude_cli_version, policy_version: 0, quarantined: false,
|
||||
execution: entry.execution === 'reused' ? 'reused' : 'executed', source: 'backfill',
|
||||
run_id: run?.run_id ?? `local:${file}`, sha: run?.sha ?? result.git_sha, recorded_at: run?.timestamp ?? result.timestamp,
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
getProjectEvalDir, getClaudeCliVersion, isFinalizedEvalResultFile, evalEntryOutcome, failureClassOf, panelVerdict,
|
||||
sanitizeTrialError, formatTrialOutcomes, CONTRACT_VIOLATIONS_FILE, TRIAL_ENV, TRIAL_OUTCOME_SCHEMA, TRIAL_OUTCOMES_FILE,
|
||||
type EvalCaseKind, type PanelShape, type PanelVerdict, type TrialFailureClass, type TrialOutcome, type TrialOutcomeRecord,
|
||||
trialFailureFields, trialSessions, type TrialFailureDetail,
|
||||
trialCostKnown, trialFailureFields, trialSessions, type TrialFailureDetail,
|
||||
} from '../../test/helpers/eval-store';
|
||||
import { readSessionLedger, type SessionLedgerRow } from '../../test/helpers/session-ledger';
|
||||
import { publishedFence, sanitizePublishedText } from './published-text';
|
||||
@@ -319,7 +319,7 @@ export function junitCensus(manifest: PaidRunManifest, artifacts: ReportArtifact
|
||||
census.history.push({ ...base, case: id, file: shardFile(key), kind, trial: 1, panel: { n: 1, k: 1 }, outcome: caseOutcome,
|
||||
...(failureClass ? { failure_class: failureClass } : {}), ...(failedRecord?.exit_reason ? { exit_reason: String(failedRecord.exit_reason) } : {}),
|
||||
...(error && failed ? { error } : {}), ...diagnosis, ...trialSessions(sessions), duration_ms: cases.reduce((sum, tc) => sum + tc.timeMs, 0),
|
||||
cost_usd: Math.round(mine.reduce((sum: number, r: any) => sum + (Number(r.cost_usd) || 0), 0) * 100) / 100,
|
||||
cost_usd: Math.round(mine.reduce((sum: number, r: any) => sum + (Number(r.cost_usd) || 0), 0) * 100) / 100, ...trialCostKnown(mine, sessions),
|
||||
...(typeof mine[0]?.model === 'string' ? { model: mine[0].model } : {}), ...(cliVersion ? { cli_version: cliVersion } : {}),
|
||||
quarantined: false, execution: outcome.reused ? 'reused' : 'executed', source: 'junit' } as TrialOutcomeRecord);
|
||||
}
|
||||
@@ -482,7 +482,13 @@ export interface ReportHeadline {
|
||||
redispatchEligible: boolean;
|
||||
}
|
||||
|
||||
export function formatHeadline(h: ReportHeadline): string[] {
|
||||
/** Known cost, never a bare total when any trial's cost is unknown, and no total when none is known. */
|
||||
export function formatCost(knownUsd: number, unknownTrials: number, trials: number): string {
|
||||
if (trials > 0 && unknownTrials === trials) return `cost unknown (no billing captured for any of ${trials} trial(s))`;
|
||||
return `cost $${knownUsd.toFixed(2)}${unknownTrials ? ` known + ${unknownTrials} of ${trials} trial(s) cost unknown` : ''}`;
|
||||
}
|
||||
|
||||
export function formatHeadline(h: ReportHeadline, cost: { unknownTrials: number; trials: number } = { unknownTrials: 0, trials: 0 }): string[] {
|
||||
const c = h.counts;
|
||||
const minutes = h.wallMs === null ? 'unknown' : `${Math.floor(h.wallMs / 60_000)}m${String(Math.round((h.wallMs % 60_000) / 1000)).padStart(2, '0')}s`;
|
||||
return [
|
||||
@@ -490,7 +496,7 @@ export function formatHeadline(h: ReportHeadline): string[] {
|
||||
` rule ${c.rule.passed}/${c.rule.total} · behavior ${c.behavior.passed}/${c.behavior.total}${c.behavior.split ? ` (${c.behavior.split} split)` : ''}`
|
||||
+ ` · judge ${c.judge.passed}/${c.judge.total} · quarantined ${c.quarantined.total} (${c.quarantined.failingLane} failing the lane)`,
|
||||
` SKIPPED ${c.skipped} · INFRA ${c.infra} · INCOMPLETE ${c.incomplete} · unattributed ${c.unattributed} · ACTION REQUIRED ${h.actionRequired}`,
|
||||
` wall ${minutes} · cost $${h.costUsd.toFixed(2)}${h.redispatchEligible ? ' · every red is machine-classified INFRA/INCOMPLETE: eligible for ONE re-dispatch as a new run (EVAL_POLICY.infraRedispatch); report both runs' : ''}`,
|
||||
` wall ${minutes} · ${formatCost(h.costUsd, cost.unknownTrials, cost.trials)}${h.redispatchEligible ? ' · every red is machine-classified INFRA/INCOMPLETE: eligible for ONE re-dispatch as a new run (EVAL_POLICY.infraRedispatch); report both runs' : ''}`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -675,7 +681,7 @@ export function runPaidReport(reportDir: string, options: { writeDurations?: boo
|
||||
...(t.outcome === 'failed' && t.failure_cause ? { failure_cause: t.failure_cause } : {}),
|
||||
...(t.outcome === 'failed' && t.failure_cause_evidence ? { failure_cause_evidence: t.failure_cause_evidence } : {}),
|
||||
...(t.outcome === 'failed' && t.failure_detail ? { failure_detail: t.failure_detail } : {}), ...(t.sessions ? { sessions: t.sessions } : {}),
|
||||
duration_ms: t.duration_ms, cost_usd: t.cost_usd, ...(t.model ? { model: t.model } : {}),
|
||||
duration_ms: t.duration_ms, cost_usd: t.cost_usd, ...(t.cost_known === false ? { cost_known: false } : {}), ...(t.model ? { model: t.model } : {}),
|
||||
...(outcome.reused ? { input_identity: outcome.reused.inputKey } : {}),
|
||||
quarantined: t.quarantined, execution: outcome.reused ? 'reused' : 'executed', source: 'shard' } as TrialOutcomeRecord);
|
||||
}
|
||||
@@ -742,7 +748,8 @@ export function runPaidReport(reportDir: string, options: { writeDurations?: boo
|
||||
costUsd: Math.round(costUsd * 100) / 100,
|
||||
redispatchEligible: red && infraOnly(verdict.problems),
|
||||
};
|
||||
const headlineLines = formatHeadline(headline);
|
||||
const executed = history.filter(r => r.attempt === primary && r.execution === 'executed' && r.outcome !== 'skipped');
|
||||
const headlineLines = formatHeadline(headline, { unknownTrials: executed.filter(r => r.cost_known === false).length, trials: executed.length });
|
||||
for (const line of headlineLines) console.log(line);
|
||||
if (failureLines.length) {
|
||||
console.log('[test:paid] failures and split verdicts:');
|
||||
|
||||
@@ -83,7 +83,7 @@ import {
|
||||
getProjectEvalDir, getClaudeCliVersion, isFinalizedEvalResultFile, evalEntryOutcome, failureClassOf, panelVerdict,
|
||||
sanitizeTrialError, formatTrialOutcomes, CONTRACT_VIOLATIONS_FILE, TRIAL_ENV, TRIAL_OUTCOME_SCHEMA, TRIAL_OUTCOMES_FILE,
|
||||
type EvalCaseKind, type PanelShape, type PanelVerdict, type TrialFailureClass, type TrialOutcome, type TrialOutcomeRecord,
|
||||
trialFailureFields, trialSessions, type TrialFailureCause, type TrialFailureDetail, type TrialSessionSummary,
|
||||
trialCostKnown, trialFailureFields, trialSessions, type TrialFailureCause, type TrialFailureDetail, type TrialSessionSummary,
|
||||
} from '../test/helpers/eval-store';
|
||||
import { readSessionLedger, type SessionLedgerRow } from '../test/helpers/session-ledger';
|
||||
import { E2E_KINDS } from '../test/helpers/touchfiles-data';
|
||||
@@ -726,6 +726,7 @@ export interface ShardTrialRecord {
|
||||
failure_cause_evidence?: string;
|
||||
failure_detail?: TrialFailureDetail;
|
||||
sessions?: TrialSessionSummary[];
|
||||
cost_known?: false;
|
||||
}
|
||||
|
||||
type TrialEvidence = { records: any[]; contract: string | null; sessions?: SessionLedgerRow[] };
|
||||
@@ -759,6 +760,7 @@ export function classifyTrialShard(
|
||||
cost_usd: Math.round(evidence.records.reduce((sum, record) => sum + (Number(record.cost_usd) || 0), 0) * 100) / 100,
|
||||
duration_ms: outcome.elapsedMs,
|
||||
...(typeof failedRecord?.model === 'string' ? { model: failedRecord.model } : {}), ...trialSessions(evidence.sessions ?? []),
|
||||
...trialCostKnown(evidence.records, evidence.sessions),
|
||||
};
|
||||
const failed = (failureClass: TrialFailureClass, error?: string): ShardTrialRecord => {
|
||||
const cls = evidence.contract !== null ? 'contract' : failureClass;
|
||||
|
||||
Reference in new issue
Block a user