Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0b228f3
feat(cli): deprecate and ignore --seed
NikolaosSokos Jun 28, 2026
0add97b
feat(viewer): per-gap query URL helpers
NikolaosSokos Jun 28, 2026
cca5b1b
feat(viewer): record direction + filter predicate
NikolaosSokos Jun 28, 2026
284ccb0
feat(viewer): proportional timeline model with gap boundaries
NikolaosSokos Jun 28, 2026
20b89fa
fix(viewer): parse tz-naive timestamps as UTC in timelineModel
NikolaosSokos Jun 28, 2026
6034b5b
feat(viewer): run + summarise FDSN requests
NikolaosSokos Jun 28, 2026
d657242
feat(viewer): summary/table/detail HTML renderers with graceful degra…
NikolaosSokos Jun 28, 2026
6ab6e95
fix(viewer): render full-window requests + correct attribute escaping
NikolaosSokos Jun 28, 2026
155929b
fix(viewer): escape single quotes in esc() to prevent attribute breakout
NikolaosSokos Jun 28, 2026
7c30bb9
feat(viewer): app shell, DOM glue, sample report
NikolaosSokos Jun 28, 2026
7964ed8
feat(report): add Interactive view link to markdown reports (#50)
NikolaosSokos Jun 28, 2026
7970e73
feat(viewer): sort control (gap/channel/time)
NikolaosSokos Jun 28, 2026
8b8e487
fix(viewer): allowlist http(s) open-link scheme + richer summary (fin…
NikolaosSokos Jun 28, 2026
85d4622
feat(viewer): ASCII timeline + GitHub-style UI + real sample report
NikolaosSokos Jun 28, 2026
0172c31
feat(viewer): summary charts, sortable table, richer detail, report b…
NikolaosSokos Jun 29, 2026
c075ed4
feat(viewer): generate report-browser manifest from the report dir
NikolaosSokos Jun 29, 2026
faecb84
feat(viewer): load reports by URL, file picker, or drag-and-drop
NikolaosSokos Jun 29, 2026
3d1601a
feat(viewer): graphical SVG timeline in the detail view
NikolaosSokos Jun 29, 2026
49dac12
feat(viewer): Oculus latest-reports landing
NikolaosSokos Jun 29, 2026
2399681
feat(viewer): derive dataselect query for reports without dataselect_url
NikolaosSokos Jun 29, 2026
9e4e5e9
feat(viewer): HAS DATA / NO DATA badge on Run results
NikolaosSokos Jun 29, 2026
29f9f26
feat(viewer): scroll to detail on row click + add viewer README
NikolaosSokos Jun 29, 2026
efbbaf7
feat(viewer): add 'How this viewer works' footer link to the README
NikolaosSokos Jun 29, 2026
8a8f005
chore(viewer): drop shipped README + footer link
NikolaosSokos Jun 29, 2026
271c465
feat(cli)!: remove seed mechanism entirely
NikolaosSokos Jul 7, 2026
d905911
feat(cli): add rerun command to re-verify a report's inconsistencies
NikolaosSokos Jul 7, 2026
7cfab44
feat(cli): point 'explore --days 0' users at the 'rerun' command
NikolaosSokos Jul 7, 2026
265e4a7
fix(cli): don't print rerun verdicts twice
NikolaosSokos Jul 7, 2026
541862c
feat(viewer): make inconsistencies obvious in words
NikolaosSokos Jul 8, 2026
40f315f
feat(viewer): drop the always-10m 'Max gap' column
NikolaosSokos Jul 8, 2026
6871d28
feat(viewer): align direction filter labels with the Disagreement column
NikolaosSokos Jul 9, 2026
c8a566d
feat(psd): add PSD coverage CSV parsing and day-coverage helper
NikolaosSokos Jul 15, 2026
9c30289
feat(psd): add psd_coverage() with padded query and status classifica…
NikolaosSokos Jul 15, 2026
5e8291d
feat(psd): add classify_psd (dataselect vs PSD, day granularity)
NikolaosSokos Jul 15, 2026
06db395
feat(psd): compute PSD triangle per window in runner (check_psd flag)
NikolaosSokos Jul 15, 2026
c15778f
feat(psd): add --psd/--no-psd flag to consistency command
NikolaosSokos Jul 16, 2026
5c4e61c
feat(psd): add filled/hollow triangle triad helper to report
NikolaosSokos Jul 16, 2026
d597eee
feat(psd): add optional three-lane PSD timeline to render_timeline
NikolaosSokos Jul 16, 2026
4cb817f
feat(psd): add PSD summary counters to report
NikolaosSokos Jul 16, 2026
3330c35
feat(psd): surface data-but-no-PSD findings in the Markdown report
NikolaosSokos Jul 16, 2026
9ffc3ed
test(psd): assert PSD fields present in serialized report
NikolaosSokos Jul 16, 2026
06498b9
fix(psd): treat unknown PSD failures as Skipped and keep full URL on …
NikolaosSokos Jul 16, 2026
cbab1be
feat(psd): verbose, self-explanatory PSD report section
NikolaosSokos Jul 16, 2026
b34b57a
feat(psd): extend PSD triangle to check, explore, and compare
NikolaosSokos Jul 16, 2026
5dec3c6
docs(psd): fix _psd_bucket docstring (no-data is its own bucket)
NikolaosSokos Jul 16, 2026
b0edaa9
feat(viewer): render the Availability/Dataselect/PSD triangle
NikolaosSokos Jul 17, 2026
a2f526d
refactor(viewer): stable psdVerdict kind + byte-exact summary for pre…
NikolaosSokos Jul 17, 2026
6813593
feat(viewer): PSD legend + PSD verdict filter
NikolaosSokos Jul 17, 2026
fd8ba29
feat(psd): add psd_scores helper (compliance + coverage, N/A-aware)
NikolaosSokos Jul 20, 2026
bc6604c
feat(psd): expose PSD compliance/coverage scores in report summary
NikolaosSokos Jul 20, 2026
f94ce0f
feat(psd): show PSD scores and network/service note in the report
NikolaosSokos Jul 20, 2026
54753bf
polish(psd): bold PSD score labels, assert denominators, dedupe bucke…
NikolaosSokos Jul 21, 2026
9594b24
feat(viewer): minimal PSD dashboard (A/D + PSD scores, per-day timeli…
NikolaosSokos Jul 21, 2026
e64fd1e
Merge branch 'fix/52-remove-seeds' into integration/psd-seeds-viewer
NikolaosSokos Jul 21, 2026
5cb8093
Merge branch 'worktree-issue-50-report-viewer' into integration/psd-s…
NikolaosSokos Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,5 @@ __marimo__/

# Scratch directory
scratch/
viewer/index.json
viewer/reports
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Changelog

## Unreleased

### Added — `rerun` command — #55

Re-verify the inconsistencies of a report against the live services, without the
boundary walk or dmtri output that `explore` produces:

```
eida-consistency rerun [REPORT] [-i N ...] [--all] [--json] [--verbose]
```

Defaults to the latest report and its inconsistent rows; `--all` re-checks every
row. Reports a verdict per row — `PERSISTS` / `RESOLVED` / `SKIPPED`, plus
`CONSISTENT` / `REGRESSED` under `--all`. Read-only (`--json` for machine
output). The report loader and window-check core are now shared with `explore`
(`reverify.py`); `explore`'s behavior is unchanged.

### Removed — seeds (breaking) — #52

The seed mechanism is removed entirely. A seed could never reliably reproduce a
run: the node's live inventory drifts over time, so the same seed selects
different channels later. Reproduce a finding instead by replaying its exact
window with `explore` or `check` (deterministic).

- `--seed` is removed from the `consistency` command. Passing it now errors
(`No such option: --seed`) instead of warning.
- `run_consistency_check()` no longer accepts a `seed` argument.
- Reports no longer carry `summary.seed`. Use `summary.timestamp` as the audit
anchor.
- Report filenames change the trailing seed slot to microseconds:
`{node}_{YYYYMMDD}_{HHMMSS}_{ffffff}.{ext}`. The 4-part underscore shape and a
numeric final field are preserved, so filename parsers keep working. The
`.json` and `.md` of one run still share a stem (both derived from the report
timestamp).

Old reports that still contain a `seed` field continue to load everywhere;
`compare`, `explore`, and the viewer never read it, and the multi-report
markdown regenerator shows the legacy seed only when present.

> **Note:** downstream consumers (Oculus crawler, dmtri) may parse `summary.seed`
> or the filename's seed field. Confirm with the Oculus admins before releasing.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Options:
- `--node`: Node code (e.g. `RESIF`, `NOA`, `ETH`)
- `--epochs`: Number of random test epochs (default: 10) OR percentage (e.g., `"5%"`, `0.05`)
- `--duration`: Epoch length in seconds (≥600)
- `--seed`: Random seed for sampling. Does **not** reproduce an older run — to re-verify a finding see [Re-run & Re-verify](#-re-run--re-verify)
- `--psd` / `--no-psd`: Also check PSD (`eidaws/psd`) coverage vs dataselect (default: on)
- `--delete-old`: Keep only the most recent report
- `--stdout`: Print JSON report to stdout
- `--report-dir`: Save reports to a custom folder (default: `reports/`); works before or after the subcommand
Expand Down Expand Up @@ -106,8 +106,8 @@ uvx eida-consistency list-nodes

Reports are stored in `./reports/` by default, or in a custom folder using `--report-dir`.

- JSON reports: `reports/<node>_<YYYYMMDD_HHMMSS>_<seed>.json`
- Markdown reports: `reports/<node>_<YYYYMMDD_HHMMSS>_<seed>.md`
- JSON reports: `reports/<node>_<YYYYMMDD>_<HHMMSS>_<microseconds>.json`
- Markdown reports: `reports/<node>_<YYYYMMDD>_<HHMMSS>_<microseconds>.md`
- Global summary: [`summary.md`](https://github.com/EIDA/eida-consistency/blob/main/reports/summary.md)

---
Expand Down Expand Up @@ -209,11 +209,12 @@ There are three distinct ways to "run it again", depending on what you want:
# 1. Run a check (reports land in reports/test_noa/)
uvx eida-consistency consistency --node NOA --epochs 20 --report-dir reports/test_noa

# 2. Re-check the inconsistencies it found
# 2. Re-check the inconsistencies it found (day-by-day boundaries)
uvx eida-consistency explore --report-dir reports/test_noa

# 3. Fix them at node level, then re-verify by replaying the same report
uvx eida-consistency explore reports/test_noa/<report>.json
# 3. Fix them at node level, then re-verify by replaying the exact windows
# (verdict per row: PERSISTS / RESOLVED / SKIPPED; add --all for CONSISTENT/REGRESSED)
uvx eida-consistency rerun reports/test_noa/<report>.json

# 4. Compare before/after
uvx eida-consistency compare reports/test_noa/old.json reports/test_noa/new.json
Expand Down
37 changes: 29 additions & 8 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ eida-consistency consistency --node NOA --epochs 10 --duration 600
* `--node TEXT`: EIDA node code (e.g., NOA, RESIF). [Required]
* `--epochs INTEGER`: Number of random time to check. [Default: 10]
* `--duration INTEGER`: Duration of each check in seconds (>= 600). [Default: 600]
* `--seed INTEGER`: Random seed for the candidate sampling. It does **not**
reliably reproduce an older run (a seed only selects the same streams while
the node's inventory is unchanged); to re-verify a past finding use
`explore`/`check`. See [Re-run & Re-verify](#re-run-re-verify).
* `--psd / --no-psd`: Also check PSD (`eidaws/psd`) coverage vs dataselect. [Default: on]
* `--report-dir PATH`: Directory to store reports. Accepted before the
subcommand (`eida-consistency --report-dir DIR consistency …`) or after it
(`eida-consistency consistency … --report-dir DIR`). [Default: `reports/`]
Expand Down Expand Up @@ -62,17 +59,41 @@ eida-consistency explore reports/noa_20260621_140111_113496.json --index 0 --ind
* `--json`: Emit discovered fixes as JSON on stdout (logs stay on stderr).
* `--report-dir PATH`: Directory to load reports from (same placement rules as above).

### rerun

Re-verify the inconsistencies of a report against the live services — no
boundary walk, no dmtri commands. Reports a verdict per row: `PERSISTS` (still
inconsistent), `RESOLVED` (now consistent), `SKIPPED` (transient dataselect
failure), plus `CONSISTENT` / `REGRESSED` when `--all` re-checks
previously-consistent rows.

```bash
eida-consistency rerun reports/noa_latest.json # all inconsistent rows
eida-consistency rerun -i 15 reports/noa_latest.json # one specific row
eida-consistency rerun --all reports/noa_latest.json # every row
eida-consistency rerun --json reports/noa_latest.json # machine-readable stdout
```

**Options:**

* `REPORT`: Report `.json` path or URL. Omitted → latest report in the report dir.
* `--index, -i INTEGER`: Result index to re-run (repeatable; overrides scope).
* `--all`: Re-verify every row, not just the inconsistent ones.
* `--json`: Emit verdicts as JSON to stdout (logs stay on stderr).
* `--verbose`: Print query URLs while re-running.

## Re-run & Re-verify

There are three ways to "run it again", depending on intent:

* **Re-verify a report's findings** — replay each recorded inconsistency's exact
window: `eida-consistency explore <report.json>` (only inconsistencies; use
`--index` to target specific ones).
window: `eida-consistency rerun <report.json>` (plain re-check), or
`eida-consistency explore <report.json>` (drills day-by-day to the boundary).
Use `--index` to target specific findings.
* **Re-check a single stream/window** — `eida-consistency check --node … --net …
--sta … --cha … --start … --end …`.
* **Fresh sampled run** — `eida-consistency consistency --node …`. Note that
`--seed` does not reproduce an older run once the node's inventory changes.
* **Fresh sampled run** — `eida-consistency consistency --node …` (samples new
windows; does not reproduce a specific past finding).

### list-nodes

Expand Down
95 changes: 89 additions & 6 deletions src/eida_consistency/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ def cli(ctx, log_level, report_dir):
@click.option("--node", help="EIDA node code (e.g., RESIF, NOA)")
@click.option("--epochs", type=str, default="10", show_default=True, help="Number of epochs or percentage (e.g. '10', '0.05', '5%')")
@click.option("--duration", type=int, default=600, show_default=True, help="Duration (s), must be >= 600")
@click.option("--seed", type=int, help="Random seed")
@click.option(
"--psd/--no-psd", "check_psd", default=True, show_default=True,
help="Also check PSD (eidaws/psd) coverage vs dataselect.",
)
@click.option(
"--delete-old",
is_flag=True,
Expand All @@ -102,7 +105,7 @@ def cli(ctx, log_level, report_dir):
help="Directory to store reports (overrides the global --report-dir).",
)
@click.pass_context
def consistency(ctx, node, epochs, duration, seed, delete_old, print_stdout, upload, report_dir_opt):
def consistency(ctx, node, epochs, duration, check_psd, delete_old, print_stdout, upload, report_dir_opt):
"""Run availability + dataselect consistency check, or housekeeping with --delete-old."""
report_dir: Path = report_dir_opt or ctx.obj["report_dir"]

Expand All @@ -122,7 +125,7 @@ def consistency(ctx, node, epochs, duration, seed, delete_old, print_stdout, upl
node=node,
epochs=epochs,
duration=duration,
seed=seed,
check_psd=check_psd,
print_stdout=print_stdout,
report_dir=report_dir,
)
Expand Down Expand Up @@ -187,16 +190,46 @@ def check(ctx, node, net, sta, cha, loc, start, end):
# "is the whole window covered by one span?" boolean — that read as "NO" even
# when availability returned data, contradicting the timeline below.
avail_has_data = bool(av_res.get("spans"))

# 3. Check PSD (Availability / Dataselect / PSD triangle; dataselect = truth)
from eida_consistency.services.psd import psd_coverage
from eida_consistency.core.consistency import classify_psd
from eida_consistency.report.report import triad

logging.info(f"Checking PSD for {net}.{sta}.{loc}.{cha}...")
psd_res = psd_coverage(base_url, net, sta, cha, start, end, loc)
psd_cls = classify_psd(ds_res, psd_res, (start, end))
psd_present = psd_cls["p_present"]

logging.info(f"\nResults for {net}.{sta}.{loc}.{cha} ({start} -> {end}):")
logging.info(f" Availability: {'data present' if avail_has_data else 'no data'} (HTTP {av_res['status']})")
logging.info(f" Dataselect: {'data present' if ds_success else 'no data'} (status {ds_res['status']})")
logging.info(f" PSD: {'data present' if psd_present else 'no data'} (status {psd_res['status']})")

if classification["consistent"] is True:
logging.info(" Summary: CONSISTENT")
logging.info(" Summary (Avail vs Data): CONSISTENT")
elif classification["consistent"] is False:
logging.info(" Summary: INCONSISTENT")
logging.info(" Summary (Avail vs Data): INCONSISTENT")
else:
logging.info(f" Summary: SKIPPED ({classification['reason']})")
logging.info(f" Summary (Avail vs Data): SKIPPED ({classification['reason']})")

# PSD verdict (Dataselect vs PSD), gated by the 2024-01-01 obligation.
if psd_res["status"] == "Unsupported":
logging.info(" Summary (Data vs PSD): UNSUPPORTED (node has no PSD coverage service)")
elif psd_cls["status"] == "Skipped":
logging.info(" Summary (Data vs PSD): SKIPPED (transient PSD failure)")
elif psd_cls["consistent"] is False:
if psd_cls["psd_required"]:
logging.info(" Summary (Data vs PSD): VIOLATION (data on/after 2024-01-01 but no PSD)")
else:
logging.info(" Summary (Data vs PSD): pre-2024 gap (data but no PSD; PSD not required, informational)")
else:
logging.info(" Summary (Data vs PSD): CONSISTENT")

logging.info(
f" Triangle: {triad(avail_has_data, ds_success, psd_present)} "
f"(▼ Avail ▲ Data ▶ PSD; filled=present, hollow=absent)"
)

from eida_consistency.report.report import render_timeline, render_gap_table

Expand Down Expand Up @@ -256,6 +289,12 @@ def explore(ctx, report, index, days, verbose, as_json, report_dir_opt):
except ValueError:
raise click.UsageError(f"No report files found in {report_dir}")

if days == 0:
logging.info(
"Note: --days 0 only re-verifies each finding's window without "
"exploring boundaries. For a plain re-check, use 'rerun' instead."
)

indices = list(index) if index else None
result = explore_boundaries(report, indices, max_days=days, verbose=verbose)
if as_json:
Expand All @@ -264,6 +303,50 @@ def explore(ctx, report, index, days, verbose, as_json, report_dir_opt):
click.echo(json.dumps(result, indent=2, default=str))


# ----------------------------------------------------------------------
# rerun command
# ----------------------------------------------------------------------
@cli.command()
@click.argument("report", required=False, type=str)
@click.option(
"--index", "-i",
multiple=True, type=int,
help="Indices of results to re-run (overrides scope).",
)
@click.option(
"--all", "all_rows", is_flag=True,
help="Re-verify every row, not just the inconsistent ones.",
)
@click.option("--verbose", is_flag=True, help="Print query URLs while re-running.")
@click.option(
"--json", "as_json", is_flag=True,
help="Emit verdicts as JSON to stdout (logs/progress stay on stderr).",
)
@click.pass_context
def rerun(ctx, report, index, all_rows, verbose, as_json):
"""Re-verify a report's inconsistencies (no boundary walk, no dmtri)."""
from eida_consistency.rerun import rerun_report, render_summary

report_dir: Path = ctx.obj["report_dir"]
if not report:
try:
report = max(report_dir.glob("*.json"), key=lambda p: p.stat().st_mtime)
logging.info(f"Using latest report: {report}")
except ValueError:
raise click.UsageError(f"No report files found in {report_dir}")

indices = list(index) if index else None
result = rerun_report(report, indices, all_rows=all_rows, verbose=verbose)

if as_json:
click.echo(json.dumps(result, indent=2, default=str))
return

# Per-row verdicts already streamed from rerun_report as each finding was
# re-checked; just close with the tally (no duplicate table).
logging.info(f"Summary: {render_summary(result)}")


# ----------------------------------------------------------------------
# reload-nodes command
# ----------------------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions src/eida_consistency/core/consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from eida_consistency.core.coverage import (
parse_iso, tolerance_seconds, clip_intervals, mismatch_intervals_directional,
)
from eida_consistency.utils.constants import PSD_REQUIRED_SINCE


def is_transient_dataselect_failure(success: bool, status: str | None) -> bool:
Expand Down Expand Up @@ -91,3 +92,30 @@ def classify_consistency(spans, ds_result: Dict[str, Any], window) -> Dict[str,
"dataselect": [[s.isoformat(), e.isoformat()] for s, e in d],
},
}


def classify_psd(ds_result: Dict[str, Any], psd_result: Dict[str, Any], window) -> Dict[str, Any]:
"""Compare dataselect (ground truth) vs PSD day-coverage for `window`."""
w1 = parse_iso(window[1])
required = bool(w1 and w1 >= parse_iso(PSD_REQUIRED_SINCE))

base = {"applicable": True, "psd_required": required,
"d_present": bool(ds_result.get("segments")),
"p_present": bool(psd_result.get("day_covered"))}

if psd_result.get("status") == "Unsupported":
return {**base, "status": "Unsupported", "scoreable": False, "consistent": None}

psd_transient = is_transient_dataselect_failure(
psd_result.get("success", False), psd_result.get("status"))
ds_transient = is_transient_dataselect_failure(
ds_result.get("success", False), ds_result.get("status"))
if psd_transient or ds_transient:
return {**base, "status": "Skipped", "scoreable": False, "consistent": None}

if not psd_result.get("success"):
return {**base, "status": "Skipped", "scoreable": False, "consistent": None}

consistent = not (base["d_present"] and not base["p_present"])
return {**base, "scoreable": True, "consistent": consistent,
"status": "Consistent" if consistent else "Inconsistent"}
41 changes: 21 additions & 20 deletions src/eida_consistency/create_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ def build_markdown(reports: List[Tuple[Path, Dict[str, Any]]]) -> str:
# --- Global table ---
lines.append("## Per-node summary")
lines.append("")
lines.append("| Node | Epochs Requested | Epochs Usable | Total Checks | Skipped | Consistent | Inconsistent | Score |")
lines.append("|------|------------------|---------------|--------------|---------|------------|--------------|-------|")
lines.append("| Node | Epochs Requested | Epochs Usable | Total Checks | Skipped | Consistent | Inconsistent | Score |")
lines.append("|------|------------------|---------------|--------------|---------|------------|--------------|-------|")

for path, rep in reports:
s = rep.get("summary", {})
node = s.get("node", "?")
requested = s.get("candidates_requested", s.get("epochs", "?"))
usable = s.get("candidates_generated", s.get("epochs", "?"))
total_checked = s.get("total_checked", 0)
total_skipped = s.get("total_skipped", 0)
total_consistent = s.get("total_consistent", 0)
total_inconsistent = s.get("total_inconsistent", 0)
score = s.get("score", 0.0)

lines.append(
f"| {node} | {requested} | {usable} | "
f"{total_checked} | {total_skipped} | {total_consistent} | "
f"{total_inconsistent} | {score} % |"
)
usable = s.get("candidates_generated", s.get("epochs", "?"))
total_checked = s.get("total_checked", 0)
total_skipped = s.get("total_skipped", 0)
total_consistent = s.get("total_consistent", 0)
total_inconsistent = s.get("total_inconsistent", 0)
score = s.get("score", 0.0)
lines.append(
f"| {node} | {requested} | {usable} | "
f"{total_checked} | {total_skipped} | {total_consistent} | "
f"{total_inconsistent} | {score} % |"
)

lines.append("")
lines.append("---")
Expand All @@ -63,15 +63,16 @@ def build_markdown(reports: List[Tuple[Path, Dict[str, Any]]]) -> str:
node = s.get("node", "?")
lines.append(f"## Node: {node}")
lines.append("")
lines.append(f"- Seed: `{s.get('seed', '?')}`")
if "seed" in s: # legacy reports only; the seed mechanism was removed
lines.append(f"- Seed: `{s['seed']}`")
lines.append(f"- Epochs requested: `{s.get('candidates_requested', s.get('epochs', '?'))}`")
lines.append(f"- Epochs usable: `{s.get('candidates_generated', s.get('epochs', '?'))}`")
lines.append(f"- Candidate pool: `{s.get('candidates_pool', '?')}`")
lines.append(f"- Queries performed: `{s.get('queries_performed', '?')}`")
lines.append(f"- Total checks: `{s.get('total_checked', 0)}`")
lines.append(f"- Skipped: `{s.get('total_skipped', 0)}`")
lines.append(f"- Consistent: `{s.get('total_consistent', 0)}`")
lines.append(f"- Inconsistent: `{s.get('total_inconsistent', 0)}`")
lines.append(f"- Queries performed: `{s.get('queries_performed', '?')}`")
lines.append(f"- Total checks: `{s.get('total_checked', 0)}`")
lines.append(f"- Skipped: `{s.get('total_skipped', 0)}`")
lines.append(f"- Consistent: `{s.get('total_consistent', 0)}`")
lines.append(f"- Inconsistent: `{s.get('total_inconsistent', 0)}`")
lines.append(f"- Score: **{s.get('score', 0.0)} %**")
lines.append("")

Expand Down
Loading
Loading