@@ -98,10 +98,10 @@ process VIRASIGN_SUMMARY {
9898 <head>
9999 <meta charset="utf-8"/>
100100 <meta name="viewport" content="width=device-width, initial-scale=1"/>
101- <title>Virasign summary </title>
101+ <title>Metatropics Results Summary </title>
102102 </head>
103103 <body>
104- <h1>Virasign summary </h1>
104+ <h1>Metatropics Results Summary </h1>
105105 <p>No confident hits were found (no <code>*_final_selected_references.json</code> files present).</p>
106106 </body>
107107</html>
@@ -624,6 +624,13 @@ if summary_html.exists():
624624
625625 text = summary_html.read_text(encoding="utf-8")
626626
627+ # Branding: Metatropics report header (keep database suffix, e.g. " - RVDB").
628+ text = text.replace("Virasign Results Summary", "Metatropics Results Summary")
629+ text = text.replace(
630+ "Viral Read ASSIGNment from nanopore sequencing",
631+ "Human viral pathogen identification from nanopore metagenomic sequencing",
632+ )
633+
627634 # If the pipeline is re-run, remove any previously injected consensus panel.
628635 # Use plain string operations (avoid regex backslash parsing issues).
629636 section_start = text.find('<section id="consensus-breadth"')
@@ -680,13 +687,72 @@ if summary_html.exists():
680687 bg_map = {}
681688 bg_map_json = json.dumps(bg_map, separators=(",", ":"))
682689
690+ # Resolve which water/background samples were actually used for Z-score.
691+ # Prefer names embedded by Virasign in samplesData.zscore_controls (truth of the run).
692+ zscore_controls_used = []
693+ try:
694+ m_sd = re.search(r"const samplesData\\ s*=\\ s*", text)
695+ if m_sd:
696+ samples_obj, _ = json.JSONDecoder().raw_decode(text, m_sd.end())
697+ seen = []
698+ for _sname, dbs in (samples_obj or {}).items():
699+ for _db, payload in (dbs or {}).items():
700+ for ref in (payload or {}).get("references") or []:
701+ for c in ref.get("zscore_controls") or []:
702+ c = str(c).strip()
703+ if c and c not in seen:
704+ seen.append(c)
705+ zscore_controls_used = seen
706+ except Exception:
707+ zscore_controls_used = []
708+
709+ if not zscore_controls_used and controls_norm:
710+ zscore_controls_used = sorted(controls_norm)
711+ if not zscore_controls_used:
712+ zscore_controls_used = sorted(s for s, v in bg_map.items() if str(v).strip().lower() == "yes")
713+
714+ if not zscore_enabled:
715+ zscore_mode = "disabled"
716+ zscore_mode_label = "disabled"
717+ zscore_water_msg = f"Z-score disabled (--zscore={zscore_enabled_raw})."
718+ elif zscore_controls_text:
719+ zscore_mode = "manual"
720+ zscore_mode_label = "manually selected"
721+ names = ", ".join(zscore_controls_used) if zscore_controls_used else zscore_controls_text
722+ if zscore_has_values:
723+ zscore_water_msg = f"Water controls (manually selected): {names}"
724+ else:
725+ zscore_water_msg = (
726+ f"Water controls provided, but none were usable/available for Z-score computation. "
727+ f"Provided: {names}"
728+ )
729+ elif zscore_controls_used and zscore_has_values:
730+ zscore_mode = "auto"
731+ zscore_mode_label = "auto-detected"
732+ zscore_water_msg = f"Water controls (auto-detected): {', '.join(zscore_controls_used)}"
733+ else:
734+ zscore_mode = "none"
735+ zscore_mode_label = "none available"
736+ zscore_water_msg = (
737+ "Water controls: none usable/available (Z-score not computed)."
738+ )
739+
740+ zscore_controls_json = json.dumps(zscore_controls_used, separators=(",", ":"))
741+ zscore_mode_json = json.dumps(zscore_mode, separators=(",", ":"))
742+ zscore_mode_label_json = json.dumps(zscore_mode_label, separators=(",", ":"))
743+ zscore_water_msg_json = json.dumps(zscore_water_msg, separators=(",", ":"))
744+
683745 inj_script = '''
684746<script>
685747(function(){
686748 const consensusMap = __CONS_MAP_JSON__;
687749 const qcReadsMap = __QC_READS_MAP_JSON__;
688750 const readLenMap = __READLEN_MAP_JSON__;
689751 const bgMap = __BG_MAP_JSON__;
752+ const zscoreControls = __ZSCORE_CONTROLS_JSON__;
753+ const zscoreMode = __ZSCORE_MODE_JSON__;
754+ const zscoreModeLabel = __ZSCORE_MODE_LABEL_JSON__;
755+ const zscoreWaterMsg = __ZSCORE_WATER_MSG_JSON__;
690756
691757 function getVal(sampleName, accession){
692758 if (!consensusMap || !consensusMap[sampleName]) return null;
@@ -945,6 +1011,62 @@ if summary_html.exists():
9451011 setConsensusColumnVisibility(sampleName, false);
9461012 }
9471013
1014+ // Inject Z-score water-control info into the Summary banner and Pipeline Configuration.
1015+ function zscoreControlsDisplay(){
1016+ if (Array.isArray(zscoreControls) && zscoreControls.length) {
1017+ return zscoreControls.join(', ');
1018+ }
1019+ return zscoreWaterMsg || 'none';
1020+ }
1021+
1022+ function injectZscoreControlsBanner(){
1023+ const summary = document.querySelector('.summary');
1024+ if (!summary || document.getElementById('zscore-controls-banner')) return;
1025+ const div = document.createElement('div');
1026+ div.id = 'zscore-controls-banner';
1027+ div.style.margin = '1rem 0 0';
1028+ div.style.padding = '0.85rem 1rem';
1029+ div.style.background = '#f0f7ff';
1030+ div.style.border = '1px solid #c5daf5';
1031+ div.style.borderRadius = '8px';
1032+ div.style.color = '#1a365d';
1033+ div.style.fontSize = '0.95em';
1034+ const modeTxt = zscoreModeLabel ? (' (' + zscoreModeLabel + ')') : '';
1035+ div.innerHTML =
1036+ '<strong>Z-score water controls</strong>' +
1037+ '<span style="opacity:0.75">' + modeTxt + '</span>: ' +
1038+ '<span>' + zscoreControlsDisplay() + '</span>';
1039+ summary.appendChild(div);
1040+ }
1041+
1042+ function appendZscoreMetadata(sampleName){
1043+ const container = document.getElementById('metadata-' + sampleName);
1044+ if (!container || container.querySelector('[data-role="zscore-controls"]')) return;
1045+ const item = document.createElement('div');
1046+ item.className = 'metadata-item';
1047+ item.setAttribute('data-role', 'zscore-controls');
1048+ const label = document.createElement('div');
1049+ label.className = 'label';
1050+ label.textContent = 'Z-score water controls' + (zscoreModeLabel ? ' (' + zscoreModeLabel + ')' : '');
1051+ const value = document.createElement('div');
1052+ value.className = 'value';
1053+ value.textContent = zscoreControlsDisplay();
1054+ value.style.wordBreak = 'break-word';
1055+ item.appendChild(label);
1056+ item.appendChild(value);
1057+ container.appendChild(item);
1058+ }
1059+
1060+ injectZscoreControlsBanner();
1061+
1062+ const origPopulateMetadata = window.populateMetadata;
1063+ if (typeof origPopulateMetadata === 'function'){
1064+ window.populateMetadata = function(sampleName){
1065+ origPopulateMetadata(sampleName);
1066+ appendZscoreMetadata(sampleName);
1067+ };
1068+ }
1069+
9481070 const origPopulateTable = window.populateTable;
9491071 if (typeof origPopulateTable === 'function'){
9501072 window.populateTable = function(sampleName){
@@ -953,10 +1075,7 @@ if summary_html.exists():
9531075 };
9541076 }
9551077
956- // (No Z-score background metadata injection: keep HTML clean.)
957-
958- // Override downloadTableAsCSV so the downloaded CSV includes QC reads + median read length + consensus breadth.
959- const origDownloadTableAsCSV = window.downloadTableAsCSV;
1078+ // Override downloadTableAsCSV so the downloaded CSV includes QC reads + median read length + consensus breadth. const origDownloadTableAsCSV = window.downloadTableAsCSV;
9601079 window.downloadTableAsCSV = function(sampleName) {
9611080 const tbody = document.getElementById('tbody-' + sampleName);
9621081 if (!tbody) return;
@@ -1096,6 +1215,7 @@ if summary_html.exists():
10961215 if (active && active.id && active.id.startsWith('sample-')){
10971216 const sn = active.id.slice('sample-'.length);
10981217 injectForSample(sn);
1218+ appendZscoreMetadata(sn);
10991219 }
11001220 } catch (e) {}
11011221})();
@@ -1106,6 +1226,10 @@ if summary_html.exists():
11061226 inj_script = inj_script.replace("__QC_READS_MAP_JSON__", qc_map_json)
11071227 inj_script = inj_script.replace("__READLEN_MAP_JSON__", readlen_map_json)
11081228 inj_script = inj_script.replace("__BG_MAP_JSON__", bg_map_json)
1229+ inj_script = inj_script.replace("__ZSCORE_CONTROLS_JSON__", zscore_controls_json)
1230+ inj_script = inj_script.replace("__ZSCORE_MODE_JSON__", zscore_mode_json)
1231+ inj_script = inj_script.replace("__ZSCORE_MODE_LABEL_JSON__", zscore_mode_label_json)
1232+ inj_script = inj_script.replace("__ZSCORE_WATER_MSG_JSON__", zscore_water_msg_json)
11091233
11101234 if 'data-col="consensus_breadth_pct"' not in text:
11111235 if "</body>" in text:
0 commit comments