feat(analyze): time an event at the step it was detected and report the quartiles

an obligation that never discharges is reported when the run ends, and timing it by the step that armed it recorded a liveness failure flushed at the budget as a violation found on step 1. the survival analysis now measures the detected step, falling back to the origin for campaigns written before the field existed, and says how many events that moved. the report gains the first and third quartiles beside the median.
This commit is contained in:
pj committed 2026-08-16 17:45:20 +05:30
1 parent a28178337c
commit b99da0be0e
9 files changed
+354 -26

No files matched your search

+9 -1
View File
@@ -14,7 +14,7 @@ import (
func writeReport(result analysis, out io.Writer) {
fmt.Fprintf(out, "primary outcome: %s\n\n", result.Outcome)
writeTable(out, []string{"arm", "runs", "violated", "censored", "excluded", "missing", "median steps", "violation rate"},
writeTable(out, []string{"arm", "runs", "violated", "censored", "excluded", "missing", "median steps", "iqr steps", "violation rate"},
func(add func(...string)) {
for _, summary := range result.Arms {
add(
@@ -25,6 +25,7 @@ func writeReport(result analysis, out io.Writer) {
strconv.Itoa(summary.Excluded),
strconv.Itoa(len(summary.MissingSeeds)),
formatMedian(summary.MedianStepsToFirstViolation),
formatMedian(summary.FirstQuartileSteps)+" to "+formatMedian(summary.ThirdQuartileSteps),
formatRatio(summary.ViolationRate, 3),
)
}
@@ -68,6 +69,13 @@ func writeReport(result analysis, out io.Writer) {
summary.Arm, summary.EventsHeldAtBudget, summary.StepBudget)
}
}
for _, summary := range result.Arms {
if summary.EventsDetectedAfterOrigin > 0 {
fmt.Fprintf(out, "\n%s timed %d violation(s) at the step they were detected rather than the step that armed them, "+
"which is what an obligation reported only when the run ended looks like",
summary.Arm, summary.EventsDetectedAfterOrigin)
}
}
if len(result.Arms) > 0 {
fmt.Fprintln(out)
}