Worked example: registering and harmonizing a real study
Every other page in this documentation describes the design. This one records what actually happened the first time AREE was pointed at a real published study — including the things that broke. It is the most useful page to read before curating your own.
The study
Hesser J, Mueller RS, Langdon C, Schubiger CB (2024). Immunomodulatory effects of a probiotic combination treatment to improve the survival of Pacific oyster (Crassostrea gigas) larvae against infection by Vibrio coralliilyticus. Frontiers in Immunology 15:1380089. doi:10.3389/fimmu.2024.1380089 (open access, CC BY 4.0).
Registered as HESSER2024_VCOR.
It was chosen because it is a genuine resilience study — larval survival under a pathogen challenge, with and without a protective intervention — and because its data are actually obtainable.
Finding a usable study is the hard part
A survey of every C. gigas series in GEO (31 series at time of writing) found not one that ships a differential-expression table. GEO holds raw reads, microarray .gpr files, count matrices, and bedgraphs. NCBI-generated count matrices do not cover this organism. The count matrices that do exist mostly use legacy or assembly-specific identifiers (Cg19979, XLOC_036894, CGI_10001851) rather than NCBI GeneIDs.
The practical consequence: for C. gigas, processed-results harmonization usually means a supplementary table from an open-access paper, not a repository download. Plan curation around that.
This study deposited no raw reads at all — its only public data are the supplementary DEG tables — which is exactly the case processed_results_harmonization exists for.
Getting from the publisher’s file to an AREE result file
The published artifact is an Excel workbook with three sheets, one per comparison. src/intake/supplementary_table.py performs the minimum mechanical reshaping and records what it did in data/studies/HESSER2024_VCOR/intake_provenance.json, with checksums of both the source file and every generated result file. It deliberately does not alter identifiers, impute missing statistics, or filter beyond what the authors did.
Two things worth knowing:
- The published sheet contains a repeated header row part-way down (source row 71 of
18hrPB+Vc v LOnly). Real supplementary tables are not clean tables. The converter coerces the numeric columns and drops non-numeric rows, counting them in the provenance record rather than letting the string"log2FoldChange"reach the harmonizer as an effect size. - One of the three published comparisons was deliberately not registered. The “18 hr PB Only” arm (probiotic, no pathogen) involves no stressor, and the stressor ontology has no term for a beneficial-microbe exposure. Forcing it into
pathogen_challengewould have been false. This is recorded in the study’slimitationsrather than quietly dropped — a gap in the vocabulary is a finding, not something to paper over.
What the crosswalk actually did
351 evidence records, resolved against data/reference/crosswalk/mgigas_gene_id_crosswalk.tsv:
| mapping_confidence | records | what these are |
|---|---|---|
exact |
274 | direct hits on the current NCBI annotation |
inferred |
32 | GeneIDs NCBI has retired, remapped to their current replacement |
unresolved |
45 | genes NCBI discontinued with no replacement |
87.2% resolved. Every single unresolved identifier was checked against NCBI gene_history: all 35 distinct unresolved LOC identifiers are genes NCBI itself withdrew without a successor. Nothing resolvable was left unresolved.
The 32 inferred records exist only because of the retired-GeneID sidecar. The authors aligned to assembly GCA_902806645.1 (cgigas_uk_roslin_v1) and its 2020-era annotation, while the crosswalk is built on the 2024 annotation; without retired-ID remapping those 32 records would have been silently lost. This is the annotation-version problem in handling_genome_versions.md showing up on the first real study rather than in theory.
The one remaining unresolved identifier is not a LOC ID at all: the published table mixes identifier systems and lists a bare gene symbol, Myd88. It is left unresolved on purpose. M. gigas has at least five MyD88-family genes (Myd88, Myd88-2, and several unnamed paralogs), so automatically assigning a bare symbol to one of them would manufacture a paralog-level claim the source does not support. Cases like this belong in data/mappings/ambiguous_symbol_map.yaml as a curated, documented decision.
Keeping real and simulated evidence apart
Once a real study exists, the demo studies are no longer harmless. Two changes enforce the separation:
simulatedis now a column in the evidence schema and part of the meta-analysis grouping key, so a pooled estimate can never combine fabricated demo evidence with evidence from a real study. The Streamlit interface has a matching “Data origin” filter.- Each study chooses its own crosswalk from its
simulatedflag (crosswalk_for_study()). A simulated study always resolves against the demo crosswalk; a real study refuses to run at all unless a real crosswalk is selected. This matters because the demo LOC numbers collide with real GeneIDs denoting different genes — harmonizing the demo studies against the real crosswalk attaches real NCBI GeneIDs to fabricated effect sizes.
The study contributes no pooled estimate — by design
aree meta-analyze returns 34 pooled rows, none of them from this study.
The source publishes a log2 fold change and a Benjamini-Hochberg adjusted p-value, and no standard error and no unadjusted p-value. There is no defensible way to recover a standard error from an adjusted p-value, so AREE declines to pool rather than imputing one. The evidence records are still in the table, still searchable, still exportable, and still available to any future analysis that does not need inverse-variance weighting.
This is the correct outcome, and it is worth stating plainly: a real study can be fully harmonized and still contribute nothing to a meta-analysis. The limitation was predicted in the study’s limitations field before the pipeline was run.
Reproducing it
aree build-crosswalk # once; ~380 MB streamed from NCBI
export AREE_CROSSWALK=data/reference/crosswalk/mgigas_gene_id_crosswalk.tsv
# The published spreadsheet -> the two result tables AREE harmonizes.
aree intake-supplementary data/studies/HESSER2024_VCOR/intake.yaml --check
aree validate-study registry/studies/HESSER2024_VCOR.yaml
aree register-study registry/studies/HESSER2024_VCOR.yaml --update
aree harmonize --study HESSER2024_VCORThe result files are committed, so the harmonize step runs without re-downloading anything from the publisher. The published source spreadsheet is committed too, which is what lets the intake step be verified rather than trusted: --check regenerates the tables into a temporary directory and compares checksums against both the committed files and intake_provenance.json. Drop --check to rewrite them; a re-run producing byte-identical output preserves the original date_generated, so verifying reproducibility never shows up as a diff.
Reading .xls/.xlsx sources needs the optional spreadsheet engines: pip install -e ".[intake]". CSV and TSV sources need nothing extra.
What the intake step deliberately will not do
The conversion is mechanical: select and rename columns, drop rows with no identifier or no effect size, drop non-numeric rows (the published sheet 18hrPB+Vc v LOnly contains a repeated header part-way down), and record all of it. It never imputes a missing statistic. Because this source reports only log2FoldChange and padj, the lfcSE and pvalue columns come out empty, and that emptiness is what later stops the study from being pooled. Turning that gap into a plausible-looking number in a spreadsheet would have hidden the single most important fact about this study.
Two guards keep that honest: the source sha256 in the intake config is verified before every conversion, and scripts/check_real_study_evidence.py (run by CI) fails if standard_error or p_value ever becomes populated for this study.