Skip to content

Latest commit

Β 

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

GeoScience-FAIRification-LOD: Palaeoclimate Data Processing Pipeline

Squilly Logo

A Python pipeline that turns palaeoclimate measurements into FAIR Linked Open Data. It covers three strands: five proxy records from the EPICA Dome C ice core (CHβ‚„, Ξ΄D, dust, δ¹⁸O of Oβ‚‚ and Ξ΄Oβ‚‚/Nβ‚‚), speleothem isotopes from the SISAL database, and Campanian Ignimbrite findspots. The graph is built on SOSA/SSN for the measurements, GeoSPARQL for the geometries, PROV-O for the provenance and CIDOC-CRM as the anchor that lets it enter the NFDI4Objects Knowledge Graph; the same run produces the figures, so a plot and a triple cannot disagree about what a source file said.

Two properties are treated as requirements rather than niceties. Every age names the chronology it comes from. The five EPICA records rest on four different depth-age models, and two of them additionally differ in phase β€” an ice age and a gas age at the same depth are not the same age. Six temporal reference systems under http://w3id.org/geo-lod/trs/ keep them apart, which is what makes it visible that the beginning of MIS 5 sits at 1734 m on one scale and at 1782 m on another, in the same core. Nothing is stated where nothing was measured. A stage boundary is not carried into depth across a data gap, a filter is not run across one, and a figure marks the gap instead of drawing a line over it.

Every run is byte-reproducible: no output carries a timestamp, and each generated dataset states the fingerprint of the input data and the generator code it came from.

DOI

πŸ“ Structure

project/
β”œβ”€β”€ main.py                       ← MAIN SCRIPT (run everything)
β”œβ”€β”€ README-RUN.md                 ← every script and flag, and what it writes
β”œβ”€β”€ plot_overview_map.py          ← all sites, read from the graph
β”œβ”€β”€ pipeline_report.txt           ← Execution log
β”œβ”€β”€ maps/                         ← the cross-strand overview map
β”‚
β”œβ”€β”€ EPICA/                        ← EPICA Dome C (ice core), five proxies
β”‚   β”œβ”€β”€ epica_data.py             ← one loader for figures and RDF, plus provenance
β”‚   β”œβ”€β”€ epica_rdf.py              ← RDF generator
β”‚   β”œβ”€β”€ epica_plates.py           ← multi-panel plates and the paper collages
β”‚   β”œβ”€β”€ plot_epica_from_tab.py    ← single figures
β”‚   β”œβ”€β”€ captions.yaml             ← figure captions (generated, editable)
β”‚   β”œβ”€β”€ plots/                    ← JPG + SVG
β”‚   β”‚   β”œβ”€β”€ ch4_vs_age_ka_full.jpg      … 30 single figures
β”‚   β”‚   β”œβ”€β”€ plate_columns_smooth11.jpg  … 7 plates
β”‚   β”‚   └── plate_pipeline_outputs.jpg  … 2 collages
β”‚   β”œβ”€β”€ rdf/
β”‚   β”‚   β”œβ”€β”€ epica_ontology.ttl
β”‚   β”‚   └── epica_dome_c.ttl
β”‚   └── report/
β”‚
β”œβ”€β”€ SISAL/                        ← SISAL (speleothems)
β”‚   β”œβ”€β”€ plot_sisal_from_csv.py
β”‚   β”œβ”€β”€ plots/                    ← profiles per speleothem, cluster plates
β”‚   β”œβ”€β”€ maps/                     ← world map and one per climate system
β”‚   β”œβ”€β”€ rdf/
β”‚   β”‚   β”œβ”€β”€ sisal_ontology.ttl
β”‚   β”‚   β”œβ”€β”€ sisal_sites.ttl       ← all 365 caves, positions only
β”‚   β”‚   └── sisal_<id>_<cave>_data.ttl
β”‚   └── report/
β”‚
β”œβ”€β”€ CI/                           ← Campanian Ignimbrite findspots
β”‚   β”œβ”€β”€ ci_pipeline.py            ← findspots to RDF
β”‚   β”œβ”€β”€ plot_ci_findspots.py      ← maps and certainty plate
β”‚   β”œβ”€β”€ maps/                     ← findspot maps
β”‚   β”œβ”€β”€ captions.yaml
β”‚   β”œβ”€β”€ rdf/                      ← ci_findspots.ttl, ci_site_annotations.ttl
β”‚   β”œβ”€β”€ plots/
β”‚   └── report/
β”‚
β”œβ”€β”€ ontology/                     ← shared, on every sub-script's PYTHONPATH
β”‚   β”œβ”€β”€ geo_lod_utils.py          ← core ontology, provenance, Mermaid
β”‚   β”œβ”€β”€ geo_lod_figures.py        ← saving, axes, the data-gap convention
β”‚   β”œβ”€β”€ geo_lod_basemap.py        ← coastlines, clipping, polar projection
β”‚   β”œβ”€β”€ geo_lod_captions.py       ← caption files
β”‚   β”œβ”€β”€ build_mis_vocab.py        ← MIS vocabulary and TRS generator
β”‚   β”œβ”€β”€ geo_lod_core.ttl          ← base ontology (generated)
β”‚   β”œβ”€β”€ crm_bridging.ttl          ← CIDOC-CRM bridging axioms
β”‚   β”œβ”€β”€ trs.ttl                   ← temporal reference systems (generated)
β”‚   β”œβ”€β”€ vocab/mis.ttl             ← Marine Isotope Stages (generated)
β”‚   β”œβ”€β”€ shapes/                   ← SHACL: core_shapes.ttl, mis_shapes.ttl
β”‚   └── *.mermaid                 ← taxonomy and instance diagrams
β”‚
β”œβ”€β”€ data/raw/                     ← primary sources, read-only
β”‚   β”œβ”€β”€ epica/                    ← the five PANGAEA .tab files
β”‚   └── mis/                      ← LR04 and Railsback tables
β”‚
β”œβ”€β”€ dist/                         ← generated
β”‚   β”œβ”€β”€ geo-lod-bundle.ttl        ← all triples in one file
β”‚   β”œβ”€β”€ mis_stages.csv            ← one row per MIS concept
β”‚   └── mis_assignments.csv       ← one row per boundary assignment
β”‚
β”œβ”€β”€ PRIMER.md                     ← working plan and decision log
β”œβ”€β”€ README.md
└── LICENSE

Anything under data/raw/ is a source and is only ever read; anything a script makes goes to dist/, plots/ or rdf/. That is what makes it readable off any file whether it is evidence or output.

πŸš€ Usage

python main.py                          # everything, development settings
python main.py --sisal-sites all        # all twelve SISAL sites
python main.py --bundle-format release  # the release: all formats, print rasters
python clean.py                         # what was generated, what is left over

README-RUN.md is the full reference β€” every script, every flag, what each one writes, and the checks worth running. What follows here is only the shape of a run.

main.py executes the strands in order and logs everything to pipeline_report.txt. The RDF step of a strand always runs before its figures: a fault in the data then surfaces before anything is drawn. Step numbers come from a counter and follow the run, so a full run counts to 14 and a --ci-only to 8.

Three defaults make a development run fast, and a release run overrides all three at once:

development release
SISAL sites five (dev) all twelve
large SISAL graphs N-Triples Turtle
JPG resolution 100 dpi 300 dpi, min. 3000 px
bundle N-Triples nt, Turtle, JSON-LD, RDF/XML

--bundle-format release sets the lot and says so whenever it overrides something. A full run takes about fifteen minutes, two thirds of it in the SHACL validation of the bundle; --ci-only --archaeo-only --no-bundle is done in seconds.

Strand switches combine: --ci-only --archaeo-only runs the two that belong together and leaves EPICA and SISAL alone.

πŸ“Š Output

Figures (JPG + SVG)

EPICA Dome C β€” 30 single figures. Five records Γ— two axes (depth, age) Γ— three smoothing variants (full, full_smooth11, full_savgol11p2). Named {proxy}_vs_{depth|age_ka}_full{variant}, with proxy one of ch4, dd, dust, d18o, do2n2.

EPICA Dome C β€” 7 plates, the comparisons a single-proxy figure cannot make:

  • plate_columns_{variant} β€” five records side by side on one vertical age axis
  • plate_rows_{variant} β€” the same five stacked on a horizontal age axis
  • plate_boundary_depths β€” the same MIS boundaries in the depth axis of each record, and each record's departure from their mean

EPICA Dome C β€” 2 collages for the paper: plate_pipeline_outputs (δ¹⁸O and CHβ‚„, each against age and depth) and plate_pipeline_outputs_five (all five records). Both show the measured series in grey behind the smoothed one.

SISAL β€” 24 plots for four caves: BotuverΓ‘ (144), Antro del Corchia (145), Sanbao (140, δ¹⁸O only), Buraca Gloriosa (275). Format {site_id}_{cave}_{isotope}_age_{variant}.{jpg,svg}.

Axis limits are derived from the data (geo_lod_figures.nice_ticks), never from a fixed tick list, so a record cannot be drawn outside its own axis. Per-record overrides live in AXIS_OVERRIDES in plot_epica_from_tab.py.

Data gaps are drawn as gaps

Where consecutive measurements are more than 15 ka apart, the line is dashed rather than solid and the last sample before and the first after the break are ringed β€” the convention used in the wdttest-* family. In depth figures the gap itself carries a neutral band labelled no samples, and a Marine Isotope Stage with only one interpolatable boundary is drawn hatched, up to the edge of the data. A stage lying entirely inside a gap gets no band at all: there is no depth it could be assigned to.

A dashed segment states no samples here β€” not no ice accumulated here.

Figure captions

EPICA/captions.yaml carries one entry per figure: caption, optionally captiondetail, the licence and the source DOIs. Field names follow captions.yaml in wdttest-tables.

The captions are generated from what was drawn β€” the chronology, the filter, the number of measurements, which stages a record cannot cover β€” because those are exactly the statements that go stale silently when a caption is maintained by hand. They can still be edited: each entry keeps the last generated text under generated, and once caption differs from it the caption is treated as the author's and left alone, while generated is refreshed so the diff shows where prose and data have drifted apart.

RDF/Linked Open Data (TTL)

Core Ontology:

  • ontology/geo_lod_core.ttl β€” Shared base classes (PalaeoclimateObservation, SamplingLocation, etc.)

EPICA:

  • EPICA/rdf/epica_ontology.ttl β€” EPICA classes (IceCoreObservation, DrillingSite, Borehole, …)
  • EPICA/rdf/epica_dome_c.ttl β€” 4,904 observations over five records, one site with two boreholes, 814 core sections, 5,587 time positions, 4,904 stage memberships, 77 stage boundaries carried into depth
  • 187,554 triples

SISAL:

  • SISAL/rdf/sisal_ontology.ttl β€” SISAL-specific classes (SpeleothemObservation, Cave, etc.)
  • SISAL/rdf/sisal_sites.ttl β€” All 365 SISAL v3 caves with WGS84 geometries and measurement counts (4,765 triples). Separate from the core graph so that a consumer after positions does not have to parse the measurements
  • SISAL/rdf/sisal_site_annotations.ttl β€” geo-lod's archaeological, Wikidata and UNESCO reading of those caves, with its own source node
  • SISAL/rdf/sisal_144_botuvera_data.ttl β€” 907 δ¹⁸O + 907 δ¹³C observations (21,795 triples)
  • SISAL/rdf/sisal_145_corchia_data.ttl β€” 1,234 δ¹⁸O + 1,234 δ¹³C observations (29,651 triples)
  • SISAL/rdf/sisal_140_sanbao_data.ttl β€” 5,832 δ¹⁸O observations (70,075 triples)
  • SISAL/rdf/sisal_275_buracagloriosa_data.ttl β€” 1,137 δ¹⁸O + 1,137 δ¹³C observations (27,327 triples)
  • SISAL/rdf/sisal_all_data.ttl β€” Combined file (152,169 triples total)

Maps live in maps/ beside the plots/ of each strand: EPICA/maps/epica_dome_c_map (Antarctica, polar stereographic), SISAL/maps/sisal_sites_map* (world and one per climate system), CI/maps/ci_findspots_map, ci_findspots_campania and ci_findspots_certainty, and maps/geo_lod_sites_map at the root, which is read from the RDF rather than from the input tables. Land polygons: Natural Earth 1:50m, public domain, in data/raw/basemap/.

Every figure is written twice: an SVG, which has no resolution, and a JPG whose quality follows main.py --dpi β€” draft (100 dpi) by default, print (300 dpi and at least 3000 px on the shorter side) for anything that goes to a printer. A release run (--bundle-format release) raises draft to print on its own.

CI (Campanian Ignimbrite):

  • CI/rdf/ci_findspots.ttl β€” Findspot data with GeoSPARQL geometries and PROV-O provenance
  • CI/rdf/ci_site_annotations.ttl β€” geo-lod's archaeological reading, kept apart from the source data

Mermaid Diagrams (Ontology Visualisation)

All diagrams generated in ontology/:

  • mermaid_taxonomy.mermaid β€” Complete class hierarchy (Core + EPICA + SISAL)

    • Includes external ontologies (SOSA, GeoSPARQL, DCAT, PROV)
    • LR (left-right) layout for readability
  • mermaid_instance_epica.mermaid β€” EPICA named individuals

    • EPICA Dome C site, ice core sample, chronology
    • Green color scheme (#d4edda)
  • mermaid_instance_sisal.mermaid β€” SISAL named individuals

    • 305 cave sites, FeatureCollections, archaeological cave sites
    • Yellow/brown color scheme (#fff3cd)
  • mermaid_instance_ci.mermaid β€” CI named individuals

    • Campanian Ignimbrite volcanic event, findspots, archaeological sites
    • Terracotta color scheme (#fce8d5)

Rendering to PNG:

# Install Mermaid CLI
npm install -g @mermaid-js/mermaid-cli

# Generate PNG images
mmdc -i ontology/mermaid_taxonomy.mermaid -o img/taxonomy.png
mmdc -i ontology/mermaid_instance_epica.mermaid -o img/instance_epica.png
mmdc -i ontology/mermaid_instance_sisal.mermaid -o img/instance_sisal.png
mmdc -i ontology/mermaid_instance_ci.mermaid -o img/instance_ci.png

πŸ–ΌοΈ RDF Model Visualisations

Ontology Taxonomy

Ontology Class Hierarchy

Complete class hierarchy showing Core, EPICA, and SISAL classes with external ontology integration (SOSA, GeoSPARQL, DCAT, PROV)

EPICA Instance Model

EPICA RDF Model

EPICA Dome C drilling site with ice core sample, observations, and chronology

SISAL Instance Model

SISAL RDF Model

SISAL cave sites (305 caves) organized in GeoSPARQL FeatureCollections

πŸ” SPARQL Queries

After export, you can load the TTL files into a triplestore (e.g., Apache Jena Fuseki, GraphDB) and query them:

All Sites (EPICA + SISAL)

PREFIX geolod: <http://w3id.org/geo-lod/>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?site ?label ?wkt
WHERE {
  ?collection rdfs:member ?site .
  ?site rdfs:label ?label ;
        geo:hasGeometry/geo:asWKT ?wkt .
}

Result: 380 sites β€” 305 SISAL caves, 74 Campanian Ignimbrite findspots and the EPICA Dome C drilling site. The two EPICA boreholes are not members of a collection; they hang below the site with crm:P89_falls_within.

EPICA CHβ‚„ observations, with the chronology their age belongs to

PREFIX geolod: <http://w3id.org/geo-lod/>
PREFIX epica:  <http://w3id.org/geo-lod/epica/>
PREFIX time:   <http://www.w3.org/2006/time#>

SELECT ?obs ?age ?trs ?value ?smoothed
WHERE {
  ?obs geolod:measurementType epica:measurement_type_ch4 ;
       geolod:hasTimePosition ?tp ;
       geolod:ageChronology ?trs ;
       geolod:measuredValue ?value ;
       geolod:smoothedValue_rollingMedian ?smoothed .
  ?tp time:hasTRS ?trs ;
      time:numericPosition ?age .
}
ORDER BY ?age

Result: 736 observations. Binding ?trs twice is what pins the age to the leading chronology; drop the second binding and each observation returns twice, once on EDC2 and once on the older EDC1 reading the source also publishes.

The same stage boundary in five depth axes

PREFIX geolod: <http://w3id.org/geo-lod/>
PREFIX crm:    <http://www.cidoc-crm.org/cidoc-crm/>
PREFIX mis:    <http://w3id.org/geo-lod/vocab/mis/>

SELECT ?trs ?depth
WHERE {
  ?a crm:P140_assigned_attribute_to mis:MIS_5 ;
     crm:P177_assigned_property_of_type geolod:MISBoundaryDepth ;
     crm:P141_assigned ?position ;
     geolod:inChronology ?trs .
  ?position geolod:atDepth_m ?depth .
}
ORDER BY ?depth

Result: 1733.94 m on AICC2023-ice, 1739.87 m on EDC3-ice, 1755.63 m on EDC2-ice, 1759.64 m on AICC2023-gas, 1782.26 m on EDC2-gas. The three ice-age scales cluster; the two gas-age scales sit systematically deeper.

SISAL Sites with Sample Counts

PREFIX geolod: <http://w3id.org/geo-lod/>

SELECT ?cave ?name ?d18o_count ?d13c_count
WHERE {
  ?cave a geolod:Cave ;
        rdfs:label ?name ;
        geolod:countD18OSamples ?d18o_count ;
        geolod:countD13CSamples ?d13c_count .
}
ORDER BY DESC(?d18o_count)

Result: 305 caves with sample counts

πŸ› οΈ Dependencies

pip install numpy pandas matplotlib scipy rdflib pyshacl pyyaml

Optional (for Mermaid PNG rendering):

npm install -g @mermaid-js/mermaid-cli

πŸ“ Ontology Overview

Class Hierarchy

geolod:PalaeoclimateObservation
  β”œβ”€β”€ geolod:IceCoreObservation (EPICA β€” five proxies)
  └── geolod:SpeleothemObservation (SISAL)
        β”œβ”€β”€ geolod:Delta18OSpeleothemObservation
        └── geolod:Delta13CSpeleothemObservation

geolod:SamplingLocation
  β”œβ”€β”€ geolod:DrillingSite (EPICA)
  β”œβ”€β”€ geolod:Borehole (EPICA β€” the hole inside the site)
  β”œβ”€β”€ geolod:Cave (SISAL)
  β”‚     └── geolod:ArchaeologicalCaveSite
  └── geolod:CIFindspot (CI)
        └── geolod:CIArchaeologicalSite

geolod:PalaeoclimateSample
  β”œβ”€β”€ geolod:IceCore (EPICA)
  β”œβ”€β”€ geolod:SampleSection (a bounded interval, only where a source states one)
  └── geolod:Speleothem (SISAL)

geolod:Chronology  βŠ‘ time:TRS
  β”œβ”€β”€ geolod:IceCoreChronology (EPICA)
  └── geolod:UThChronology (SISAL)

crm:E13_Attribute_Assignment
  β”œβ”€β”€ geolod:MISAttributeAssignment      (assigns a property TO a stage)
  └── geolod:MISMembershipAssignment     (assigns a stage TO an observation)

A chronology is also a time:TRS, so one node serves both roles: an observation names it with geolod:ageChronology, and the time:TimePosition of that observation names the same node with time:hasTRS.

Temporal reference systems

Under http://w3id.org/geo-lod/trs/, one per chronology and phase:

TRS Used by
EDC1-gas CHβ‚„, kept as the alternative reading alongside EDC2
EDC2-gas CHβ‚„
EDC2-ice Ξ΄D
EDC3-ice dust
AICC2023-gas δ¹⁸O of Oβ‚‚
AICC2023-ice Ξ΄Oβ‚‚/Nβ‚‚
LR04, Railsback2015 the MIS vocabulary

The gas/ice split is not bookkeeping. At a given depth the air trapped in the bubbles is younger than the ice around it, so the same published boundary age falls at a different depth depending on which branch a record uses. Read as one scale, CHβ‚„ and Ξ΄D place the beginning of MIS 5 twenty-seven metres apart with nothing in the graph to explain why.

FeatureCollections (GeoSPARQL)

  • geolod:EPICA_DrillingSite_Collection β€” 1 member
  • geolod:SISAL_Cave_Collection β€” 305 members
  • geolod:SISAL_ArchaeologicalCave_Collection β€” 37 members
  • geolod:AllPalaeoclimateSites_Collection β€” 306 members
  • geolod:CIFindspotCollection β€” CI findspots

🌐 W3ID URIs

All resources use persistent W3ID.org URIs:

Classes and properties stay flat under the base namespace; instance data lives in one branch per strand.

Path Content
http://w3id.org/geo-lod/ core ontology
http://w3id.org/geo-lod/epica/ ice-core instance data
http://w3id.org/geo-lod/sisal/ speleothem instance data
http://w3id.org/geo-lod/ci/ Campanian Ignimbrite findspots
http://w3id.org/geo-lod/vocab/mis/ Marine Isotope Stage vocabulary
http://w3id.org/geo-lod/trs/ temporal reference systems

Examples: …/epica/site_dome_c, …/epica/obs_ch4_0001, …/epica/tp_ch4_0001, …/trs/AICC2023-gas, …/vocab/mis/MIS_5e.

πŸ“ˆ Statistics

EPICA Dome C

  • 1 drilling site, two boreholes (75.10Β°S/123.35Β°E and 75.102Β°S/123.395Β°E β€” both published, neither discarded)
  • 4,904 observations across five records: 736 CHβ‚„, 814 Ξ΄D, 1,154 dust, 1,378 δ¹⁸O of Oβ‚‚, 822 Ξ΄Oβ‚‚/Nβ‚‚
  • Time span: 0–805.8 ka BP Β· depth range: 8.5–3,191.1 m
  • 187,554 RDF triples

SISAL

  • 305 cave sites worldwide (37 typed as geolod:ArchaeologicalCaveSite, 27 with Wikidata owl:sameAs, 7 UNESCO World Heritage)
  • 9,110 observations in four example caves
  • 152,169 RDF triples

Bundle

  • 354,901 triples, 57 distinct classes, 121 distinct properties
  • CIDOC-CRM coverage complete, 0 SHACL violations

♻️ Reproducibility

Two consecutive runs produce byte-identical output β€” 155 generated files at the time of writing; only pipeline_report.txt differs, since it logs wall-clock times. Four things make that work:

  • No clock in the output. No generator reads the current time. Dates come from GEO_LOD_RELEASE in geo_lod_utils.py.
  • Content fingerprints instead of run timestamps. Every generated dataset carries owl:versionInfo with a SHA-256 over its input data and generator script, and a prov:Activity naming each input with its own checksum. The fingerprint changes when the data or the model changes β€” and only then, so a dump can be checked against the state it claims to come from.
  • Deterministic figures. svg.hashsalt fixes matplotlib's clip-path ids and the SVG metadata date is suppressed.
  • Line endings that survive a checkout. SVGs are written through a binary file handle (geo_lod_figures.save_figure). Matplotlib otherwise opens the target in text mode, and Python then writes CRLF on Windows while .gitattributes stores LF β€” leaving every figure permanently different from its own committed form. The same rule applies to the log files.

πŸ•°οΈ Marine Isotope Stage Vocabulary

ontology/vocab/mis.ttl holds 315 concepts (228 stages, 87 substages) with 792 boundary assignments, generated from the primary sources in data/raw/mis/. Railsback et al. (2015) is the leading scheme wherever it reaches; beyond its coverage of 1013.1 ka BP the LR04 boundaries of Lisiecki & Raymo (2005) take over. The two sources disagree β€” LR04 puts the 5/6 boundary at 130 ka, Railsback puts 5e/6a at 132.2 ka β€” and that disagreement is kept rather than resolved: each reading is its own crm:E13_Attribute_Assignment with its own dct:source, marked geolod:LeadingAssignment or geolod:AlternativeAssignment. Filtering on the former yields one consistent age axis without having to know where a source's coverage ends.

The same run writes dist/mis_stages.csv (one row per concept, leading values) and dist/mis_assignments.csv (one row per assignment, both readings) for figures and age-axis code, so plots and RDF cannot drift apart.

πŸ“– Literature

EPICA:

SISAL:

EPICA chronologies:

MIS Boundaries:

πŸ› Troubleshooting

Import Error: geo_lod_utils not found

The scripts automatically set PYTHONPATH to include the ontology/ directory. If you still get import errors:

  1. Check structure:

    project/
    β”œβ”€β”€ main.py
    β”œβ”€β”€ EPICA/
    β”‚   └── plot_epica_from_tab.py
    β”œβ”€β”€ SISAL/
    β”‚   └── plot_sisal_from_csv.py
    └── ontology/
        └── geo_lod_utils.py  ← must be here!
    
  2. Run via main.py (not individual scripts):

    python main.py

No Mermaid diagrams generated

If ontology/*.mermaid files are missing:

  • Check pipeline_report.txt for import errors
  • Ensure geo_lod_utils.py is in ontology/ directory
  • Run with --clean flag: python main.py --clean

No data found

Primary sources live under data/raw/, read-only:

ls data/raw/epica/*.tab data/raw/mis/*.csv

Required for EPICA: EDC_CH4.tab, EPICA_Dome_C_dD.tab, EPICA_Dome_C_dust.tab, EPICA_Dome_C_d18O.tab, EPICA_Dome_C_do2n2.tab. The SISAL CSV exports are still read from SISAL/; they move to data/raw/ with the SISAL rebuild.

ModuleNotFoundError: No module named 'yaml'

The caption layer reads its own output back, to recognise hand-written captions. Install PyYAML:

pip install pyyaml

RDF export not working

β†’ Install rdflib:

pip install rdflib

🧭 Working document

PRIMER.md is the working plan: the steps S0 to S5, what each one changes, and a decision log recording every choice with its date and its reason. Where this README says why something is the way it is, the long version is there. It is in German β€” an internal document that happens to lie open, not one addressed outwards.

🀝 Authors

Florian Thiery ORCID: https://orcid.org/0000-0002-3246-3531 Research Squirrel Engineers, Mainz β€” LEIZA, Leibniz-Zentrum fΓΌr ArchΓ€ologie

Fiona Schenk Johannes Gutenberg-UniversitΓ€t Mainz β€” geoscientific input on the speleothem and tephra side.

πŸ€– Use of AI

Large parts of the Python code in this repository, and parts of this README, were written with the assistance of a large language model (Anthropic's Claude) in an iterative session with the author, who set the task, took every design decision and reviewed the result. The RDF, the ontology and the figures are produced by that code; none of the measurements, and none of the statements about the data, were generated by a model.

The point of saying so is not disclosure for its own sake. Code written this way is fluent, and fluent code reads as correct whether or not it is β€” so this repository leans on checks that do not care how something was written:

  • The pipeline is validated on every run: CIDOC-CRM coverage over every typed instance, SHACL over the whole bundle, and a triple and class inventory in pipeline_report.txt.
  • Two consecutive runs must produce byte-identical output. A file that always shows a diff trains everyone to skip its diff, and then a real change passes unnoticed.
  • Every generated dataset carries a fingerprint over its input data and its generator script, so a dump can be checked against the state it claims to come from.
  • Every decision is recorded in PRIMER.md with its reason and its date, including the ones later revised. Several of the corrections logged there β€” a filter running across a data gap, an axis clipping six per cent of a record, a chronology conflated with another β€” were bugs in exactly this kind of plausible-looking code, found by the checks rather than by reading.

Responsibility for the content, and for the errors that remain, lies with the authors.

πŸ“„ Licence

Code: MIT (see LICENSE).

The data are not ours to license. Each PANGAEA record keeps its own terms β€” three of the EPICA files are CC BY 3.0, two CC BY 4.0 β€” and the licence and DOI of each are recorded per figure in EPICA/captions.yaml and per dataset in the RDF. SISALv3 is cited via Kaushal et al. (2024). Anything reused from this repository should carry those attributions on, not this one alone.

Releases

Contributors

Languages