Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 docs/adoption-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Goal: make FastaGuard visible in standard pipeline reports.
- Continue emitting `fastaguard_mqc.json` custom content.
- Develop `integrations/multiqc/` into a packaged MultiQC plugin.
- Test the plugin against multiple sample reports.
- Keep v0.5 gate, readiness, and submission fields visible in the native
MultiQC summary table.
- Decide whether to submit upstream to MultiQC once public adoption begins.

Done when:
Expand Down
2 changes: 1 addition & 1 deletion docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,4 @@ cd ../../examples/reports
multiqc .
```

This is intentionally compact: it parses `fastaguard_mqc.json`, adds key metrics to MultiQC general stats, and adds a FastaGuard summary section. The full evidence remains in FastaGuard's own HTML and JSON reports.
This is intentionally compact: it parses `fastaguard_mqc.json`, adds key metrics to MultiQC general stats, and adds a FastaGuard summary section with gate, readiness, and submission-readiness fields. The full evidence remains in FastaGuard's own HTML and JSON reports.
10 changes: 7 additions & 3 deletions integrations/multiqc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
This directory contains a dedicated MultiQC plugin starter for FastaGuard.

FastaGuard already emits MultiQC custom-content JSON as `fastaguard_mqc.json`.
This plugin is the next step: a native module that can add FastaGuard verdicts
and key assembly preflight metrics directly to MultiQC reports.
This plugin is the next step: a native module that can add FastaGuard verdicts,
gate status, readiness status, submission readiness, and key assembly preflight
metrics directly to MultiQC reports.

## Local Install

Expand Down Expand Up @@ -42,7 +43,10 @@ multiqc --strict --module fastaguard examples/reports

- Parse FastaGuard custom-content JSON.
- Add verdict and summary metrics to the MultiQC general stats table.
- Add one FastaGuard summary table section.
- Add one FastaGuard summary table section with gate, readiness, and v0.5
submission fields including `submission_target`, `submission_status`,
unsafe identifier counts, long identifier counts, duplicate first-token ID
counts, and gap-like N-run counts.

Keep the module compact. MultiQC should summarize many FastaGuard reports, not
replicate every field from the full FastaGuard HTML report.
32 changes: 32 additions & 0 deletions integrations/multiqc/src/fastaguard_multiqc/multiqc_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,38 @@ def _summary_headers() -> dict:
"title": "Readiness blockers",
"description": "Readiness categories and finding IDs blocking downstream use",
},
"submission_target": {
"title": "Submission target",
"description": "Submission target profile used by FastaGuard",
},
"submission_status": {
"title": "Submission status",
"description": "FASTA-level submission readiness status",
},
"unsafe_identifier_count": {
"title": "Unsafe IDs",
"description": "Number of identifiers with submission-unsafe characters",
"min": 0,
"scale": "OrRd",
},
"long_identifier_count": {
"title": "Long IDs",
"description": "Number of identifiers longer than the submission advisory threshold",
"min": 0,
"scale": "YlOrBr",
},
"duplicate_first_token_id_count": {
"title": "Duplicate first-token IDs",
"description": "Number of duplicate first-token FASTA identifiers",
"min": 0,
"scale": "OrRd",
},
"gap_like_n_run_count": {
"title": "Gap-like N runs",
"description": "Number of records with gap-like N runs worth submission review",
"min": 0,
"scale": "YlOrBr",
},
"duplicate_id_count": {
"title": "Duplicate IDs",
"description": "Number of duplicate FASTA record IDs",
Expand Down
6 changes: 6 additions & 0 deletions integrations/multiqc/src/fastaguard_multiqc/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
"gate_blocking_findings",
"readiness_status",
"readiness_blockers",
"submission_target",
"submission_status",
"unsafe_identifier_count",
"long_identifier_count",
"duplicate_first_token_id_count",
"gap_like_n_run_count",
"duplicate_id_count",
"invalid_sequence_count",
"high_n_sequence_count",
Expand Down
36 changes: 36 additions & 0 deletions tests/python/test_adoption_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ def test_multiqc_parser_reads_fastaguard_custom_content(self):
self.assertEqual(summary["valid_assembly"]["gate_status"], "WARN")
self.assertEqual(summary["valid_assembly"]["readiness_status"], "WARN")
self.assertEqual(summary["valid_assembly"]["readiness_blockers"], "")
self.assertEqual(summary["valid_assembly"]["submission_target"], ".")
self.assertEqual(summary["valid_assembly"]["submission_status"], "WARN")
self.assertEqual(summary["valid_assembly"]["duplicate_first_token_id_count"], 0)
self.assertEqual(summary["valid_assembly"]["sequence_count"], 3)
self.assertEqual(summary["valid_assembly"]["n50"], 16)

Expand All @@ -172,6 +175,12 @@ def test_multiqc_parser_reads_expanded_fields_from_cli_example(self):
"gate_blocking_findings": "duplicate_ids,duplicate_first_token_ids,invalid_chars",
"readiness_status": "FAIL",
"readiness_blockers": "index.duplicate_ids,submission.duplicate_ids,index.duplicate_first_token_ids,submission.duplicate_first_token_ids,alphabet.invalid_chars,submission.invalid_chars",
"submission_target": ".",
"submission_status": "FAIL",
"unsafe_identifier_count": 0,
"long_identifier_count": 0,
"duplicate_first_token_id_count": 1,
"gap_like_n_run_count": 0,
"sequence_count": 5,
"total_length": 145,
"n50": 110,
Expand Down Expand Up @@ -221,6 +230,12 @@ def test_multiqc_parser_reads_expanded_summary_fields(self):
"finding_count": 4,
"readiness_status": "WARN",
"readiness_blockers": "assembly.high_n_rate",
"submission_target": "ncbi",
"submission_status": "WARN",
"unsafe_identifier_count": 1,
"long_identifier_count": 1,
"duplicate_first_token_id_count": 2,
"gap_like_n_run_count": 3,
}
},
}
Expand All @@ -240,6 +255,10 @@ def test_multiqc_parser_reads_expanded_summary_fields(self):
"n_percent": 2.5,
"finding_count": 4,
"duplicate_id_count": 1,
"unsafe_identifier_count": 1,
"long_identifier_count": 1,
"duplicate_first_token_id_count": 2,
"gap_like_n_run_count": 3,
"invalid_sequence_count": 0,
"high_n_sequence_count": 2,
"tiny_contig_count": 1,
Expand All @@ -249,6 +268,8 @@ def test_multiqc_parser_reads_expanded_summary_fields(self):
"composite_anomaly_count": 1,
"readiness_status": "WARN",
"readiness_blockers": "assembly.high_n_rate",
"submission_target": "ncbi",
"submission_status": "WARN",
},
)

Expand Down Expand Up @@ -400,8 +421,23 @@ def test_multiqc_plugin_summary_headers_include_gate_fields(self):
self.assertIn('"gate_blocking_findings"', module_source)
self.assertIn('"readiness_status"', module_source)
self.assertIn('"readiness_blockers"', module_source)
self.assertIn('"submission_target"', module_source)
self.assertIn('"submission_status"', module_source)
self.assertIn('"unsafe_identifier_count"', module_source)
self.assertIn('"long_identifier_count"', module_source)
self.assertIn('"duplicate_first_token_id_count"', module_source)
self.assertIn('"gap_like_n_run_count"', module_source)
self.assertIn("Finding IDs blocking the FastaGuard gate", module_source)
self.assertIn("FastaGuard readiness status", module_source)
self.assertIn("FASTA-level submission readiness status", module_source)

def test_multiqc_docs_describe_submission_fields(self):
readme = (ROOT / "integrations" / "multiqc" / "README.md").read_text()

self.assertIn("submission readiness", readme)
self.assertIn("`submission_target`", readme)
self.assertIn("`submission_status`", readme)
self.assertIn("duplicate first-token", readme)

def test_multiqc_plugin_registers_filename_first_fastaguard_search_pattern(self):
patterns = getattr(multiqc_parser, "FASTAGUARD_SEARCH_PATTERN", {})
Expand Down