Remove Trino: Superset reads fuel-db directly as fuel_ro (PD-14)

trino/ deleted; the role trino_ro becomes fuel_ro, its password read from a
secret file at connect time; dashboards' SQL and column list for PostgreSQL;
the Compose project is renamed petrolyst, with volumes pinned to their old
names. AGENTS.md points to the architecture context. Not deployed: the next
deploy needs README's one-time steps first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nasim SadatandClaude Opus 5.5 committed 2026-10-30 10:57:18 +01:00
1 parent 686318dfac
commit 8405706ed8
25 files changed
+453 -410

No files matched your search

@@ -5,7 +5,7 @@ Implements SPEC.md in this folder: the two dashboards the Petrolyst app embeds,
(atoma-overview) and "ATOMA · Site" (atoma-site, the four detail tabs), one dataset per
public.v_* view and every chart. Nothing in the bundle names a site.
build_bundle.py --database-uuid <uuid of "Fuel (Trino)"> [--out atoma-dashboards.zip]
build_bundle.py --database-uuid <uuid of "Fuel"> [--out atoma-dashboards.zip]
The database uuid differs per Superset instance; `import_bundle.py --show-database`
prints it. Output is deterministic: the same inputs give a byte-identical zip,
@@ -37,8 +37,10 @@ import yaml
HERE = Path(__file__).resolve().parent
VERSION = "1.0.0"
NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "https://petrolyst.com/superset/atoma-dashboards")
DATABASE_NAME = "Fuel (Trino)"
DATABASE_URI = "trino://superset@trino:8080/fuel" # as superset-init registers it
DATABASE_NAME = "Fuel"
# As superset-init registers it: fuel-db directly, as the read-only role fuel_ro. The password
# comes from a secret file at connect time (superset_config.py), never from the bundle.
DATABASE_URI = "postgresql+psycopg2://fuel_ro@fuel-db:5432/fuel"
CATALOG, SCHEMA = "fuel", "public"
SITE_FILTER = "NATIVE_FILTER-site"
@@ -69,7 +71,7 @@ MAIN_DTTM = {
"v_electricity_cost": "bill_month",
}
# Calculated columns: (name, Trino SQL, type, label). The Petrolyst server's site_status
# Calculated columns: (name, PostgreSQL SQL, type, label). The Petrolyst server's site_status
# (petrolyst-platform server/) shows the same two texts.
CALCULATED = {
"v_site": [
@@ -93,7 +95,7 @@ CALCULATED = {
],
}
# Virtual datasets: name -> (view it extends, extra output columns, Trino SQL).
# Virtual datasets: name -> (view it extends, extra output columns, PostgreSQL SQL).
# coverage_detail feeds S3. The view's lane names start with a sort number ("1 Stock
# intervals"), which reads as a count on the chart, so it adds lane_name without the number
# (the chart still sorts by lane). Its amount column is the tooltip: the litres delivered in a
@@ -106,7 +108,7 @@ CALCULATED = {
# day cannot be told apart. Everywhere else the date window is exact: on a single-visit day the
# opening visit's delivery is included and the closing visit's excluded.
VIRTUAL = {
"coverage_detail": ("v_coverage", [("lane_name", "varchar(60)"), ("amount", "varchar(120)")], """\
"coverage_detail": ("v_coverage", [("lane_name", "character varying(60)"), ("amount", "character varying(120)")], """\
SELECT c.site_uid, c.site_code, c.site_name, c.site_label, c.lane, c.status,
c.start_on, c.end_on, c.start_ts, c.end_ts,
regexp_replace(c.lane, '^[0-9]+ ', '') AS lane_name,
@@ -114,19 +116,19 @@ SELECT c.site_uid, c.site_code, c.site_name, c.site_label, c.lane, c.status,
CASE WHEN f.ambiguous THEN 'not attributed: a delivery on a day with several visits (see Deliveries)'
WHEN f.filled_l IS NULL AND coalesce(f.n_unknown, 0) = 0 THEN 'no delivery'
WHEN f.filled_l IS NULL THEN 'filled, quantity not recorded'
ELSE format('%,.0f L filled', CAST(f.filled_l AS double)) END
ELSE to_char(round(f.filled_l), 'FM999,999,999,990') || ' L filled' END
|| CASE WHEN f.n_unknown > 0 AND f.filled_l IS NOT NULL AND NOT f.ambiguous
THEN ', plus ' || CAST(f.n_unknown AS varchar) || ' of unknown quantity' ELSE '' END
WHEN c.lane = '4 Electricity bills' THEN
CASE WHEN b.n_bills > 1 THEN 'several bills start this day (see Electricity bills)'
WHEN b.disputed THEN 'bill disputed, amount withheld'
WHEN b.amount IS NULL THEN 'bill amount not recorded'
ELSE format('%,.0f AFN billed (inferred)', CAST(b.amount AS double)) END
ELSE to_char(round(b.amount), 'FM999,999,999,990') || ' AFN billed (inferred)' END
END AS amount
FROM public.v_coverage c
LEFT JOIN (SELECT s.site_uid, s.start_on, s.end_on,
sum(CASE WHEN d.delivery_date < s.end_on THEN d.delivered_l END) AS filled_l,
count_if(d.delivery_date < s.end_on AND d.delivered_l IS NULL) AS n_unknown,
count(*) FILTER (WHERE d.delivery_date < s.end_on AND d.delivered_l IS NULL) AS n_unknown,
bool_or(d.delivery_date IN (s.start_on, s.end_on) AND m.site_uid IS NOT NULL) AS ambiguous
FROM (SELECT DISTINCT site_uid, start_on, end_on FROM public.v_coverage
WHERE lane = '1 Stock intervals') s
@@ -216,8 +218,8 @@ def read_columns(path: Path) -> dict[str, list[tuple[str, str]]]:
return views
def superset_type(trino_type: str) -> str:
return trino_type.upper().replace(",", ", ")
def superset_type(data_type: str) -> str:
return data_type.upper().replace(",", ", ")
def dataset_yaml(view: str, columns: list[tuple[str, str]], database_uuid: str) -> dict[str, Any]:
@@ -968,7 +970,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
views = read_columns(columns_path)
files: dict[str, Any] = {
"metadata.yaml": {"version": VERSION, "type": "Dashboard", "timestamp": "2026-10-24T00:00:00+00:00"},
"databases/Fuel_Trino.yaml": {
"databases/Fuel.yaml": {
"database_name": DATABASE_NAME,
"sqlalchemy_uri": DATABASE_URI,
"cache_timeout": None,
@@ -988,7 +990,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
for name, (base, extra, _) in VIRTUAL.items():
views[name] = views[base] + extra
for view in sorted(views):
files[f"datasets/Fuel_Trino/{view}.yaml"] = dataset_yaml(view, views[view], database_uuid)
files[f"datasets/Fuel/{view}.yaml"] = dataset_yaml(view, views[view], database_uuid)
# PD-12: the Petrolyst app embeds the Overview and one site dashboard, whose four detail
# tabs are Superset's own (owner, 29 Oct). The app shows the site header and chooses site
@@ -1083,7 +1085,7 @@ def write_zip(files: dict[str, Any], out: Path) -> None:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--database-uuid", required=True, type=uuid.UUID,
help='uuid of the "Fuel (Trino)" database in the target Superset')
help='uuid of the "Fuel" database in the target Superset')
parser.add_argument("--columns", type=Path, default=HERE / "columns.tsv")
parser.add_argument("--out", type=Path, default=Path("atoma-dashboards.zip"))
args = parser.parse_args(argv)