ledger: extract committed-file inventory table

This commit is contained in:
ledger committed 2026-11-02 07:17:41 +00:00
1 parent 01aac38c5a
commit e98c6bd260
6 files changed
+4545

No files matched your search

@@ -0,0 +1,11 @@
city,country,lat,lon,current_time,temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,weather_desc,wind_speed_10m,today_max,today_min,today_precip,today_sunrise,today_sunset
Berlin,Germany,52.52437,13.41053,2026-11-02T08:00,7.3,5.6,87,2,Partly cloudy,3.3,9.3,6.2,0.3,2026-11-02T07:04,2026-11-02T16:34
Mumbai,India,19.07283,72.88261,2026-11-02T12:30,34.1,38.0,41,1,Mainly clear,1.4,34.3,27.4,0.0,2026-11-02T06:39,2026-11-02T18:04
Dubai,United Arab Emirates,25.07725,55.30927,2026-11-02T11:00,33.2,35.5,34,0,Clear sky,4.8,35.2,24.0,0.0,2026-11-02T06:26,2026-11-02T17:37
Toronto,Canada,43.70643,-79.39864,2026-11-02T02:00,7.1,4.1,77,0,Clear sky,9.9,16.9,6.3,0.0,2026-11-02T06:54,2026-11-02T17:06
Buenos Aires,Argentina,-34.61315,-58.37723,2026-11-02T04:00,16.6,17.9,91,0,Clear sky,2.6,24.7,15.5,0.1,2026-11-02T05:50,2026-11-02T19:24
Jakarta,Indonesia,-6.21462,106.84513,2026-11-02T14:00,31.7,36.8,60,51,Light drizzle,7.0,33.9,25.2,2.5,2026-11-02T05:26,2026-11-02T17:46
Reykjavik,Iceland,64.13548,-21.89541,2026-11-02T07:00,9.3,5.3,86,3,Overcast,22.3,9.8,9.1,1.2,2026-11-02T09:13,2026-11-02T17:07
Lagos,Nigeria,6.45407,3.39467,2026-11-02T08:00,28.2,33.7,79,1,Mainly clear,5.2,31.2,27.4,1.0,2026-11-02T06:33,2026-11-02T18:26
Seoul,South Korea,37.566,126.9784,2026-11-02T16:00,13.3,10.4,49,0,Clear sky,9.1,13.6,6.9,0.0,2026-11-02T06:57,2026-11-02T17:33
Auckland,New Zealand,-36.84853,174.76349,2026-11-02T20:00,17.0,14.9,68,3,Overcast,16.5,18.1,14.8,0.0,2026-11-02T06:15,2026-11-02T19:54
1 city country lat lon current_time temperature_2m apparent_temperature relative_humidity_2m weather_code weather_desc wind_speed_10m today_max today_min today_precip today_sunrise today_sunset
2 Berlin Germany 52.52437 13.41053 2026-11-02T08:00 7.3 5.6 87 2 Partly cloudy 3.3 9.3 6.2 0.3 2026-11-02T07:04 2026-11-02T16:34
3 Mumbai India 19.07283 72.88261 2026-11-02T12:30 34.1 38.0 41 1 Mainly clear 1.4 34.3 27.4 0.0 2026-11-02T06:39 2026-11-02T18:04
4 Dubai United Arab Emirates 25.07725 55.30927 2026-11-02T11:00 33.2 35.5 34 0 Clear sky 4.8 35.2 24.0 0.0 2026-11-02T06:26 2026-11-02T17:37
5 Toronto Canada 43.70643 -79.39864 2026-11-02T02:00 7.1 4.1 77 0 Clear sky 9.9 16.9 6.3 0.0 2026-11-02T06:54 2026-11-02T17:06
6 Buenos Aires Argentina -34.61315 -58.37723 2026-11-02T04:00 16.6 17.9 91 0 Clear sky 2.6 24.7 15.5 0.1 2026-11-02T05:50 2026-11-02T19:24
7 Jakarta Indonesia -6.21462 106.84513 2026-11-02T14:00 31.7 36.8 60 51 Light drizzle 7.0 33.9 25.2 2.5 2026-11-02T05:26 2026-11-02T17:46
8 Reykjavik Iceland 64.13548 -21.89541 2026-11-02T07:00 9.3 5.3 86 3 Overcast 22.3 9.8 9.1 1.2 2026-11-02T09:13 2026-11-02T17:07
9 Lagos Nigeria 6.45407 3.39467 2026-11-02T08:00 28.2 33.7 79 1 Mainly clear 5.2 31.2 27.4 1.0 2026-11-02T06:33 2026-11-02T18:26
10 Seoul South Korea 37.566 126.9784 2026-11-02T16:00 13.3 10.4 49 0 Clear sky 9.1 13.6 6.9 0.0 2026-11-02T06:57 2026-11-02T17:33
11 Auckland New Zealand -36.84853 174.76349 2026-11-02T20:00 17.0 14.9 68 3 Overcast 16.5 18.1 14.8 0.0 2026-11-02T06:15 2026-11-02T19:54
@@ -0,0 +1,212 @@
{
"Berlin": {
"geocode": {
"lat": 52.52437,
"lon": 13.41053,
"country": "Germany"
},
"weather": {
"current_time": "2026-11-02T08:00",
"temperature_2m": 7.3,
"apparent_temperature": 5.6,
"relative_humidity_2m": 87,
"weather_code": 2,
"weather_desc": "Partly cloudy",
"wind_speed_10m": 3.3,
"today_max": 9.3,
"today_min": 6.2,
"today_precip": 0.3,
"today_sunrise": "2026-11-02T07:04",
"today_sunset": "2026-11-02T16:34"
}
},
"Mumbai": {
"geocode": {
"lat": 19.07283,
"lon": 72.88261,
"country": "India"
},
"weather": {
"current_time": "2026-11-02T12:30",
"temperature_2m": 34.1,
"apparent_temperature": 38.0,
"relative_humidity_2m": 41,
"weather_code": 1,
"weather_desc": "Mainly clear",
"wind_speed_10m": 1.4,
"today_max": 34.3,
"today_min": 27.4,
"today_precip": 0.0,
"today_sunrise": "2026-11-02T06:39",
"today_sunset": "2026-11-02T18:04"
}
},
"Dubai": {
"geocode": {
"lat": 25.07725,
"lon": 55.30927,
"country": "United Arab Emirates"
},
"weather": {
"current_time": "2026-11-02T11:00",
"temperature_2m": 33.2,
"apparent_temperature": 35.5,
"relative_humidity_2m": 34,
"weather_code": 0,
"weather_desc": "Clear sky",
"wind_speed_10m": 4.8,
"today_max": 35.2,
"today_min": 24.0,
"today_precip": 0.0,
"today_sunrise": "2026-11-02T06:26",
"today_sunset": "2026-11-02T17:37"
}
},
"Toronto": {
"geocode": {
"lat": 43.70643,
"lon": -79.39864,
"country": "Canada"
},
"weather": {
"current_time": "2026-11-02T02:00",
"temperature_2m": 7.1,
"apparent_temperature": 4.1,
"relative_humidity_2m": 77,
"weather_code": 0,
"weather_desc": "Clear sky",
"wind_speed_10m": 9.9,
"today_max": 16.9,
"today_min": 6.3,
"today_precip": 0.0,
"today_sunrise": "2026-11-02T06:54",
"today_sunset": "2026-11-02T17:06"
}
},
"Buenos Aires": {
"geocode": {
"lat": -34.61315,
"lon": -58.37723,
"country": "Argentina"
},
"weather": {
"current_time": "2026-11-02T04:00",
"temperature_2m": 16.6,
"apparent_temperature": 17.9,
"relative_humidity_2m": 91,
"weather_code": 0,
"weather_desc": "Clear sky",
"wind_speed_10m": 2.6,
"today_max": 24.7,
"today_min": 15.5,
"today_precip": 0.1,
"today_sunrise": "2026-11-02T05:50",
"today_sunset": "2026-11-02T19:24"
}
},
"Jakarta": {
"geocode": {
"lat": -6.21462,
"lon": 106.84513,
"country": "Indonesia"
},
"weather": {
"current_time": "2026-11-02T14:00",
"temperature_2m": 31.7,
"apparent_temperature": 36.8,
"relative_humidity_2m": 60,
"weather_code": 51,
"weather_desc": "Light drizzle",
"wind_speed_10m": 7.0,
"today_max": 33.9,
"today_min": 25.2,
"today_precip": 2.5,
"today_sunrise": "2026-11-02T05:26",
"today_sunset": "2026-11-02T17:46"
}
},
"Reykjavik": {
"geocode": {
"lat": 64.13548,
"lon": -21.89541,
"country": "Iceland"
},
"weather": {
"current_time": "2026-11-02T07:00",
"temperature_2m": 9.3,
"apparent_temperature": 5.3,
"relative_humidity_2m": 86,
"weather_code": 3,
"weather_desc": "Overcast",
"wind_speed_10m": 22.3,
"today_max": 9.8,
"today_min": 9.1,
"today_precip": 1.2,
"today_sunrise": "2026-11-02T09:13",
"today_sunset": "2026-11-02T17:07"
}
},
"Lagos": {
"geocode": {
"lat": 6.45407,
"lon": 3.39467,
"country": "Nigeria"
},
"weather": {
"current_time": "2026-11-02T08:00",
"temperature_2m": 28.2,
"apparent_temperature": 33.7,
"relative_humidity_2m": 79,
"weather_code": 1,
"weather_desc": "Mainly clear",
"wind_speed_10m": 5.2,
"today_max": 31.2,
"today_min": 27.4,
"today_precip": 1.0,
"today_sunrise": "2026-11-02T06:33",
"today_sunset": "2026-11-02T18:26"
}
},
"Seoul": {
"geocode": {
"lat": 37.566,
"lon": 126.9784,
"country": "South Korea"
},
"weather": {
"current_time": "2026-11-02T16:00",
"temperature_2m": 13.3,
"apparent_temperature": 10.4,
"relative_humidity_2m": 49,
"weather_code": 0,
"weather_desc": "Clear sky",
"wind_speed_10m": 9.1,
"today_max": 13.6,
"today_min": 6.9,
"today_precip": 0.0,
"today_sunrise": "2026-11-02T06:57",
"today_sunset": "2026-11-02T17:33"
}
},
"Auckland": {
"geocode": {
"lat": -36.84853,
"lon": 174.76349,
"country": "New Zealand"
},
"weather": {
"current_time": "2026-11-02T20:00",
"temperature_2m": 17.0,
"apparent_temperature": 14.9,
"relative_humidity_2m": 68,
"weather_code": 3,
"weather_desc": "Overcast",
"wind_speed_10m": 16.5,
"today_max": 18.1,
"today_min": 14.8,
"today_precip": 0.0,
"today_sunrise": "2026-11-02T06:15",
"today_sunset": "2026-11-02T19:54"
}
}
}
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# CHECK — ledger: open-meteo cities extract (2026-11-02).
#
# Exits 0 iff the committed table (2026-11-02-openmeteo-cities.csv) is a
# faithful re-derivation of the committed raw source snapshot
# (2026-11-02-openmeteo-cities.json). The CSV's own generator is NOT imported;
# the table is rebuilt cell-by-cell from the source JSON with plain python.
# No `|| true` / `; exit 0` fallback: the last command's exit code is the verdict.
set -u
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$DIR" || exit 2
python3 - <<'PY'
import csv
import json
import sys
src = json.load(open("2026-11-02-openmeteo-cities.json"))
with open("2026-11-02-openmeteo-cities.csv", newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
def cell(value):
# Numbers round-tripped by the generator: keep as str() so csv and json
# representations must agree exactly.
return str(value)
expected = []
for city, info in src.items():
g = info["geocode"]
w = info["weather"]
expected.append({
"city": city,
"country": g["country"],
"lat": cell(g["lat"]),
"lon": cell(g["lon"]),
"current_time": w["current_time"],
"temperature_2m": cell(w["temperature_2m"]),
"apparent_temperature": cell(w["apparent_temperature"]),
"relative_humidity_2m": cell(w["relative_humidity_2m"]),
"weather_code": cell(w["weather_code"]),
"weather_desc": w["weather_desc"],
"wind_speed_10m": cell(w["wind_speed_10m"]),
"today_max": cell(w["today_max"]),
"today_min": cell(w["today_min"]),
"today_precip": cell(w["today_precip"]),
"today_sunrise": w["today_sunrise"],
"today_sunset": w["today_sunset"],
})
if len(rows) != len(expected):
print("ROW COUNT MISMATCH csv=%d expected=%d" % (len(rows), len(expected)))
sys.exit(1)
for r, e in zip(rows, expected):
for key in e:
if r.get(key) != e[key]:
print("CELL MISMATCH row=%r column=%r csv=%r src=%r"
% (r.get("city"), key, r.get(key), e[key]))
sys.exit(1)
print("rows=%d cities=%s re-derived from source snapshot — PASS"
% (len(rows), ", ".join(r["city"] for r in rows)))
PY
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Extract a current + daily weather table for a fixed city set (Extract lane).
Source: Open-Meteo free public API (no vendor, no key). Each city is resolved
through the geocoding endpoint (``geocoding-api.open-meteo.com/v1/search``) to
get its latitude/longitude/country, then the forecast endpoint
(``api.open-meteo.com/v1/forecast``) returns current conditions plus today's
max/min/precip sum/sunrise/sunset.
Artifacts written beside this script:
- 2026-11-02-openmeteo-cities.csv extracted table (16 columns x 10 cities)
- 2026-11-02-openmeteo-cities.json raw per-city source snapshot (the evidence)
Deterministic check: the committed CSV is re-derived cell-by-cell from the
committed source JSON by ``CHECK-2026-11-02-openmeteo-cities``.
"""
import csv
import json
import urllib.parse
import urllib.request
from pathlib import Path
HERE = Path(__file__).resolve().parent
CITIES = [
"Berlin", "Mumbai", "Dubai", "Toronto", "Buenos Aires",
"Jakarta", "Reykjavik", "Lagos", "Seoul", "Auckland",
]
FIELDS = [
"city", "country", "lat", "lon", "current_time",
"temperature_2m", "apparent_temperature", "relative_humidity_2m",
"weather_code", "weather_desc", "wind_speed_10m",
"today_max", "today_min", "today_precip",
"today_sunrise", "today_sunset",
]
WMO = {
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
45: "Fog", 48: "Fog",
51: "Light drizzle", 53: "Drizzle", 55: "Heavy drizzle",
61: "Slight rain", 63: "Rain", 65: "Heavy rain",
71: "Slight snow", 73: "Snow", 75: "Heavy snow",
80: "Slight showers", 81: "Showers", 82: "Heavy showers",
85: "Snow showers", 86: "Heavy snow showers",
95: "Thunderstorm", 96: "Thunderstorm with slight hail", 99: "Thunderstorm with heavy hail",
}
def http_get(url, timeout=25):
req = urllib.request.Request(url, headers={"User-Agent": "ledger-extract/1.0"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.read().decode("utf-8")
def geocode(name):
url = ("https://geocoding-api.open-meteo.com/v1/search?name=" + urllib.parse.quote(name)
+ "&count=1&language=en&format=json")
data = json.loads(http_get(url))
hits = data.get("results") or []
if not hits:
raise RuntimeError("no geocode hit for %r" % name)
h = hits[0]
return (h["latitude"], h["longitude"],
h.get("country") or h.get("country_code") or "n/a")
def fetch_weather(lat, lon):
lat_s = "%.6f" % lat
lon_s = "%.6f" % lon
url = ("https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}"
"&current=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m"
"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,sunrise,sunset"
"&timezone=auto".format(lat=lat_s, lon=lon_s))
data = json.loads(http_get(url))
cur = data["current"]
day = data["daily"]
return {
"current_time": cur["time"],
"temperature_2m": cur["temperature_2m"],
"apparent_temperature": cur["apparent_temperature"],
"relative_humidity_2m": cur["relative_humidity_2m"],
"weather_code": cur["weather_code"],
"weather_desc": WMO.get(cur["weather_code"], "Unknown"),
"wind_speed_10m": cur["wind_speed_10m"],
"today_max": day["temperature_2m_max"][0],
"today_min": day["temperature_2m_min"][0],
"today_precip": day["precipitation_sum"][0],
"today_sunrise": day["sunrise"][0],
"today_sunset": day["sunset"][0],
}
def main():
rows = []
source = {}
for city in CITIES:
try:
lat, lon, country = geocode(city)
w = fetch_weather(lat, lon)
rows.append({"city": city, "country": country, "lat": lat, "lon": lon, **w})
source[city] = {"geocode": {"lat": lat, "lon": lon, "country": country},
"weather": w}
except Exception as exc: # noqa: BLE001
rows.append({"city": city, "country": "ERROR", "lat": "", "lon": "",
"current_time": "", "temperature_2m": "", "apparent_temperature": "",
"relative_humidity_2m": "", "weather_code": "", "weather_desc": "FAILED",
"wind_speed_10m": "", "today_max": "", "today_min": "",
"today_precip": "", "today_sunrise": "", "today_sunset": ""})
source[city] = {"error": repr(exc)}
csv_path = HERE / "2026-11-02-openmeteo-cities.csv"
with csv_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS, lineterminator="\n")
writer.writeheader()
for r in rows:
writer.writerow(r)
(HERE / "2026-11-02-openmeteo-cities.json").write_text(
json.dumps(source, ensure_ascii=False, indent=1), encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,12 @@
import os, stat
p = 'extraction_manifest.json'
print('exists', os.path.exists(p))
print('isfile', os.path.isfile(p))
print('listdir', repr(os.listdir('.')))
try:
st = os.stat(p)
print('mode', oct(stat.S_IFMT(st.st_mode)), oct(st.st_mode), 'size', st.st_size)
print('inod', st.st_ino)
print('contents_b200', repr(open(p, 'rb').read(200)))
except Exception as e:
print('stat_err', repr(e))
File diff suppressed because it is too large. Load diff