wavsummary
A command-line tool that recursively scans a directory for wavelens analyze HTML reports and produces one aggregate summary — how many passed, how many failed, and which primitive(s) failed for each failure — so a directory of 100 individual reports doesn't have to be opened and read one at a time.
Overview
wavsummary runs no analysis itself and reads no WAV files — it reads the verdicts wavelens analyze already computed and rendered into its HTML report, the same way a test-results dashboard reads JUnit XML rather than re-running the tests. Point it at a directory (or a whole tree of subdirectories) and it produces one page: total reports found, how many passed, how many failed, and for each failure, exactly which primitive(s) — Spectral Fidelity, Bit-Exactness, Level Fidelity, Artifact Detection, Distortion, Loudness, and so on — caused it.
It's not a batch-analysis driver. There's no --ref/--dut concept and it never invokes wavelens itself — running the individual analyze calls that produce the reports in the first place is still an external script or CI job's responsibility. wavsummary picks up after that step: point it at wherever those reports landed, recursively, and it does the rest.
Requirements
- A valid, active WaveLens license file (see License below)
- A directory containing one or more
wavelens analyzeHTML reports, at any depth — the scan is always recursive
License
wavsummary requires an active WaveLens license to run, checked before any directory scan begins. There are three ways to point it at your license file, checked in this order — the first one that's set wins.
1. Explicit path with --license
# explicit --license overrides both the env var and the next-to-executable default wavsummary --license /path/to/wavelens.lic --input reports/ -o summary.html
2. WAVELENS_LICENSE_FILE environment variable
# bash / zsh export WAVELENS_LICENSE_FILE=/path/to/wavelens.lic wavsummary --input reports/ -o summary.html
3. Zero configuration — no flag, no env var
Place a file named exactly wavelens.lic in the same folder as the wavsummary executable.
Don't have a license file, or need a renewal? Contact your WaveLens distributor or administrator.
Quick start
Point wavsummary at a directory of wavelens analyze reports, recursively:
wavsummary --input ci_reports/ -o summary.html
# output:
wrote summary.html (100 reports, 92 passed, 8 failed)
Open summary.html — it reads as a member of the same report family wavelens analyze itself produces (same theme, same light/dark support), with a Failed section listing which primitive(s) each failing case tripped, followed by a Passed section, both sorted alphabetically by path for a stable, scannable order.
Command syntax
One flat command — no subcommands, no modes. --input's recursion is the entire reason this tool exists, so there's no --no-recursive/--depth toggle to turn it off.
wavsummary [--license <file.lic>] --input <directory> -o <summary.html> [--json]
| Option | Description |
|---|---|
| --input DIR | Root directory to scan, walked recursively into every subdirectory. |
| -o PATH | Where to write the HTML summary. |
| --json | Optional. Also writes a machine-readable companion alongside -o's output — same basename, .json instead of .html/.htm. See --json schema. |
What counts as a report
A scanned .html/.htm file is treated as a wavelens analyze report if and only if it contains the exact Overall Verdict tile markup analyze itself emits. This marker is unique to analyze reports — a wavelens plot report has its own nav bar (for its Spectrum/Overview tabs) but never an Overall Verdict tile, since a single-file plot has no ref/DUT comparison to verdict on.
Any scanned HTML file without that marker — a plot report, a stray non-wavelens HTML file, or wavsummary's own previously-generated output (see Self-exclusion) — isn't treated as a test case. It's listed separately under Other Files in the summary, informational only, never counted toward the pass/fail totals. The scan never errors out over a file that doesn't match; an unrecognized file is data, not a failure.
The Overall Verdict and each report's failed primitives are both read verbatim from the rendered HTML, never recomputed from the report's own embedded raw JSON. Recomputing PASS/FAIL would mean reimplementing wavelens's own gating logic — which primitives gate the overall verdict and which stay informational-only — a second copy of that logic that could silently drift out of sync if wavelens itself ever changes it. Reading the one verdict wavelens already decided keeps wavsummary a pure aggregator, never a second source of truth.
Summary report layout
Top banner — four tiles: Total Reports, Passed, Failed, and a Suite Verdict tile (PASS only if every report passed and nothing was unreadable, FAIL otherwise) — so the headline doesn't require counting table rows.
Results section — a Failed table (omitted entirely if there are no failures) followed by a Passed table. Within each, rows are sorted alphabetically by relative path, and numbered continuously by a # column that runs across both tables rather than restarting at 1 for Passed.
| # | Test Case | Analysis Mode | Verdict | Failed Primitives |
|---|---|---|---|---|
| 1 | mbcompressor/case_j | Frequency Domain | FAIL | Distortion, Spectral Fidelity |
| 2 | ampsweep_fail_stereo | Frequency Sweep | FAIL | Spectral Fidelity |
| 3 | bitexact_case | Bit-Exact · Frequency Domain | PASS | — |
Test Case is the file's path relative to --input, extension stripped, and links to the original report — the href is computed relative to wherever the summary itself was written, so the link resolves correctly whether -o sits inside the scanned tree or off to the side of it. Analysis Mode mirrors the same KPI tile wavelens itself shows at the top of each report — Bit-Exact/Bit Accuracy (plus the underlying spectral profile as a muted trailing label) when either mode gates or ran, otherwise just the spectral profile (Frequency Domain/Frequency Sweep/Amplitude Sweep). Failed Primitives is — for every passing row, never blank.
Other Files section (only rendered if any exist) — every scanned HTML file that didn't match the analyze-report marker, listed by relative path only, no verdict. A file that couldn't be read at all is listed here too, marked (unreadable).
--json schema
Pass --json to also get a machine-readable companion, written alongside -o's HTML output with the same basename:
{
"scanned_directory": "ci_reports",
"total_reports": 100,
"passed": 92,
"failed": 8,
"reports": [
{
"relative_path": "mbcompressor/case_j.html",
"verdict": "FAIL",
"analysis_mode": "Frequency Domain",
"analysis_mode_sub": "",
"failed_primitives": ["Distortion", "Spectral Fidelity"]
}
],
"other_files": [
{ "relative_path": "plot_demo.html", "reason": "not_analyze_report" },
{ "relative_path": "corrupted.html", "reason": "unreadable" }
]
}
reports covers every file counted in total_reports/passed/failed, in scan order. analysis_mode/analysis_mode_sub mirror the Results table's own Analysis Mode column — empty strings, not omitted fields, when the report's Analysis Mode tile wasn't found. other_files is everything excluded from those counts, each tagged with a reason — "not_analyze_report" for a scanned file that didn't match the detection marker, "unreadable" for one that couldn't be opened at all — so a downstream consumer doesn't have to guess why a file didn't count.
Self-exclusion
The exact path passed to -o is skipped if it's encountered during the scan — relevant whenever -o points inside --input, e.g. regenerating a summary in place:
# -o lands inside the same directory being scanned wavsummary --input reports/ -o reports/summary.html
Without this, a second run would still classify its own prior output correctly as "not an analyze report" (it has neither marker) and list it under Other Files rather than misreport it as a test case — self-exclusion is a cleanliness fix to keep that list free of the tool's own output, not a correctness requirement. It works regardless of whether -o and --input were spelled relative or absolute, or how deep in the recursive walk the match turns up.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Every analyze report found passed, and no file was unreadable. |
| 1 | At least one analyze report failed, or at least one file was unreadable, or --input itself was invalid. |
Lets a CI pipeline gate directly off wavsummary's own exit code, without a caller having to parse HTML or JSON output just to know pass/fail:
wavsummary --input ci_reports/ -o summary.html || echo "suite failed"
Exit code 1 doesn't distinguish "some reports failed" from "a file was unreadable" — if that distinction matters to your pipeline, read --json's own failed count and other_files array instead of relying on the exit code alone.
Common recipes
Summarize a CI output directory and gate the pipeline
wavsummary --input ci_reports/ -o summary.html --json || exit 1
Regenerate a summary in place, inside the tree it scans
wavsummary --input reports/ -o reports/summary.html
Write the summary somewhere outside the scanned tree entirely
wavsummary --input tests/reports/ -o tests/summary.html
Just want the numbers, no HTML to open
wavsummary --input reports/ -o summary.html --json python3 -c "import json; d = json.load(open('summary.json')); print(d['passed'], d['failed'])"
Errors & troubleshooting
| Message | Meaning & fix |
|---|---|
| error: license INVALID -- no license found at '...' | Check whichever of the three lookup mechanisms actually resolved — see License. |
| error: license EXPIRED -- license expired on ... | Contact your administrator for a renewal. |
| error: wavsummary requires --input and -o | Both are required on every invocation. |
| error: '--input' directory '...' doesn't exist or isn't a directory | Hard error — unlike a bad file found mid-scan, a bad --input itself is a misuse of the tool, not data it tolerates and reports around. No output is written. |
| error: unrecognized argument '...' | Check the flag name — wavsummary only accepts --license, --input, -o, --json, and --version. |
| error: failed to write '...' | The output path's parent directory doesn't exist, or isn't writable. |
An unreadable file found during the scan is not an error — it's logged as an Other Files entry with reason unreadable, and the summary still gets written. Only a bad --input itself, or a failure writing the output file, actually aborts the run.
FAQ
Does wavsummary run wavelens analyze for me?
No. It only reads reports that already exist on disk. Producing them in the first place — running analyze once per ref/DUT pair — is still a script or CI job's job, exactly as before wavsummary existed.
What happens to a wavelens plot report sitting in the same directory?
It's scanned, recognized as not an analyze report (no Overall Verdict tile), and listed under Other Files — informational only, never counted toward the pass/fail totals.
Can I scan only a subset of a directory with a filter or glob?
Not currently — every .html/.htm file under --input is scanned, recursively, with no include/exclude pattern. If you need a subset, point --input at a narrower directory, or copy the reports you want summarized into their own folder first.
Zero reports were found — is that an error?
No. A summary is still written (0 total, 0 passed, 0 failed) — that's itself information (wrong directory pointed at, or a directory of only plot reports), not a reason to abort.
Does the summary recompute anything from the reports' raw JSON?
No — every verdict and every failed-primitive label is read verbatim from what wavelens analyze already rendered. See What counts as a report.