gea-program 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gea/BENCH_TEST_PROTOCOL.md +97 -0
- gea/IMPORT_RECORD.md +61 -0
- gea/__init__.py +160 -0
- gea/__main__.py +661 -0
- gea/acceptance_tests.py +1315 -0
- gea/accuracy_statement.py +181 -0
- gea/alarm_engine.py +307 -0
- gea/bench.py +144 -0
- gea/blind_harness.py +108 -0
- gea/case_study.py +229 -0
- gea/catalog/acex_lomonosov_age_depth_model.provenance.json +23 -0
- gea/catalog/acex_lomonosov_age_depth_model.txt +28 -0
- gea/catalog/agassiz77_canada_temperature.csv +68 -0
- gea/catalog/agassiz77_canada_temperature.provenance.json +17 -0
- gea/catalog/barbados_110_consolidation.provenance.json +24 -0
- gea/catalog/barbados_110_consolidation.txt +91 -0
- gea/catalog/bengal_u1452_grain_size.provenance.json +20 -0
- gea/catalog/bengal_u1452_grain_size.txt +252 -0
- gea/catalog/blake_164_methane_isotopes.provenance.json +22 -0
- gea/catalog/blake_164_methane_isotopes.txt +68 -0
- gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +23 -0
- gea/catalog/chicxulub_m0077a_pwave_velocity.txt +735 -0
- gea/catalog/collingwood_1_28_ks_complete.las +128 -0
- gea/catalog/collingwood_1_28_ks_complete.provenance.json +17 -0
- gea/catalog/costa_rica_odp_friction_envelope.provenance.json +24 -0
- gea/catalog/costa_rica_odp_friction_envelope.txt +57 -0
- gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +21 -0
- gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +94 -0
- gea/catalog/dsdp_504b_physical_properties.provenance.json +24 -0
- gea/catalog/dsdp_504b_physical_properties.txt +82 -0
- gea/catalog/dsdp_504b_sound_velocity.provenance.json +21 -0
- gea/catalog/dsdp_504b_sound_velocity.txt +81 -0
- gea/catalog/elgygytgyn_5011_turbidites.provenance.json +20 -0
- gea/catalog/elgygytgyn_5011_turbidites.txt +193 -0
- gea/catalog/epica_domec_co2_800kyr.provenance.json +21 -0
- gea/catalog/epica_domec_co2_800kyr.txt +265 -0
- gea/catalog/fram_909_organic_petrography.provenance.json +22 -0
- gea/catalog/fram_909_organic_petrography.txt +40 -0
- gea/catalog/gbr_325_coral_uth_ages.provenance.json +23 -0
- gea/catalog/gbr_325_coral_uth_ages.txt +71 -0
- gea/catalog/gisp2_greenland_temperature.csv +599 -0
- gea/catalog/gisp2_greenland_temperature.provenance.json +17 -0
- gea/catalog/gom_308_t2p_insitu.provenance.json +21 -0
- gea/catalog/gom_308_t2p_insitu.txt +40 -0
- gea/catalog/guaymas_385_dom_d13c.provenance.json +21 -0
- gea/catalog/guaymas_385_dom_d13c.txt +103 -0
- gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +26 -0
- gea/catalog/hikurangi_u1520_friction_insitu.txt +74 -0
- gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +22 -0
- gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +60 -0
- gea/catalog/iodp_u1324_pore_pressure.provenance.json +22 -0
- gea/catalog/iodp_u1324_pore_pressure.txt +42 -0
- gea/catalog/jfast_c0019_slow_slip_events.provenance.json +28 -0
- gea/catalog/jfast_c0019_slow_slip_events.txt +35 -0
- gea/catalog/kennetcook_2_p129_excerpt.las +139 -0
- gea/catalog/kennetcook_2_p129_excerpt.provenance.json +17 -0
- gea/catalog/ktb_hb_bhgm_density.dat +227 -0
- gea/catalog/ktb_hb_bhgm_density.provenance.json +22 -0
- gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +20 -0
- gea/catalog/ktb_hb_complog_6020_excerpt.txt +72 -0
- gea/catalog/ktb_hb_hlog246_temperature.dat +1636 -0
- gea/catalog/ktb_hb_hlog246_temperature.provenance.json +22 -0
- gea/catalog/ktb_hb_rockmech_compress.dat +33 -0
- gea/catalog/ktb_hb_rockmech_compress.provenance.json +22 -0
- gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +2817 -0
- gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +20 -0
- gea/catalog/ktb_vb_rockmech_compress.dat +125 -0
- gea/catalog/ktb_vb_rockmech_compress.provenance.json +22 -0
- gea/catalog/ktb_vb_vlog251_temperature.dat +1127 -0
- gea/catalog/ktb_vb_vlog251_temperature.provenance.json +24 -0
- gea/catalog/l06_06_nl_survey.csv +201 -0
- gea/catalog/l06_06_nl_survey.provenance.json +17 -0
- gea/catalog/l07_01_nl_excerpt.las +90 -0
- gea/catalog/l07_01_nl_excerpt.provenance.json +17 -0
- gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +21 -0
- gea/catalog/mariana_1200_serpentinite_geochem.txt +63 -0
- gea/catalog/med_160_sapropels.provenance.json +22 -0
- gea/catalog/med_160_sapropels.txt +43 -0
- gea/catalog/nankai_megasplay_shear_strength.provenance.json +26 -0
- gea/catalog/nankai_megasplay_shear_strength.txt +42 -0
- gea/catalog/odp_1027b_thermal_conductivity.provenance.json +21 -0
- gea/catalog/odp_1027b_thermal_conductivity.txt +52 -0
- gea/catalog/odp_1027c_cork_temperature.provenance.json +20 -0
- gea/catalog/odp_1027c_cork_temperature.txt +26 -0
- gea/catalog/odp_1165b_thermal_conductivity.provenance.json +22 -0
- gea/catalog/odp_1165b_thermal_conductivity.txt +102 -0
- gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +27 -0
- gea/catalog/odp_1274a_mantle_peridotite_mad.txt +44 -0
- gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +27 -0
- gea/catalog/odp_504b_dike_elastic_moduli.txt +85 -0
- gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +19 -0
- gea/catalog/odp_504b_leg137_borehole_fluids.txt +68 -0
- gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +28 -0
- gea/catalog/odp_735b_gabbro_elastic_moduli.txt +127 -0
- gea/catalog/peru_201_sulfate_reduction.provenance.json +22 -0
- gea/catalog/peru_201_sulfate_reduction.txt +322 -0
- gea/catalog/scorpio_e1_sa_excerpt.las +113 -0
- gea/catalog/scorpio_e1_sa_excerpt.provenance.json +17 -0
- gea/catalog/sumatra_362_cohesion.provenance.json +21 -0
- gea/catalog/sumatra_362_cohesion.txt +38 -0
- gea/catalog/university_6_17_no1_tx_excerpt.las +119 -0
- gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +17 -0
- gea/catalog/ursa_308_xrd_mineralogy.provenance.json +21 -0
- gea/catalog/ursa_308_xrd_mineralogy.txt +46 -0
- gea/catalog/volve_15_9_19_sr_excerpt.las +183 -0
- gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +16 -0
- gea/catalog/volve_15_9_19a_core_excerpt.csv +88 -0
- gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +18 -0
- gea/catalog/volve_f12_f14_production_excerpt.csv +167 -0
- gea/catalog/volve_f12_f14_production_excerpt.provenance.json +21 -0
- gea/catalog/walvis_208_petm_carbonate.provenance.json +21 -0
- gea/catalog/walvis_208_petm_carbonate.txt +268 -0
- gea/catalog/woodlark_1109_rock_eval.provenance.json +23 -0
- gea/catalog/woodlark_1109_rock_eval.txt +30 -0
- gea/cli.py +125 -0
- gea/client_reports.py +943 -0
- gea/config_versioning.py +133 -0
- gea/correlation.py +155 -0
- gea/dashboard.py +390 -0
- gea/deviation.py +70 -0
- gea/downhole_engine.py +395 -0
- gea/drift_monitor.py +310 -0
- gea/earth_model.py +230 -0
- gea/example_register_map.json +14 -0
- gea/fat_sat.py +68 -0
- gea/follower.py +98 -0
- gea/forward_model.py +139 -0
- gea/gamma.py +176 -0
- gea/gauge_specs.py +112 -0
- gea/gravity_reference.py +116 -0
- gea/inverse_engine.py +215 -0
- gea/matplotlib_demo.py +85 -0
- gea/modbus.py +229 -0
- gea/model_card.py +248 -0
- gea/operator_app.py +442 -0
- gea/ports.py +340 -0
- gea/profile_catalog.py +773 -0
- gea/project.py +213 -0
- gea/qt6_downhole_app.py +144 -0
- gea/quartz_hpht_extension.py +152 -0
- gea/reconciler.py +206 -0
- gea/rock_inventory.py +404 -0
- gea/sample_record.py +430 -0
- gea/sample_well_profile.csv +15 -0
- gea/sbom.py +116 -0
- gea/segy.py +181 -0
- gea/service_life.py +173 -0
- gea/shell.py +107 -0
- gea/sla_report.py +199 -0
- gea/store_forward.py +234 -0
- gea/strata_join.py +186 -0
- gea/survey_cmd.py +264 -0
- gea/survey_view.py +138 -0
- gea/telemetry.py +306 -0
- gea/tool_library.py +260 -0
- gea/well_assembler.py +457 -0
- gea/well_test_validation.py +369 -0
- gea_program-0.1.0.dist-info/METADATA +138 -0
- gea_program-0.1.0.dist-info/RECORD +163 -0
- gea_program-0.1.0.dist-info/WHEEL +5 -0
- gea_program-0.1.0.dist-info/entry_points.txt +2 -0
- gea_program-0.1.0.dist-info/licenses/LICENSE +373 -0
- gea_program-0.1.0.dist-info/top_level.txt +1 -0
gea/config_versioning.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
"""config_versioning — version-controlled configuration with export, import
|
|
5
|
+
and rollback (SOW 4.2.1.4 change and continuity; 4.2.24 audit trail).
|
|
6
|
+
|
|
7
|
+
Every configuration the program runs on - the well-test criteria file, the
|
|
8
|
+
alarm definitions, the tag catalogue, the drift-monitor thresholds - is
|
|
9
|
+
committed here as a numbered version with author, note, timestamp, sha256
|
|
10
|
+
and a key-level diff against the previous version. Nothing is ever deleted:
|
|
11
|
+
a rollback is a new version whose content equals an older one, recorded as
|
|
12
|
+
such. `export` writes any version to a file; `import_file` commits a file.
|
|
13
|
+
|
|
14
|
+
Store layout (one directory):
|
|
15
|
+
|
|
16
|
+
<dir>/<name>/v0001.json the content
|
|
17
|
+
<dir>/<name>/history.jsonl one line per version: meta + diff summary
|
|
18
|
+
|
|
19
|
+
Headless-safe: standard library only.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import hashlib
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _iso(dt: Optional[datetime] = None) -> str:
|
|
32
|
+
dt = dt or datetime.now(timezone.utc)
|
|
33
|
+
return dt.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _sha(obj: Any) -> str:
|
|
37
|
+
return hashlib.sha256(json.dumps(obj, sort_keys=True, default=str).encode('utf-8')).hexdigest()[:16]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _flatten(obj: Any, prefix: str = '') -> Dict[str, Any]:
|
|
41
|
+
out: Dict[str, Any] = {}
|
|
42
|
+
if isinstance(obj, dict):
|
|
43
|
+
for k, v in obj.items():
|
|
44
|
+
out.update(_flatten(v, f'{prefix}{k}.'))
|
|
45
|
+
elif isinstance(obj, list):
|
|
46
|
+
for i, v in enumerate(obj):
|
|
47
|
+
out.update(_flatten(v, f'{prefix}[{i}].'))
|
|
48
|
+
else:
|
|
49
|
+
out[prefix[:-1]] = obj
|
|
50
|
+
return out
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def diff(old: Any, new: Any) -> dict:
|
|
54
|
+
a, b = _flatten(old), _flatten(new)
|
|
55
|
+
added = sorted(k for k in b if k not in a)
|
|
56
|
+
removed = sorted(k for k in a if k not in b)
|
|
57
|
+
changed = sorted(k for k in a if k in b and a[k] != b[k])
|
|
58
|
+
return {'added': added, 'removed': removed,
|
|
59
|
+
'changed': [{'key': k, 'before': a[k], 'after': b[k]} for k in changed],
|
|
60
|
+
'n_changes': len(added) + len(removed) + len(changed)}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConfigStore:
|
|
64
|
+
def __init__(self, root: str):
|
|
65
|
+
self.root = str(root)
|
|
66
|
+
os.makedirs(self.root, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
def _dir(self, name: str) -> str:
|
|
69
|
+
d = os.path.join(self.root, name)
|
|
70
|
+
os.makedirs(d, exist_ok=True)
|
|
71
|
+
return d
|
|
72
|
+
|
|
73
|
+
def history(self, name: str) -> List[dict]:
|
|
74
|
+
p = os.path.join(self._dir(name), 'history.jsonl')
|
|
75
|
+
if not os.path.exists(p):
|
|
76
|
+
return []
|
|
77
|
+
with open(p, encoding='utf-8') as f:
|
|
78
|
+
return [json.loads(l) for l in f if l.strip()]
|
|
79
|
+
|
|
80
|
+
def latest_version(self, name: str) -> int:
|
|
81
|
+
h = self.history(name)
|
|
82
|
+
return h[-1]['version'] if h else 0
|
|
83
|
+
|
|
84
|
+
def get(self, name: str, version: Optional[int] = None) -> Any:
|
|
85
|
+
v = version or self.latest_version(name)
|
|
86
|
+
if v == 0:
|
|
87
|
+
raise KeyError(f'no versions of {name}')
|
|
88
|
+
with open(os.path.join(self._dir(name), f'v{v:04d}.json'), encoding='utf-8') as f:
|
|
89
|
+
return json.load(f)
|
|
90
|
+
|
|
91
|
+
def commit(self, name: str, content: Any, author: str, note: str = '', now: Optional[datetime] = None,
|
|
92
|
+
rollback_of: Optional[int] = None) -> dict:
|
|
93
|
+
prev_v = self.latest_version(name)
|
|
94
|
+
prev = self.get(name, prev_v) if prev_v else None
|
|
95
|
+
if prev is not None and _sha(prev) == _sha(content) and rollback_of is None:
|
|
96
|
+
return {'name': name, 'version': prev_v, 'unchanged': True, 'sha256': _sha(content)}
|
|
97
|
+
v = prev_v + 1
|
|
98
|
+
with open(os.path.join(self._dir(name), f'v{v:04d}.json'), 'w', encoding='utf-8') as f:
|
|
99
|
+
json.dump(content, f, indent=1, sort_keys=True, default=str)
|
|
100
|
+
meta = {'name': name, 'version': v, 'timestamp_utc': _iso(now), 'author': author, 'note': note,
|
|
101
|
+
'sha256': _sha(content), 'previous_version': prev_v or None,
|
|
102
|
+
'diff': diff(prev, content) if prev is not None else {'added': sorted(_flatten(content)), 'removed': [], 'changed': [], 'n_changes': len(_flatten(content))},
|
|
103
|
+
'rollback_of': rollback_of}
|
|
104
|
+
with open(os.path.join(self._dir(name), 'history.jsonl'), 'a', encoding='utf-8') as f:
|
|
105
|
+
f.write(json.dumps(meta, default=str) + '\n')
|
|
106
|
+
return meta
|
|
107
|
+
|
|
108
|
+
def import_file(self, name: str, path: str, author: str, note: str = '', now: Optional[datetime] = None) -> dict:
|
|
109
|
+
with open(path, encoding='utf-8') as f:
|
|
110
|
+
content = json.load(f)
|
|
111
|
+
return self.commit(name, content, author, note or f'imported from {os.path.basename(path)}', now=now)
|
|
112
|
+
|
|
113
|
+
def export(self, name: str, path: str, version: Optional[int] = None) -> str:
|
|
114
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
115
|
+
json.dump(self.get(name, version), f, indent=1, sort_keys=True)
|
|
116
|
+
return path
|
|
117
|
+
|
|
118
|
+
def rollback(self, name: str, to_version: int, author: str, note: str = '', now: Optional[datetime] = None) -> dict:
|
|
119
|
+
target = self.get(name, to_version)
|
|
120
|
+
return self.commit(name, target, author, note or f'rollback to v{to_version:04d}', now=now, rollback_of=to_version)
|
|
121
|
+
|
|
122
|
+
def names(self) -> List[str]:
|
|
123
|
+
return sorted(d for d in os.listdir(self.root) if os.path.isdir(os.path.join(self.root, d)))
|
|
124
|
+
|
|
125
|
+
def summary(self) -> List[dict]:
|
|
126
|
+
out = []
|
|
127
|
+
for n in self.names():
|
|
128
|
+
h = self.history(n)
|
|
129
|
+
if h:
|
|
130
|
+
out.append({'name': n, 'versions': len(h), 'latest': h[-1]['version'], 'sha256': h[-1]['sha256'],
|
|
131
|
+
'last_change_utc': h[-1]['timestamp_utc'], 'last_author': h[-1]['author'],
|
|
132
|
+
'rollbacks': sum(1 for e in h if e.get('rollback_of'))})
|
|
133
|
+
return out
|
gea/correlation.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
"""correlation - Part 4 of the subsurface surveying tool: WELL-TO-WELL
|
|
5
|
+
CORRELATION (v1.81.0), with the honest finding stated up front.
|
|
6
|
+
|
|
7
|
+
THE FINDING (2026-08-29, and re-verified on every run): with 29 globally
|
|
8
|
+
scattered sites, the library currently supports ZERO cross-site depth-frame
|
|
9
|
+
correlations - every candidate pair (CaCO3 Barbados x Walvis, thermal
|
|
10
|
+
conductivity Hikurangi x Prydz, friction coefficient Barbados x Costa Rica)
|
|
11
|
+
has overlapping depth RANGES but too few coinciding SAMPLES (max 3 common
|
|
12
|
+
bins against a floor of 5). The engine reports that refusal census instead
|
|
13
|
+
of manufacturing correlations, and the machinery stands ready for the data
|
|
14
|
+
that will change the answer (co-located suites, twin holes, denser columns).
|
|
15
|
+
|
|
16
|
+
WHERE THE REAL STRUCTURE LIVES - THE TIME FRAME: four sites carry archive
|
|
17
|
+
age models, and one (the ACEX Lomonosov Ridge age-depth model, 0-55,904 ka)
|
|
18
|
+
is the library's MASTER CHRONOLOGY, whose window overlaps every other
|
|
19
|
+
age-bearing site (Bengal 0.5-193.5 ka; EPICA 611-799 ka; Fram 12,050-17,370
|
|
20
|
+
ka). Time is the Earth Model's fourth axis, and this module registers it.
|
|
21
|
+
|
|
22
|
+
DISTANCE HONESTY (doctrine): a statistical correlation between sites more
|
|
23
|
+
than CONTINUITY_KM apart is NEVER presented as geological continuity -
|
|
24
|
+
the continuity_claim field says 'NONE' with the distance printed. Only
|
|
25
|
+
twin holes (< CONTINUITY_KM) may claim physical continuity, and none
|
|
26
|
+
currently qualify with shared properties.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import collections
|
|
32
|
+
import math
|
|
33
|
+
import re
|
|
34
|
+
import statistics
|
|
35
|
+
from typing import Dict, List
|
|
36
|
+
|
|
37
|
+
from .earth_model import EarthModel, haversine_km
|
|
38
|
+
|
|
39
|
+
CONTINUITY_KM = 5.0 # beyond this, correlation is statistics, not strata
|
|
40
|
+
MIN_COMMON_BINS = 5 # below this, the pair REFUSES (disclosed)
|
|
41
|
+
TARGET_BINS = 8 # default overlap resolution (disclosed)
|
|
42
|
+
|
|
43
|
+
_AGE_WORD = re.compile(r'\bage\b', re.IGNORECASE)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _family(prop: str) -> str:
|
|
47
|
+
return prop.split(' [')[0].split(' (')[0].strip().lower()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def time_frame(em: EarthModel = None) -> Dict:
|
|
51
|
+
"""The library's fourth axis: every archive age model, its window, the
|
|
52
|
+
pairwise epoch overlaps, and the master chronology (the site whose
|
|
53
|
+
window overlaps the most others)."""
|
|
54
|
+
em = em or EarthModel()
|
|
55
|
+
ages = []
|
|
56
|
+
for s in em.sites.values():
|
|
57
|
+
for r in s.records:
|
|
58
|
+
if _AGE_WORD.search(r.property) and 'ka' in (r.unit or '').lower():
|
|
59
|
+
vals = [v for v in r.values if v == v]
|
|
60
|
+
if vals:
|
|
61
|
+
ages.append({'site': s.key, 'entry': r.entry,
|
|
62
|
+
'property': r.property, 'unit': r.unit,
|
|
63
|
+
'age_min_ka': min(vals), 'age_max_ka': max(vals)})
|
|
64
|
+
overlaps = []
|
|
65
|
+
for i in range(len(ages)):
|
|
66
|
+
for j in range(i + 1, len(ages)):
|
|
67
|
+
a, b = ages[i], ages[j]
|
|
68
|
+
if a['site'] == b['site']:
|
|
69
|
+
continue
|
|
70
|
+
lo = max(a['age_min_ka'], b['age_min_ka'])
|
|
71
|
+
hi = min(a['age_max_ka'], b['age_max_ka'])
|
|
72
|
+
if hi > lo:
|
|
73
|
+
overlaps.append({'a': a['site'], 'b': b['site'],
|
|
74
|
+
'epoch_overlap_ka': (lo, hi),
|
|
75
|
+
'span_ka': round(hi - lo, 3)})
|
|
76
|
+
counts = collections.Counter()
|
|
77
|
+
for o in overlaps:
|
|
78
|
+
counts[o['a']] += 1
|
|
79
|
+
counts[o['b']] += 1
|
|
80
|
+
master = counts.most_common(1)[0] if counts else (None, 0)
|
|
81
|
+
return {'age_bearing_records': ages, 'n_sites': len({a['site'] for a in ages}),
|
|
82
|
+
'epoch_overlaps': overlaps,
|
|
83
|
+
'master_chronology': {'site': master[0], 'overlaps_others': master[1]}}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def depth_frame_pairs(em: EarthModel = None, target_bins: int = TARGET_BINS,
|
|
87
|
+
min_common: int = MIN_COMMON_BINS) -> Dict:
|
|
88
|
+
"""Every cross-site same-property pair with overlapping z_ref: correlated
|
|
89
|
+
where the samples support it, REFUSED with counts where they do not -
|
|
90
|
+
and today they do not, anywhere. Nothing is interpolated between
|
|
91
|
+
archives; bins carry only real samples."""
|
|
92
|
+
em = em or EarthModel()
|
|
93
|
+
recs = []
|
|
94
|
+
for s in em.sites.values():
|
|
95
|
+
for r in s.records:
|
|
96
|
+
if r.z_ref_m and r.values:
|
|
97
|
+
pts = [(z, v) for z, v in zip(r.z_ref_m, r.values) if v == v]
|
|
98
|
+
if len(pts) >= 5:
|
|
99
|
+
recs.append((s, r, pts))
|
|
100
|
+
fams = collections.defaultdict(list)
|
|
101
|
+
for s, r, pts in recs:
|
|
102
|
+
fams[_family(r.property)].append((s, r, pts))
|
|
103
|
+
ok, refused = [], []
|
|
104
|
+
for fname, group in fams.items():
|
|
105
|
+
for i in range(len(group)):
|
|
106
|
+
for j in range(i + 1, len(group)):
|
|
107
|
+
sa, ra, pa = group[i]
|
|
108
|
+
sb, rb, pb = group[j]
|
|
109
|
+
if sa.key == sb.key:
|
|
110
|
+
continue
|
|
111
|
+
lo = max(min(z for z, _ in pa), min(z for z, _ in pb))
|
|
112
|
+
hi = min(max(z for z, _ in pa), max(z for z, _ in pb))
|
|
113
|
+
if hi <= lo:
|
|
114
|
+
continue
|
|
115
|
+
w = (hi - lo) / target_bins
|
|
116
|
+
def _bins(pts):
|
|
117
|
+
b = collections.defaultdict(list)
|
|
118
|
+
for z, v in pts:
|
|
119
|
+
if lo <= z <= hi:
|
|
120
|
+
b[int((z - lo) // w)].append(v)
|
|
121
|
+
return {k: sum(v) / len(v) for k, v in b.items()}
|
|
122
|
+
ba, bb = _bins(pa), _bins(pb)
|
|
123
|
+
common = sorted(set(ba) & set(bb))
|
|
124
|
+
dist = haversine_km(sa.latitude, sa.longitude,
|
|
125
|
+
sb.latitude, sb.longitude)
|
|
126
|
+
base = {'property_family': fname, 'a': sa.key, 'b': sb.key,
|
|
127
|
+
'entries': (ra.entry, rb.entry),
|
|
128
|
+
'z_overlap_m': round(hi - lo, 1),
|
|
129
|
+
'common_bins': len(common), 'target_bins': target_bins,
|
|
130
|
+
'distance_km': round(dist, 1),
|
|
131
|
+
'continuity_claim': (
|
|
132
|
+
'TWIN_HOLE_ELIGIBLE' if dist < CONTINUITY_KM else
|
|
133
|
+
'NONE - statistical comparison only (%.0f km '
|
|
134
|
+
'exceeds the %.0f km continuity threshold)'
|
|
135
|
+
% (dist, CONTINUITY_KM))}
|
|
136
|
+
if len(common) < min_common:
|
|
137
|
+
base['status'] = 'REFUSED_THIN_DATA (min %d)' % min_common
|
|
138
|
+
refused.append(base)
|
|
139
|
+
continue
|
|
140
|
+
xa = [ba[k] for k in common]
|
|
141
|
+
xb = [bb[k] for k in common]
|
|
142
|
+
ma, mb = statistics.mean(xa), statistics.mean(xb)
|
|
143
|
+
den = math.sqrt(sum((x - ma) ** 2 for x in xa)
|
|
144
|
+
* (sum((y - mb) ** 2 for y in xb)))
|
|
145
|
+
base['status'] = 'OK'
|
|
146
|
+
base['pearson_r'] = ((sum((x - ma) * (y - mb)
|
|
147
|
+
for x, y in zip(xa, xb)) / den)
|
|
148
|
+
if den else float('nan'))
|
|
149
|
+
ok.append(base)
|
|
150
|
+
return {'ok': ok, 'refused': refused, 'n_ok': len(ok),
|
|
151
|
+
'n_refused': len(refused),
|
|
152
|
+
'finding': ('the library currently supports %d cross-site '
|
|
153
|
+
'depth-frame correlations; %d candidate pairs '
|
|
154
|
+
'refused thin - the honest census, not a failure'
|
|
155
|
+
% (len(ok), len(refused)))}
|