"""Dated-events table (hand-compiled from the literature, with citations). Ages are in Ga before present; future events are negative ages (Gyr from now, sign flipped in the `future_Gyr` column). `lo`/`hi` are the published interval or the range of competing estimates (see `interval_kind`). `status`: accepted - broadly accepted in the literature contested - published, but biogenicity / age / method disputed in print model - model-dependent estimate (molecular clock, climate model) `verified` says how the number was checked in this study: primary - read in the paper's own text / abstract / data repository secondary - confirmed via a search-result summary of the abstract or a press release cited - taken from how another paper (named in `note`) quotes it; not re-checked """ from __future__ import annotations import csv import os EVENTS = [ # ---------------- habitability onset -------------------------------------------- dict(key="moon_impact", group="habitability", event="Moon-forming impact (earliest possible start)", age=4.510, lo=4.500, hi=4.520, interval_kind="+/-10 Myr (calibration)", status="accepted", source="Moody et al. 2024, Nat Ecol Evol 8:1654 (calibration: 4,510 Ma +/- 10 Myr)", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC11383801/", verified="primary", note="Used by Moody et al. as the hard maximum for the root of the tree."), dict(key="zircon_water", group="habitability", event="Jack Hills zircon: liquid water / continental crust", age=4.404, lo=4.396, hi=4.412, interval_kind="1 sigma", status="accepted", source="Wilde et al. 2001, Nature 409:175 (4.404 +/- 0.008 Ga)", url="https://doi.org/10.1038/35051550", verified="cited", note="Value as quoted by Kipping 2020/2025, who adopt it as the start of habitability."), dict(key="oceans_cavosie", group="habitability", event="Oceans present (zircon d18O)", age=4.25, lo=4.2, hi=4.3, interval_kind="range", status="accepted", source="Cavosie et al. 2005 (as tabulated by Snyder-Beattie et al. 2021, Table 1)", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC7997718", verified="cited", note="Snyder-Beattie et al. start their clock at 4.2 or 4.3 Ga."), dict(key="lhb", group="habitability", event="Late Heavy Bombardment as a sterilising spike at ~3.9 Ga", age=3.9, lo=3.8, hi=4.1, interval_kind="range", status="contested", source="Moody et al. 2024 reject it as a calibration maximum; Mills et al. 2025 keep a 4.5-3.9 Ga 'habitability window'", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC11383801/", verified="primary", note="Not used as a start date here; kept as a sensitivity case (start = 3.9 Ga makes every pre-3.9 Ga claim moot)."), # ---------------- earliest life --------------------------------------------------- dict(key="luca", group="life", event="LUCA (molecular clock, cross-braced, ILN+GBM)", age=4.20, lo=4.09, hi=4.33, interval_kind="95% (paper); repository gives ILN 4.03-4.31, GBM 4.18-4.35", status="model", source="Moody et al. 2024, Nat Ecol Evol 8:1654; Zenodo 10.5281/zenodo.11260523", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC11383801/", verified="primary", note="Composite over analyses 3.94-4.52 Ga; LUCA post-dates the origin of life."), dict(key="zircon_graphite", group="life", event="Isotopically light graphite in a 4.1 Ga Jack Hills zircon", age=4.10, lo=4.09, hi=4.11, interval_kind="1 sigma", status="contested", source="Bell et al. 2015, PNAS 112:14518", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC4664322", verified="secondary", note="One inclusion in one zircon; 'potentially biogenic' by the authors' own wording."), dict(key="labrador", group="life", event="Saglek (Labrador) graphite", age=3.95, lo=3.0, hi=3.95, interval_kind="claimed age; host rocks possibly < 3 Ga", status="contested", source="Tashiro et al. 2017, Nature 549:516; age disputed by Whitehouse et al. 2019", url="https://www.nature.com/news/oldest-traces-of-life-on-earth-may-lurk-in-canadian-rocks-1.22685", verified="cited", note="Per the project's abiogenesis evidence brief; treated as unreliable."), dict(key="nuvvuagittuq", group="life", event="Nuvvuagittuq haematite tubes", age=3.77, lo=3.77, hi=4.28, interval_kind="min-max age of host rock", status="contested", source="Dodd et al. 2017, Nature 543:60", url="https://pubs.usgs.gov/publication/70193303", verified="cited", note="Biogenicity contested (McMahon 2019)."), dict(key="isua", group="life", event="Isua: stromatolite-like structures / 13C-depleted graphite", age=3.70, lo=3.70, hi=3.80, interval_kind="range", status="contested", source="Nutman et al. 2016, Nature 537:535; rebutted by Allwood et al. 2018, Nature 563:241", url="https://link.springer.com/article/10.1038/s41586-018-0610-4", verified="cited", note="Kipping 2025 uses 3.7 Ga as his 'microfossil' case."), dict(key="dresser", group="life", event="Dresser Formation stromatolites / hot-spring deposits", age=3.48, lo=3.47, hi=3.49, interval_kind="formation age", status="accepted", source="Djokic et al. 2017, Nat Commun 8:15263", url="https://unsw.edu.au/newsroom/news/2017/05/oldest-evidence-of-life-on-land-found-in-3-48-billion-year-old-a", verified="cited", note="Oldest broadly accepted evidence."), dict(key="apex", group="life", event="Apex chert microfossils", age=3.465, lo=3.46, hi=3.47, interval_kind="formation age", status="accepted", source="Schopf et al. 2018, PNAS 115:53 (as used by Kipping 2020)", url="https://arxiv.org/abs/2005.09008", verified="primary", note="Kipping 2020 'conservative' case, t_L' = 0.939 Gyr."), # ---------------- oxygen and later transitions ------------------------------------- dict(key="oxyphoto", group="transition", event="Oxygenic photosynthesis (total-group Oxyphotobacteria)", age=2.99, lo=2.95, hi=3.09, interval_kind="95% posterior (ILN); fossil minimum 2.954 Ga", status="model", source="Moody et al. 2024 repository (node TG-OXYPHOTOBACTERIA); Mo-isotope minimum 2,954 +/- 9 Ma", url="https://doi.org/10.5281/zenodo.11260523", verified="primary", note="Other estimates range from >3.0 Ga (Fournier et al. 2021) to just before the GOE (Shih et al. 2017)."), dict(key="goe", group="environment", event="Great Oxidation Event (onset to permanent oxygenation)", age=2.33, lo=2.22, hi=2.46, interval_kind="onset 2.46-2.426 Ga; permanent by ~2.22 Ga", status="accepted", source="Gumsley et al. 2017, PNAS 114:1811; Poulton et al. 2021, Nature 592:232", url="https://portal.research.lu.se/en/publications/timing-and-tempo-of-the-great-oxidation-event/", verified="secondary", note="Gate for aerobic eukaryotes in the pacing model (>= 0.001-0.4% PAL O2)."), dict(key="mfeca", group="transition", event="Mitochondrial lineage splits from Alphaproteobacteria (mFECA)", age=2.25, lo=2.13, hi=2.37, interval_kind="95% (clock)", status="model", source="Kay et al. 2025, Nature 650:129", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC12872463/", verified="primary", note="Mitochondrial endosymbiosis aligned to ~2.2 Ga by the authors."), dict(key="leca", group="transition", event="Last eukaryotic common ancestor (LECA)", age=1.735, lo=1.67, hi=1.80, interval_kind="95% (clock)", status="model", source="Kay et al. 2025, Nature 650:129", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC12872463/", verified="primary", note="Moody et al. 2024 repository: ILN 1.54-1.86, GBM 1.20-1.41 Ga; oldest eukaryote fossils ~1.65 Ga."), dict(key="noe", group="environment", event="Neoproterozoic oxygenation (dynamic, rising pO2)", age=0.635, lo=0.539, hi=0.80, interval_kind="range", status="contested", source="Mills et al. 2025, Sci Adv 11:eads5698 (and refs therein)", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC11827626/", verified="primary", note="Whether animals needed it is disputed in Mills et al. themselves (0.1-1% PAL may have been met earlier)."), dict(key="metazoa", group="transition", event="Crown Metazoa", age=0.60, lo=0.574, hi=0.80, interval_kind="fossil minimum 0.574 Ga; clocks to ~0.8 Ga", status="model", source="Carlisle et al. 2024, Sci Adv 10:eadp7161 (early Ediacaran origin); calibration 0.574-0.609 Ga in Moody et al. 2024", url="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC11559618/", verified="secondary", note="Central value follows Carlisle et al.; the upper end (0.8 Ga) is now supported by Lole Durbin et al. 2026 (next row). " "The models draw this date uniformly over 0.574-0.80 Ga."), dict(key="metazoa_ld26", group="transition", event="Origin of Metazoa (molecular clock with re-evaluated maximum calibrations)", age=0.75, lo=0.70, hi=0.80, interval_kind="range quoted in the abstract (~800-700 Ma)", status="model", source="Lole Durbin, Anttila, Briggs, Macdonald & Anderson 2026, Sci Adv 12(40):eaeg6289", url="https://doi.org/10.1126/sciadv.aeg6289", verified="primary", note="Published 2 Oct 2026; abstract read via Crossref (data: Dryad 10.5061/dryad.tqjq2bwfd). Predates a 0.635 Ga " "oxygen gate; used only in the gate-definition sensitivity (data/a4_gate_definition_sensitivity.csv)."), dict(key="embryophyta", group="transition", event="Land plants (crown Embryophyta)", age=0.475, lo=0.431, hi=0.515, interval_kind="95% (clock) / calibration bounds", status="model", source="Moody et al. 2024 repository (EMBRYOPHYTA node, ILN); cf. Morris et al. 2018, PNAS 115:E2274", url="https://doi.org/10.5281/zenodo.11260523", verified="primary", note=""), dict(key="poe", group="environment", event="Paleozoic oxygenation: pO2 stabilises near modern", age=0.41, lo=0.40, hi=0.42, interval_kind="range", status="accepted", source="Mills et al. 2025, Sci Adv 11:eads5698", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC11827626/", verified="primary", note="Gate for human-type physiology (53-59% PAL) and open-air fire (87.8% PAL) in the pacing model."), dict(key="sapiens", group="transition", event="Homo sapiens (Jebel Irhoud)", age=0.000315, lo=0.000281, hi=0.000349, interval_kind="1 sigma", status="accepted", source="Richter et al. 2017, Nature 546:293 (315 +/- 34 ka)", url="https://doi.org/10.1038/nature22335", verified="secondary", note="Technological civilisation is ~0 on a Gyr scale; treated as t = 0."), # ---------------- future end of habitability (Gyr from now, stored as negative age) --- dict(key="end_ck92", group="future", event="C4-plant biosphere ends (CO2 limit ... temperature limit)", age=-0.9, lo=-1.5, hi=-0.9, interval_kind="CO2-limited 0.9, temperature-limited 1.5", status="model", source="Caldeira & Kasting 1992, Nature 360:721", url="https://www.nature.com/articles/360721a0", verified="secondary", note="Kipping 2020/2025 adopt 0.9 Gyr (T = 5.304 Gyr)."), dict(key="end_lvb01", group="future", event="Biosphere life span with biotic weathering feedback", age=-1.0, lo=-1.2, hi=-0.8, interval_kind="range", status="model", source="Lenton & von Bloh 2001, GRL 28:1715", url="https://www.pik-potsdam.de/~bloh/abstracts/lenton2001.html", verified="secondary", note=""), dict(key="end_omj13", group="future", event="Last (unicellular) life in refuges", age=-2.8, lo=-2.8, hi=-2.8, interval_kind="upper limit", status="model", source="O'Malley-James et al. 2013, Int J Astrobiol 12:99", url="https://discovery.dundee.ac.uk/en/publications/swansong-biospheres-refuges-for-life-and-novel-microbial-biospher/", verified="secondary", note="Microbial limit; not a window for complex life."), dict(key="end_or21", group="future", event="Atmospheric O2 falls below 1% PAL", age=-1.08, lo=-1.22, hi=-0.94, interval_kind="1 sigma", status="model", source="Ozaki & Reinhard 2021, Nat Geosci 14:138", url="https://arxiv.org/abs/2103.02694", verified="secondary", note="Baseline 'short' future for oxygen-breathing observers."), dict(key="end_gha24", group="future", event="Land plants end (weak T-dependence of weathering)", age=-1.73, lo=-1.86, hi=-1.60, interval_kind="range", status="model", source="Graham, Halevy & Abbot 2024, PSJ 5:255", url="https://arxiv.org/abs/2409.10714", verified="secondary", note="'Long' future."), dict(key="end_hmw26", group="future", event="Vegetative biosphere ends (3D GCM)", age=-1.84, lo=-1.87, hi=-1.68, interval_kind="1.68 most plants too hot; 1.84 CO2 < 1 ppm; 1.87 all plants too hot", status="model", source="Haqq-Misra & Wolf 2026, JGR Atmospheres (arXiv:2605.22404)", url="https://arxiv.org/abs/2605.22404", verified="secondary", note="Newest estimate found; supports the 'long' future."), ] def write_events_csv(path): cols = ["key", "group", "event", "age_Ga", "lo_Ga", "hi_Ga", "interval_kind", "status", "verified", "source", "url", "note"] os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w", newline="") as f: w = csv.writer(f) w.writerow(cols) for e in EVENTS: w.writerow([e["key"], e["group"], e["event"], e["age"], e["lo"], e["hi"], e["interval_kind"], e["status"], e["verified"], e["source"], e["url"], e["note"]]) return len(EVENTS) def ev(key): for e in EVENTS: if e["key"] == key: return e raise KeyError(key) if __name__ == "__main__": here = os.path.dirname(os.path.abspath(__file__)) n = write_events_csv(os.path.join(here, "..", "data", "events.csv")) print("wrote", n, "events")