Retire the Superset reader setup and the ATOMA dashboard (PD-13)

ATOMA's users use the Petrolyst app; Superset keeps admins, editors and the
app's embedded dashboards. Removed: the "ATOMA Reader" role and its rules
(petrolyst_reader.py, ReaderAccessMixin, reader.css, the head template, the
SQL-hiding patch), the atoma-reader mapping (it now maps to nothing in
Superset), the iframe allowance for the Map tab, and the "atoma" dashboard
with its Fleet/Map tab, D0 and dashboard.css. The bundle holds atoma-overview
and atoma-site only (byte-identical to before); the guest role keeps the
reader's 12 permissions in roles/atoma_guest.py. Brand files now name
petrolyst-web. SPEC marks the retired parts as history.

Checks (local): bundle checks pass; image rebuilt and healthy; import with
column drift 0 and unchanged embedding ids; the production config loads with
OAuth, embedding off, no reader hook; admin sees atoma-site with its tabs and
filter column; the app's browser tests pass 10/10. shellcheck not run (not
installed). Not deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nasim SadatandClaude Opus 5.5 committed 2026-10-29 21:10:19 +01:00
1 parent 1b8e57adb4
commit da3f803947
22 files changed
+164 -702

No files matched your search

@@ -1,10 +1,9 @@
#!/usr/bin/env python3
"""Generate the ATOMA dashboard as one Superset 6.1 import bundle.
"""Generate ATOMA's dashboards for the Petrolyst app as one Superset 6.1 import bundle.
Implements SPEC.md in this folder: the "ATOMA" dashboard with a Map tab (the
fleet-map view of petrolyst-platform, embedded as an iframe) and an Insights tab,
one dataset per public.v_* view and every chart. Nothing in the bundle names
a site.
Implements SPEC.md in this folder: the two dashboards the Petrolyst app embeds, "ATOMA · Overview"
(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]
@@ -17,9 +16,8 @@ unchanged).
Before writing, the bundle is checked: every column a chart or filter uses
exists in its dataset, every chart in a Site filter's scope has site_label, the
fleet map's address names this dashboard, its Site filter and the Insights tab,
every layout id that dashboard.css or reader.css names exists, and no row is wider
than 12. Requires PyYAML.
Overview sums recorded values only and money only per currency, and no row is
wider than 12. Requires PyYAML.
"""
from __future__ import annotations
@@ -43,36 +41,11 @@ DATABASE_NAME = "Fuel (Trino)"
DATABASE_URI = "trino://superset@trino:8080/fuel" # as superset-init registers it
CATALOG, SCHEMA = "fuel", "public"
SLUG = "atoma"
SITE_FILTER = "NATIVE_FILTER-site"
PERIOD_FILTER = "NATIVE_FILTER-period"
FLEET_TAB = "TAB-fleet"
SITE_TAB = "TAB-site"
# The tabs' names: the lenses Map and Insights (owner, 29 Oct); readers see them as a switch
# in the navbar (assets/branding/reader.css).
FLEET_TAB_TITLE = "Map"
SITE_TAB_TITLE = "Insights"
# SPEC §3: the Map tab is the fleet-map view of petrolyst-platform, served on the same
# origin under /views (superset_config.py lets the iframe through the Markdown sanitiser).
# Its parameters name where "Site details" leads: this dashboard, its Site filter and the
# Insights tab, with that tab's name for the map's key. The view builds the native_filters
# link from them.
# The operator's logo in the map's bottom right corner: a file in assets/branding/, served by
# Superset under /static/assets/branding/ (same origin, so the view's CSP allows it).
OPERATOR_LOGO = "atoma_logo.svg"
# The dashboard's own CSS (the Insights tab's inner tabs as section buttons), and the
# readers' stylesheet, which names this layout's ids too (the lens switch, the map's size).
DASHBOARD_CSS = HERE / "dashboard.css"
READER_CSS = HERE.parent.parent / "assets" / "branding" / "reader.css"
FLEET_MAP_SRC = (f"/views/fleet-map/?detail=/superset/dashboard/{SLUG}/"
f"&amp;filter={SITE_FILTER}&amp;tab={SITE_TAB}&amp;tab_label={SITE_TAB_TITLE}"
f"&amp;logo=/static/assets/branding/{OPERATOR_LOGO}&amp;logo_alt=ATOMA")
FLEET_MAP = (f'<iframe src="{FLEET_MAP_SRC}" title="Fleet map" loading="lazy" '
'style="display:block;width:100%;height:100%;border:0"></iframe>')
# SPEC §3: neutral hues, no red or green, keyed by v_site.baseline_status_label. The
# fleet-map view uses the same colours (petrolyst-platform views/src/shared/status.ts).
# Petrolyst app's map uses the same colours (petrolyst-platform app/src/app/core/status.ts).
STATUS_COLOURS = {
"Approved baseline": "#009688",
"Eligible - not yet approved": "#3987e5",
@@ -96,8 +69,8 @@ MAIN_DTTM = {
"v_electricity_cost": "bill_month",
}
# Calculated columns: (name, Trino SQL, type, label). The fleet-map view's read service
# (petrolyst-platform services/read, site_status) shows the same two texts.
# Calculated columns: (name, Trino SQL, type, label). The Petrolyst server's site_status
# (petrolyst-platform server/) shows the same two texts.
CALCULATED = {
"v_site": [
("lph_text", "COALESCE(CAST(lph_estimate AS varchar), 'not available')", "VARCHAR", "L/h"),
@@ -588,9 +561,6 @@ def detail_charts() -> list[Chart]:
return [
# Handlebars would render these as cards, but it compiles templates with
# eval, which Superset's own CSP blocks. One-row tables need no CSP change.
table("D0", "Site", "v_site", [
"site_code", "site_name", "province", "pilot_status", "baseline_status_text",
"location_status"]),
table("S1", "Fuel per running hour", "v_site", [
"lph_text", "lph_method", "lph_window_start", "lph_window_end", "baseline_status_text",
"baseline_validity_note", "baseline_decision_ref", "baseline_conditions",
@@ -799,7 +769,6 @@ TAB_NOTES = {
"the map or the sites list. The Viewing period does not apply to this tab.",
}
DETAIL_HEADER = [("D0", 12, 24)]
DETAIL_TABS = [
("summary", "Summary", [
[("MARKDOWN:notes-summary", 12, 17)],
@@ -982,8 +951,8 @@ def base_metadata(layout: Layout, filters: list[dict[str, Any]], cross_filters:
"map_label_colors": {},
"color_scheme_domain": [],
"cross_filters_enabled": cross_filters,
# Filters in Superset's left column (owner, 29 Oct). Readers don't see it on the Fleet
# tab (assets/branding/reader.css), so their map gets the full width.
# Filters in Superset's left column (owner, 29 Oct); the Petrolyst app hides the column
# when it embeds a dashboard and sets the filters itself.
"filter_bar_orientation": "VERTICAL",
"native_filter_configuration": filters,
"chart_configuration": {},
@@ -1021,30 +990,12 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
for view in sorted(views):
files[f"datasets/Fuel_Trino/{view}.yaml"] = dataset_yaml(view, views[view], database_uuid)
charts = {c.key: c for c in detail_charts()}
# SPEC §2: [Map] [Insights]; the site header and the four detail tabs live in
# Insights, so the Map tab shows the map only.
layout = Layout("ATOMA", charts)
layout.markdown["fleet-map"] = FLEET_MAP
layout.markdown.update({f"notes-{slug}": text for slug, text in TAB_NOTES.items()})
main_tabs = layout.add("TABS-main", "TABS", "GRID_ID", {})
layout.rows([[("MARKDOWN:fleet-map", 12, 100)]], layout.tab(FLEET_TAB, FLEET_TAB_TITLE, main_tabs), "fleet")
site_tab = layout.tab(SITE_TAB, SITE_TAB_TITLE, main_tabs)
layout.rows([DETAIL_HEADER], site_tab, "header")
layout.tabs(DETAIL_TABS, site_tab, "TABS-detail")
metadata = base_metadata(layout, detail_filters(layout.ids), cross_filters=False)
metadata["label_colors"].update(STOCK_STATUS_COLOURS)
files["dashboards/ATOMA.yaml"] = dashboard_yaml("atoma", "ATOMA", SLUG, layout, metadata,
DASHBOARD_CSS.read_text())
dashboards = [(layout, charts, metadata, (DASHBOARD_CSS, READER_CSS))]
# 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
# and period, so these have no D0 and no CSS.
# and period. Admins open the same dashboards in Superset, with the filter column.
overview = {c.key: c for c in overview_charts()}
all_charts = {**charts, **overview}
all_charts = {**{c.key: c for c in detail_charts()}, **overview}
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any]]] = []
for key, title in [(OVERVIEW_SLUG, "Overview"), (SITE_SLUG, "Site")]:
page = Layout(f"ATOMA · {title}", all_charts)
page.markdown.update({f"notes-{k}": v for k, v in TAB_NOTES.items()})
@@ -1061,7 +1012,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
page_metadata["label_colors"].update(STOCK_STATUS_COLOURS)
files[f"dashboards/{key}.yaml"] = dashboard_yaml(key, f"ATOMA · {title}", key, page,
page_metadata)
dashboards.append((page, placed, page_metadata, ()))
dashboards.append((page, placed, page_metadata))
for chart in all_charts.values():
name = re.sub(r"\W+", "_", f"{chart.key}_{chart.title}").strip("_")
@@ -1072,7 +1023,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any], tuple[Path, ...]]],
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any]]],
overview: dict[str, Chart]) -> None:
problems = []
columns = {v: {c for c, _ in cols} | {c[0] for c in CALCULATED.get(v, [])}
@@ -1083,11 +1034,7 @@ def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
for view, col in MAIN_DTTM.items():
if col not in columns.get(view, set()):
problems.append(f"{view}: main time column {col} missing")
for layout, charts, metadata, stylesheets in dashboards:
for css in stylesheets:
for ref in sorted(set(re.findall(r"#([A-Z]+-[\w-]+)", css.read_text()))):
if ref not in layout.nodes:
problems.append(f"{css.name}: #{ref} is not in the layout")
for layout, charts, metadata in dashboards:
placed = set(layout.ids)
if placed != set(charts):
problems.append(f"charts not placed or unknown: {sorted(placed ^ set(charts))}")
@@ -1104,26 +1051,6 @@ def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
for key in in_scope:
if col not in columns.get(charts[key].view, set()):
problems.append(f"{f['id']}: {key} ({charts[key].view}) lacks {col}")
# The fleet map's "Site details" link is built from its iframe address.
fleet = layout.nodes.get("MARKDOWN-fleet-map")
if fleet is not None:
filter_ids = {f["id"] for f in metadata["native_filter_configuration"]}
for needle, what in [(f"detail=/superset/dashboard/{SLUG}/", "this dashboard"),
(f"filter={SITE_FILTER}", "the Site filter"),
(f"tab={SITE_TAB}", "the Insights tab"),
(f"tab_label={SITE_TAB_TITLE}", "the Insights tab's name")]:
if needle not in fleet["meta"]["code"]:
problems.append(f"fleet map: its address does not name {what}")
if SITE_FILTER not in filter_ids or SITE_TAB not in layout.nodes:
problems.append("fleet map: the Site filter or the Insights tab is missing")
if not (HERE.parent.parent / "assets" / "branding" / OPERATOR_LOGO).is_file():
problems.append(f"fleet map: assets/branding/{OPERATOR_LOGO} is missing")
if metadata.get("filter_bar_orientation") != "VERTICAL":
problems.append("fleet map: the filters must sit in the left column")
if f'data-node-key="{FLEET_TAB}"' not in READER_CSS.read_text():
problems.append(f"fleet map: reader.css does not hide the filters on {FLEET_TAB}")
if fleet["meta"]["width"] != 12:
problems.append("fleet map: reader.css stretches it, so it must span all 12 columns")
# SPEC §10: the Overview sums recorded values only, and money only per currency.
for chart in overview.values():
metrics = chart.params.get("metrics") or [chart.params["metric"]]