Adds stacks/fuel-monitoring/fuel-db/migrations/0001-0003 with their README, and petrolyst-insights/dashboards/atoma/SPEC.md. Nothing applies the migrations: deploy.sh never touches fuel-db, and a person applies them by hand in phase P3. Verified in scratch Docker (timescaledb 2.31.0-pg17 with this repository's 01-roles.sh, trino 484 with its trino/etc): 150 SQL checks pass; the 18 views have 303 columns with identical types in PostgreSQL and Trino; fm cannot be read through Trino. Codex (gpt-6-astra) inspected the change in four rounds; the round-4 fixes are committed without a further inspection, on the owner's instruction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
622 lines
44 KiB
PL/PgSQL
622 lines
44 KiB
PL/PgSQL
-- 0003_bi_views.sql: the views Superset reads through Trino (schema `public`), plus
|
|
-- fm.v_site_baseline_status. Owned by fuel_owner, so trino_ro can read them through the existing
|
|
-- default privileges; they run with the owner's rights, so Trino never reads `fm` (it lists fm's
|
|
-- table names, as it lists every schema's, but any query on them is refused).
|
|
-- DRAFT, not applied. Applied after 0002 by a loader acting as fuel_owner (README in this folder).
|
|
-- Design: fuel-monitoring .context/plans/2026-10-24-fuel-db-baseline-dashboard.md, §5.2 and §6.
|
|
BEGIN;
|
|
SET LOCAL search_path = fm, pg_catalog, public;
|
|
|
|
-- Status, estimate and method come from ONE baseline_version, never from ad-hoc pooling of intervals
|
|
-- (exploratory interval figures stay on the Baseline & Evidence tab):
|
|
-- approved : the latest approved KPI version; a decision is recorded. The only state that says "approved".
|
|
-- If its inputs changed since, it stays approved and baseline_validity_note says so.
|
|
-- eligible : no approval; the latest proposed KPI version passed BP-1 when `baseline propose`
|
|
-- computed it from its inputs (meets_policy), and those inputs are unchanged today.
|
|
-- provisional : no approval; a proposed version below BP-1, or usable intervals but no version yet
|
|
-- (then no estimate is shown).
|
|
-- insufficient_evidence: none of the above (today: every site, since there is no runtime).
|
|
CREATE VIEW fm.v_site_baseline_status AS
|
|
WITH cur AS (
|
|
SELECT DISTINCT ON (b.site_uid) b.*
|
|
FROM fm.baseline_version b
|
|
WHERE b.status IN ('approved','proposed')
|
|
AND b.metric IN ('measured_generator_l_per_h','apparent_l_per_generator_h',
|
|
'apparent_l_per_set_h','apparent_l_per_site_dg_h') -- never modelled, never per-day
|
|
ORDER BY b.site_uid, (b.status = 'approved') DESC, b.approved_on DESC NULLS LAST, b.computed_at DESC),
|
|
ev AS (
|
|
SELECT site_uid, count(*) FILTER (WHERE eligibility <> 'excluded') AS n_usable
|
|
FROM fm.v_lph_interval GROUP BY site_uid)
|
|
SELECT s.site_uid,
|
|
CASE WHEN cur.status = 'approved' THEN 'approved'
|
|
WHEN cur.status = 'proposed' AND cur.meets_policy AND val.inputs_unchanged THEN 'eligible'
|
|
WHEN cur.status = 'proposed' OR coalesce(ev.n_usable, 0) > 0 THEN 'provisional'
|
|
ELSE 'insufficient_evidence' END AS baseline_status,
|
|
CASE WHEN cur.baseline_id IS NOT NULL AND NOT coalesce(val.inputs_unchanged, false)
|
|
THEN CASE cur.status WHEN 'approved' THEN 'Inputs changed since approval: review needed'
|
|
ELSE 'Inputs changed since computation: re-propose' END END AS baseline_validity_note,
|
|
cur.code AS baseline_code,
|
|
CASE WHEN cur.status = 'approved' OR val.inputs_unchanged THEN cur.value END AS lph_estimate,
|
|
CASE WHEN cur.status = 'approved' OR val.inputs_unchanged THEN cur.range_low END AS range_low,
|
|
CASE WHEN cur.status = 'approved' OR val.inputs_unchanged THEN cur.range_high END AS range_high,
|
|
cur.metric AS lph_metric, cur.population, cur.window_start AS lph_window_start,
|
|
cur.window_end AS lph_window_end, cur.conditions, cur.decision_ref,
|
|
coalesce(ev.n_usable, 0) AS n_usable_intervals
|
|
FROM fm.site s LEFT JOIN cur USING (site_uid) LEFT JOIN ev USING (site_uid)
|
|
LEFT JOIN fm.v_baseline_validity val ON val.baseline_id = cur.baseline_id;
|
|
|
|
-- One row per site, every status: the map dataset and the single source of site-level fields.
|
|
-- "Current" means the period that contains today's date in Kabul, not "no end date".
|
|
CREATE VIEW public.v_site AS
|
|
WITH today AS (SELECT (now() AT TIME ZONE 'Asia/Kabul')::date AS d),
|
|
alias AS ( -- v1: the operator is ATOMA; later: the site's current operator
|
|
SELECT a.site_uid, a.label FROM fm.site_alias a, today
|
|
WHERE a.issuer = 'ATOMA' AND a.is_primary
|
|
AND daterange(coalesce(a.valid_from, '-infinity'::date), a.valid_to, '[)') @> today.d),
|
|
pp AS ( -- one programme, chosen explicitly
|
|
SELECT p.site_uid, p.status FROM fm.pilot_participation p, today
|
|
WHERE p.programme = 'ATOMA OFMS pilot' AND daterange(p.valid_from, p.valid_to, '[)') @> today.d),
|
|
-- alias and pp are at most one row per site: the exclusion constraints forbid overlapping primary
|
|
-- labels per (site, issuer) and overlapping periods per (site, programme).
|
|
loc AS (SELECT site_uid, latitude, longitude, basis, precision_m FROM fm.site_location WHERE acceptance = 'accepted'),
|
|
loc_any AS (SELECT site_uid, bool_or(acceptance = 'pending') AS has_pending FROM fm.site_location GROUP BY site_uid),
|
|
cal AS (SELECT site_uid, sum(consumed_l) AS fuel_l, sum(days) AS days
|
|
FROM fm.v_stock_interval WHERE stock_status = 'ok' GROUP BY site_uid),
|
|
latest AS (SELECT site_uid, max(visit_on) AS latest_record_on -- live records only: a fully
|
|
FROM fm.v_visit_live GROUP BY site_uid) -- retracted visit does not count
|
|
SELECT s.site_uid::varchar(12) AS site_uid,
|
|
coalesce(alias.label, s.site_uid)::varchar(20) AS site_code,
|
|
coalesce(s.name, 'name not recorded')::varchar(120) AS site_name,
|
|
(coalesce(alias.label, s.site_uid) || ' - ' || coalesce(s.name, 'name not recorded')
|
|
|| ' - ' || s.site_uid)::varchar(170) AS site_label, -- the one filter value
|
|
s.zone::varchar(40) AS zone, s.province::varchar(40) AS province, s.district::varchar(60) AS district,
|
|
coalesce(pp.status, 'none')::varchar(12) AS pilot_status,
|
|
loc.latitude::numeric(9,6) AS latitude, loc.longitude::numeric(9,6) AS longitude, -- NULL unless accepted
|
|
(CASE WHEN loc.site_uid IS NOT NULL THEN 'accepted'
|
|
WHEN loc_any.has_pending THEN 'awaiting acceptance' ELSE 'missing' END)::varchar(20) AS location_status,
|
|
loc.basis::varchar(20) AS location_basis, loc.precision_m::integer AS location_precision_m,
|
|
bs.baseline_status::varchar(24) AS baseline_status,
|
|
(CASE bs.baseline_status WHEN 'approved' THEN 'Approved baseline'
|
|
WHEN 'eligible' THEN 'Eligible - not yet approved' WHEN 'provisional' THEN 'Provisional'
|
|
ELSE 'Insufficient evidence' END)::varchar(40) AS baseline_status_label,
|
|
bs.baseline_validity_note::varchar(60) AS baseline_validity_note,
|
|
round(bs.lph_estimate, 2)::numeric(6,2) AS lph_estimate,
|
|
(CASE bs.lph_metric WHEN 'apparent_l_per_generator_h' THEN 'Stock balance per generator running hour'
|
|
WHEN 'apparent_l_per_set_h' THEN 'Stock balance per summed generator-hour'
|
|
WHEN 'apparent_l_per_site_dg_h' THEN 'Stock balance per hour with any generator running'
|
|
WHEN 'measured_generator_l_per_h' THEN 'Measured engine fuel per running hour'
|
|
ELSE CASE WHEN bs.n_usable_intervals > 0 THEN 'Not available: no baseline version proposed'
|
|
ELSE 'Not available: no runtime evidence' END END)::varchar(60) AS lph_method,
|
|
bs.lph_window_start, bs.lph_window_end, bs.conditions::varchar(200) AS baseline_conditions,
|
|
bs.decision_ref::varchar(12) AS baseline_decision_ref,
|
|
round(cal.fuel_l / NULLIF(cal.days, 0), 1)::numeric(7,1) AS fuel_l_per_calendar_day,
|
|
latest.latest_record_on
|
|
FROM fm.site s
|
|
LEFT JOIN alias USING (site_uid) LEFT JOIN loc USING (site_uid) LEFT JOIN loc_any USING (site_uid)
|
|
LEFT JOIN pp USING (site_uid) LEFT JOIN fm.v_site_baseline_status bs USING (site_uid)
|
|
LEFT JOIN cal USING (site_uid) LEFT JOIN latest USING (site_uid);
|
|
|
|
-- Every view below joins public.v_site for site_uid, site_code, site_name and site_label, so one
|
|
-- Site filter drives every dataset, and casts to Trino-safe types (varchar(n), integer,
|
|
-- numeric(p,s), date, timestamp(0), boolean). Live = asserted, reviewed or disputed.
|
|
|
|
-- S2, R3: one row per site and measure; the rows stay when a value is not established.
|
|
CREATE VIEW public.v_kpi_measures AS
|
|
WITH cur AS ( -- the site's L/h version; its snapshot only while approved or its inputs unchanged
|
|
SELECT bs.site_uid, b.baseline_id, b.code, b.fuel_l, b.runtime_h, b.matched_days, b.window_start, b.window_end,
|
|
(b.status = 'approved' OR coalesce(val.inputs_unchanged, false)) AS shown,
|
|
' (' || b.status || ')'
|
|
|| CASE WHEN coalesce(val.inputs_unchanged, false) THEN ''
|
|
WHEN b.status = 'approved' THEN '; inputs changed since approval: review needed'
|
|
ELSE '; inputs changed since computation: re-propose' END AS state
|
|
FROM fm.v_site_baseline_status bs JOIN fm.baseline_version b ON b.code = bs.baseline_code
|
|
LEFT JOIN fm.v_baseline_validity val ON val.baseline_id = b.baseline_id),
|
|
ninp AS (SELECT baseline_id, count(*) FILTER (WHERE included) AS n FROM fm.baseline_input GROUP BY baseline_id),
|
|
m4 AS (SELECT site_uid, sum(consumed_l) AS fuel_l, sum(days) AS days, min(start_on) AS first_on, max(end_on) AS last_on
|
|
FROM fm.v_m4_interval WHERE eligibility = 'eligible' GROUP BY site_uid),
|
|
span AS (SELECT site_uid, min(visit_on) AS first_on, max(visit_on) AS last_on FROM fm.v_visit_live GROUP BY site_uid),
|
|
del AS (SELECT v.site_uid, count(*) AS n FROM fm.v_movement_event e JOIN fm.v_visit_live v ON v.visit_id = e.visit_id
|
|
WHERE e.kind = 'delivery' GROUP BY v.site_uid),
|
|
rdg AS (SELECT m.site_uid, count(*) AS n FROM fm.hour_meter_reading h JOIN fm.runtime_meter m ON m.meter_id = h.meter_id
|
|
WHERE h.claim_status IN ('asserted','reviewed','disputed') GROUP BY m.site_uid),
|
|
kwh AS (SELECT site_uid, sum(kwh) AS kwh, count(kwh) AS n_kwh, min(bill_month) AS first_m, max(bill_month) AS last_m
|
|
FROM fm.electricity_bill WHERE claim_status IN ('asserted','reviewed') GROUP BY site_uid), -- values never from disputed
|
|
lday AS ( -- the L/day reference: the latest approved, else proposed, L/day version
|
|
SELECT DISTINCT ON (b.site_uid) b.site_uid, b.code, b.status, b.value, b.window_start, b.window_end,
|
|
coalesce(val.inputs_unchanged, false) AS inputs_unchanged
|
|
FROM fm.baseline_version b LEFT JOIN fm.v_baseline_validity val ON val.baseline_id = b.baseline_id
|
|
WHERE b.metric = 'fuel_l_per_calendar_day' AND b.status IN ('approved','proposed')
|
|
ORDER BY b.site_uid, (b.status = 'approved') DESC, b.approved_on DESC NULLS LAST, b.computed_at DESC)
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
x.ord::integer AS measure_order, x.measure::varchar(60) AS measure,
|
|
round(x.value, 2)::numeric(12,2) AS value, x.unit::varchar(12) AS unit,
|
|
(CASE WHEN x.value IS NULL THEN 'not established' ELSE x.kind END)::varchar(16) AS value_kind,
|
|
x.window_start, x.window_end, x.note::varchar(200) AS note
|
|
FROM public.v_site s
|
|
LEFT JOIN cur ON cur.site_uid = s.site_uid
|
|
LEFT JOIN ninp ON ninp.baseline_id = cur.baseline_id
|
|
LEFT JOIN m4 ON m4.site_uid = s.site_uid
|
|
LEFT JOIN span ON span.site_uid = s.site_uid
|
|
LEFT JOIN del ON del.site_uid = s.site_uid
|
|
LEFT JOIN rdg ON rdg.site_uid = s.site_uid
|
|
LEFT JOIN kwh ON kwh.site_uid = s.site_uid
|
|
LEFT JOIN lday ON lday.site_uid = s.site_uid
|
|
CROSS JOIN LATERAL (VALUES
|
|
(1, 'Matched fuel', CASE WHEN cur.shown THEN cur.fuel_l END, 'L', 'calculated', cur.window_start, cur.window_end,
|
|
'fuel used in the intervals of L/h baseline version ' || cur.code || cur.state),
|
|
(2, 'Matched runtime', CASE WHEN cur.shown THEN cur.runtime_h END, 'h', 'calculated', cur.window_start, cur.window_end,
|
|
'generator running hours in the same intervals' || coalesce(cur.state, '')),
|
|
(3, 'Runtime per day', CASE WHEN cur.shown THEN cur.runtime_h / NULLIF(cur.matched_days, 0) END, 'h/day', 'calculated',
|
|
cur.window_start, cur.window_end, 'matched runtime / matched days' || coalesce(cur.state, '')),
|
|
(4, 'Intervals used', CASE WHEN cur.shown THEN ninp.n::numeric END, 'intervals', 'calculated', cur.window_start, cur.window_end,
|
|
'included inputs of the L/h baseline version' || coalesce(cur.state, '')),
|
|
(5, 'Fuel per calendar day, whole record', m4.fuel_l / NULLIF(m4.days, 0), 'L/day', 'calculated',
|
|
m4.first_on, m4.last_on, 'stock balance over all usable intervals / calendar days; not per running hour'),
|
|
(6, 'L/day reference (baseline version)',
|
|
CASE WHEN lday.status = 'approved' OR lday.inputs_unchanged THEN lday.value END, 'L/day', 'calculated',
|
|
lday.window_start, lday.window_end,
|
|
CASE WHEN lday.code IS NULL THEN 'no L/day baseline version'
|
|
ELSE 'version ' || lday.code || ' (' || lday.status || '), fixed window; not per running hour'
|
|
|| CASE WHEN lday.inputs_unchanged THEN ''
|
|
WHEN lday.status = 'approved' THEN '; inputs changed since approval: review needed'
|
|
ELSE '; inputs changed since computation: re-propose' END END),
|
|
(7, 'Days covered by usable stock intervals', m4.days::numeric, 'days', 'calculated', m4.first_on, m4.last_on,
|
|
'compare with the record span'),
|
|
(8, 'Record span', (span.last_on - span.first_on)::numeric, 'days', 'recorded', span.first_on, span.last_on,
|
|
'first to last live record'),
|
|
(9, 'Deliveries recorded', coalesce(del.n, 0)::numeric, 'deliveries', 'recorded', span.first_on, span.last_on,
|
|
'one per delivery event'),
|
|
(10, 'Timer readings', coalesce(rdg.n, 0)::numeric, 'readings', 'recorded', NULL::date, NULL::date,
|
|
CASE WHEN coalesce(rdg.n, 0) = 0 THEN 'no hour-meter readings' END),
|
|
(11, 'Grid energy', kwh.kwh, 'kWh', 'recorded', kwh.first_m, kwh.last_m,
|
|
CASE WHEN coalesce(kwh.n_kwh, 0) = 0
|
|
THEN 'no kWh records; bills are cost only and are not converted to fuel or hours' END)
|
|
) AS x(ord, measure, value, unit, kind, window_start, window_end, note);
|
|
|
|
-- R2: timer readings as recorded (no rows in v1).
|
|
CREATE VIEW public.v_hour_meter_evidence AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
m.source_label::varchar(40) AS meter_label,
|
|
(CASE WHEN m.asset_uid IS NULL THEN 'unmapped'
|
|
ELSE m.asset_uid || ' (' || m.mapping_status || ')' END)::varchar(60) AS mapped_set,
|
|
v.visit_on AS reading_date, h.date_basis::varchar(30) AS date_basis,
|
|
round(h.hours, 1)::numeric(9,1) AS hours, h.reading_state::varchar(16) AS reading_state,
|
|
h.source_field::varchar(16) AS source_field, e.epoch_no::integer AS epoch_no,
|
|
h.claim_status::varchar(12) AS claim_status
|
|
FROM fm.hour_meter_reading h
|
|
JOIN fm.runtime_meter m ON m.meter_id = h.meter_id
|
|
JOIN fm.visit v ON v.visit_id = h.visit_id
|
|
JOIN public.v_site s ON s.site_uid = m.site_uid
|
|
LEFT JOIN fm.meter_epoch e ON e.epoch_id = h.epoch_id
|
|
WHERE h.claim_status IN ('asserted','reviewed','disputed');
|
|
|
|
-- B5: assumption versions in force or proposed; a coverage assumption lists its members.
|
|
CREATE VIEW public.v_assumption AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
x.code::varchar(60) AS code, x.topic::varchar(30) AS topic, x.asset_uid::varchar(40) AS asset_uid,
|
|
x.value_num::numeric(10,3) AS value, x.low_num::numeric(10,3) AS value_low,
|
|
x.high_num::numeric(10,3) AS value_high, x.unit::varchar(20) AS unit,
|
|
x.applies_from, x.applies_to, x.basis::varchar(20) AS basis, x.derived_from_fuel,
|
|
x.status::varchar(12) AS status, x.accepted_on, x.decision_ref::varchar(12) AS decision_ref,
|
|
x.rationale::varchar(400) AS rationale, x.contradicted_by::varchar(200) AS contradicted_by,
|
|
mem.members::varchar(400) AS members
|
|
FROM fm.assumption x
|
|
JOIN public.v_site s ON s.site_uid = x.site_uid
|
|
LEFT JOIN LATERAL (
|
|
SELECT string_agg(m.member_role || ': ' || coalesce(m.meter_id, m.asset_uid), '; '
|
|
ORDER BY m.member_role, coalesce(m.meter_id, m.asset_uid)) AS members
|
|
FROM fm.assumption_member m WHERE m.assumption_id = x.assumption_id) mem ON true
|
|
WHERE x.status <> 'superseded';
|
|
|
|
-- B1: baseline versions, with whether their inputs are unchanged today.
|
|
CREATE VIEW public.v_baseline_version AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
b.code::varchar(60) AS code, b.metric::varchar(40) AS metric, b.status::varchar(12) AS status,
|
|
b.population::varchar(200) AS population,
|
|
round(b.value, 3)::numeric(8,3) AS value, round(b.range_low, 3)::numeric(8,3) AS range_low,
|
|
round(b.range_high, 3)::numeric(8,3) AS range_high,
|
|
(CASE WHEN b.metric = 'fuel_l_per_calendar_day' THEN 'L/day' ELSE 'L/h' END)::varchar(8) AS unit,
|
|
b.window_start, b.window_end, round(b.fuel_l, 1)::numeric(10,1) AS fuel_l,
|
|
round(b.runtime_h, 1)::numeric(10,1) AS runtime_h, b.matched_days::integer AS matched_days,
|
|
b.policy_version::varchar(10) AS policy_version, b.meets_policy, b.policy_check::varchar(300) AS policy_check,
|
|
b.evidence_grade::varchar(2) AS evidence_grade, b.conditions::varchar(200) AS conditions,
|
|
b.season_note::varchar(200) AS season_note, b.approved_on, b.approved_by_role::varchar(40) AS approved_by_role,
|
|
b.decision_ref::varchar(12) AS decision_ref, coalesce(val.inputs_unchanged, false) AS inputs_unchanged,
|
|
(b.computed_at AT TIME ZONE 'Asia/Kabul')::timestamp(0) AS computed_at
|
|
FROM fm.baseline_version b
|
|
JOIN public.v_site s ON s.site_uid = b.site_uid
|
|
LEFT JOIN fm.v_baseline_validity val ON val.baseline_id = b.baseline_id;
|
|
|
|
-- B2: the inputs of each version, with dependency drift since computation.
|
|
CREATE VIEW public.v_baseline_input AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
b.code::varchar(60) AS baseline_code, b.status::varchar(12) AS baseline_status,
|
|
(to_char(bi.start_on, 'YYYY-MM-DD') || ' to ' || to_char(bi.end_on, 'YYYY-MM-DD'))::varchar(30) AS interval_label,
|
|
bi.start_on, bi.end_on, bi.included, bi.exclusion_reason::varchar(300) AS exclusion_reason,
|
|
round(bi.fuel_l, 1)::numeric(10,1) AS fuel_l, round(bi.runtime_h, 1)::numeric(10,1) AS runtime_h,
|
|
bi.days::integer AS days, bi.eligibility_at_computation::varchar(12) AS eligibility_at_computation,
|
|
bi.evidence_grade_at_computation::varchar(2) AS evidence_grade_at_computation,
|
|
(d.interval_key IS NULL OR d.stock_dep_sha256 <> bi.stock_dep_sha256
|
|
OR (b.metric <> 'fuel_l_per_calendar_day'
|
|
AND d.runtime_dep_sha256 IS DISTINCT FROM bi.runtime_dep_sha256)) AS dependencies_changed
|
|
FROM fm.baseline_input bi
|
|
JOIN fm.baseline_version b ON b.baseline_id = bi.baseline_id
|
|
JOIN public.v_site s ON s.site_uid = bi.site_uid
|
|
LEFT JOIN fm.v_interval_dependency d ON d.open_visit_id = bi.open_visit_id AND d.close_visit_id = bi.close_visit_id;
|
|
|
|
-- B7: documents the site's figures rest on (the document itself is never served).
|
|
CREATE VIEW public.v_evidence AS
|
|
WITH cites AS (
|
|
SELECT site_uid, source_sha256, 'fuel movement' AS record_type
|
|
FROM fm.fuel_movement WHERE claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT site_uid, source_sha256, 'stock reading'
|
|
FROM fm.stock_observation WHERE claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT m.site_uid, h.source_sha256, 'timer reading'
|
|
FROM fm.hour_meter_reading h JOIN fm.runtime_meter m ON m.meter_id = h.meter_id
|
|
WHERE h.claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT site_uid, source_sha256, 'electricity bill'
|
|
FROM fm.electricity_bill WHERE claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT site_uid, source_sha256, 'imported calculation'
|
|
FROM fm.imported_calculation WHERE site_uid IS NOT NULL AND claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT p.site_uid, e.source_sha256, 'equipment specification'
|
|
FROM fm.equipment_spec e JOIN fm.asset_placement p ON p.asset_uid = e.asset_uid
|
|
WHERE e.source_sha256 IS NOT NULL AND e.claim_status IN ('asserted','reviewed','disputed')
|
|
UNION SELECT site_uid, source_sha256, 'assumption'
|
|
FROM fm.assumption WHERE source_sha256 IS NOT NULL AND status <> 'superseded')
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
left(d.sha256, 16)::varchar(16) AS document_key, d.title::varchar(200) AS title,
|
|
d.kind::varchar(20) AS kind, d.issuer::varchar(80) AS issuer, d.document_date,
|
|
string_agg(c.record_type, ', ' ORDER BY c.record_type)::varchar(200) AS used_for,
|
|
d.contains_personal_data
|
|
FROM cites c
|
|
JOIN fm.source_document d ON d.sha256 = c.source_sha256
|
|
JOIN public.v_site s ON s.site_uid = c.site_uid
|
|
GROUP BY s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
d.sha256, d.title, d.kind, d.issuer, d.document_date, d.contains_personal_data;
|
|
|
|
-- F1, F4: one row per delivery event; quantity NULL = not recorded. No monthly sums.
|
|
CREATE VIEW public.v_delivery AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
e.event_key::varchar(80) AS event_key, v.visit_on AS delivery_date, v.seq_in_day::integer AS seq_in_day,
|
|
round(e.litres, 1)::numeric(8,1) AS delivered_l,
|
|
b.unit_price::numeric(8,2) AS unit_price, b.price_currency::varchar(8) AS currency,
|
|
b.record_ref::varchar(60) AS source_ref,
|
|
e.n_claims::integer AS n_sources, (e.spread_l > 0) AS sources_disagree, e.all_reviewed AS reviewed
|
|
FROM fm.v_movement_event e
|
|
JOIN fm.v_visit_live v ON v.visit_id = e.visit_id
|
|
JOIN public.v_site s ON s.site_uid = v.site_uid
|
|
CROSS JOIN LATERAL ( -- price and reference of the best-ranked claim;
|
|
-- a disputed claim gives the reference only, never a price
|
|
SELECT CASE WHEN m.claim_status <> 'disputed' THEN m.unit_price END AS unit_price,
|
|
CASE WHEN m.claim_status <> 'disputed' THEN m.price_currency END AS price_currency, m.record_ref
|
|
FROM fm.fuel_movement m JOIN fm.v_source_rank r ON r.sha256 = m.source_sha256
|
|
WHERE m.event_key = e.event_key AND m.claim_status IN ('asserted','reviewed','disputed')
|
|
ORDER BY (m.claim_status = 'disputed'), r.rank, m.movement_id LIMIT 1) b
|
|
WHERE e.kind = 'delivery';
|
|
|
|
-- F2: stock readings as recorded, plus after-delivery points derived as before + delivered.
|
|
CREATE VIEW public.v_stock_reading AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
v.visit_on AS observed_on, v.seq_in_day::integer AS seq_in_day,
|
|
(CASE st.phase WHEN 'before_delivery' THEN 'before delivery' WHEN 'after_delivery' THEN 'after delivery'
|
|
ELSE 'stock check' END)::varchar(20) AS reading_type,
|
|
round(st.litres, 1)::numeric(8,1) AS litres, 'recorded'::varchar(12) AS value_kind,
|
|
round(st.spread_l, 1)::numeric(8,1) AS spread_l, st.all_reviewed AS reviewed
|
|
FROM fm.v_visit_stock st
|
|
JOIN fm.v_visit_live v ON v.visit_id = st.visit_id
|
|
JOIN public.v_site s ON s.site_uid = v.site_uid
|
|
UNION ALL
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
p.visit_on, p.seq_in_day::integer, 'after delivery'::varchar(20),
|
|
round(p.depart_l, 1)::numeric(8,1), 'calculated'::varchar(12),
|
|
round(p.depart_spread_l, 1)::numeric(8,1), p.depart_reviewed
|
|
FROM fm.v_stock_point p
|
|
JOIN public.v_site s ON s.site_uid = p.site_uid
|
|
WHERE p.depart_basis = 'derived';
|
|
|
|
-- F3, F4, B3, B4: one row per stock interval, with its L/h evaluation. The approved baseline is set
|
|
-- beside an interval only when both have the same metric and population (B4 compares like with like).
|
|
CREATE VIEW public.v_interval AS
|
|
WITH cur AS (SELECT bs.site_uid, bs.baseline_code, bs.baseline_status, bs.lph_estimate, b.metric, b.population_key
|
|
FROM fm.v_site_baseline_status bs LEFT JOIN fm.baseline_version b ON b.code = bs.baseline_code),
|
|
inb AS ( -- the inputs of each site's KPI version
|
|
SELECT bi.open_visit_id, bi.close_visit_id, bi.included
|
|
FROM fm.baseline_input bi
|
|
JOIN fm.baseline_version b ON b.baseline_id = bi.baseline_id
|
|
JOIN cur ON cur.baseline_code = b.code),
|
|
cond AS ( -- assumptions a valid interval depends on
|
|
SELECT l.interval_key, string_agg(DISTINCT x.code, ', ') AS codes
|
|
FROM fm.v_lph_interval l
|
|
JOIN fm.assumption x ON x.site_uid = l.site_uid AND x.status IN ('proposed','accepted')
|
|
AND x.topic IN ('generator_idle','meter_coverage')
|
|
AND daterange(x.applies_from, x.applies_to, '[]') @> daterange(l.start_on, l.end_on, '[]')
|
|
WHERE l.population_valid
|
|
GROUP BY l.interval_key)
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
(to_char(l.start_on, 'YYYY-MM-DD') || ' to ' || to_char(l.end_on, 'YYYY-MM-DD'))::varchar(30) AS interval_label,
|
|
l.start_on, l.end_on, l.days::integer AS days,
|
|
round(l.opening_l, 1)::numeric(9,1) AS opening_l, l.opening_basis::varchar(8) AS opening_basis,
|
|
round(l.deliveries_between_l, 1)::numeric(9,1) AS deliveries_l,
|
|
round(l.out_l, 1)::numeric(9,1) AS withdrawals_l,
|
|
round(l.closing_l, 1)::numeric(9,1) AS closing_l, l.closing_basis::varchar(8) AS closing_basis,
|
|
round(l.consumed_l, 1)::numeric(9,1) AS fuel_used_l, round(l.consumed_unc_l, 1)::numeric(9,1) AS fuel_used_unc_l,
|
|
round(l.consumed_l / NULLIF(l.days, 0), 1)::numeric(8,1) AS fuel_l_per_day,
|
|
l.stock_status::varchar(40) AS stock_status,
|
|
round(l.runtime_h, 1)::numeric(9,1) AS runtime_h,
|
|
round(l.runtime_h / NULLIF(l.days, 0), 2)::numeric(6,2) AS runtime_h_per_day,
|
|
coalesce(l.n_meters, 0)::integer AS n_meters, l.metric::varchar(40) AS metric,
|
|
l.population_key::varchar(200) AS population_key, -- pool only within one metric and population
|
|
round(l.l_per_h, 3)::numeric(8,3) AS l_per_h, round(l.l_per_h_low, 3)::numeric(8,3) AS l_per_h_low,
|
|
round(l.l_per_h_high, 3)::numeric(8,3) AS l_per_h_high,
|
|
l.eligibility::varchar(12) AS eligibility, nullif(l.exclusion_reasons, '')::varchar(600) AS exclusion_reasons,
|
|
l.evidence_grade::varchar(2) AS evidence_grade, cond.codes::varchar(200) AS conditions,
|
|
coalesce(inb.included, false) AS in_current_baseline,
|
|
coalesce(cur.baseline_status = 'approved' AND l.metric = cur.metric
|
|
AND l.population_key = cur.population_key, false) AS baseline_comparable,
|
|
(CASE WHEN cur.baseline_status = 'approved' AND l.metric = cur.metric
|
|
AND l.population_key = cur.population_key
|
|
THEN cur.lph_estimate END)::numeric(8,3) AS current_baseline_l_per_h
|
|
FROM fm.v_lph_interval l
|
|
JOIN public.v_site s ON s.site_uid = l.site_uid
|
|
JOIN cur ON cur.site_uid = l.site_uid
|
|
LEFT JOIN inb ON inb.open_visit_id = l.open_visit_id AND inb.close_visit_id = l.close_visit_id
|
|
LEFT JOIN cond ON cond.interval_key = l.interval_key;
|
|
|
|
-- R1: hours per day by meter interval (no rows in v1). Every pair is listed with its flags; runtime
|
|
-- and h/day only for a valid pair (one epoch, not decreasing, at most 24 h per day).
|
|
CREATE VIEW public.v_runtime AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
m.source_label::varchar(40) AS meter_label,
|
|
(CASE WHEN m.asset_uid IS NULL THEN 'unmapped'
|
|
ELSE m.asset_uid || ' (' || m.mapping_status || ')' END)::varchar(60) AS mapped_set,
|
|
(m.source_label || ': ' || to_char(r.start_on, 'YYYY-MM-DD') || ' to '
|
|
|| to_char(r.end_on, 'YYYY-MM-DD'))::varchar(80) AS interval_label,
|
|
r.start_on, r.end_on, r.days::integer AS days,
|
|
(CASE WHEN v.pair_valid THEN round(r.runtime_h, 1) END)::numeric(9,1) AS runtime_h,
|
|
(CASE WHEN v.pair_valid THEN round(r.h_per_day, 2) END)::numeric(6,2) AS h_per_day,
|
|
v.pair_valid, r.same_epoch, r.monotonic, r.over_24h_per_day, r.reviewed
|
|
FROM fm.v_runtime_pair r
|
|
CROSS JOIN LATERAL (SELECT coalesce(r.same_epoch AND r.monotonic AND NOT r.over_24h_per_day, false) AS pair_valid) v
|
|
JOIN fm.runtime_meter m ON m.meter_id = r.meter_id
|
|
JOIN public.v_site s ON s.site_uid = r.site_uid;
|
|
|
|
-- S3 (Gantt): which periods each kind of evidence covers; Kabul midnight as timestamp(0).
|
|
CREATE VIEW public.v_coverage AS
|
|
WITH spans AS (
|
|
SELECT site_uid, '1 Stock intervals' AS lane, stock_status AS status, start_on, end_on
|
|
FROM fm.v_stock_interval
|
|
UNION ALL
|
|
SELECT r.site_uid, '2 Runtime: ' || m.source_label,
|
|
CASE WHEN r.same_epoch AND r.monotonic AND NOT r.over_24h_per_day THEN 'ok' ELSE 'flagged' END,
|
|
r.start_on, r.end_on
|
|
FROM fm.v_runtime_pair r JOIN fm.runtime_meter m ON m.meter_id = r.meter_id
|
|
UNION ALL
|
|
SELECT site_uid, '3 Baseline window', baseline_status, lph_window_start, lph_window_end
|
|
FROM fm.v_site_baseline_status WHERE lph_window_start IS NOT NULL
|
|
UNION ALL
|
|
SELECT site_uid, '4 Electricity bills',
|
|
CASE WHEN claim_status = 'disputed' THEN 'disputed' WHEN kwh IS NULL THEN 'cost only' ELSE 'kWh' END,
|
|
coalesce(period_start, bill_month), coalesce(period_end, (bill_month + interval '1 month')::date)
|
|
FROM fm.electricity_bill WHERE claim_status IN ('asserted','reviewed','disputed'))
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
x.lane::varchar(60) AS lane, x.status::varchar(40) AS status, x.start_on, x.end_on,
|
|
x.start_on::timestamp(0) AS start_ts, x.end_on::timestamp(0) AS end_ts
|
|
FROM spans x JOIN public.v_site s ON s.site_uid = x.site_uid;
|
|
|
|
-- B9: stock-balance fuel against engine-measured fuel inside the interval. No rows until flow-meter
|
|
-- data exist. The window runs from the opening visit's departure to the closing visit's arrival
|
|
-- (Kabul time) when both times are recorded, else from midnight to midnight. covered_h = the hours
|
|
-- of that window the generator's records cover (live records never overlap per asset). The
|
|
-- difference is shown only for an `ok` stock interval with both times recorded, in which every
|
|
-- generator is placed at the site throughout (one placement spans the interval, as in
|
|
-- fm.v_lph_interval) and covered for the whole window.
|
|
CREATE VIEW public.v_reconciliation AS
|
|
WITH gen AS ( -- one row per interval and generator placed at the site at any time in it
|
|
SELECT l.interval_key, l.site_uid, l.start_on, l.end_on, l.consumed_l, l.stock_status, p.asset_uid,
|
|
bool_or(daterange(coalesce(p.occupied_from, '-infinity'::date), p.occupied_to, '[)')
|
|
@> daterange(l.start_on, l.end_on, '[]')) AS placed_throughout,
|
|
(o.time_out_local IS NOT NULL AND c.time_in_local IS NOT NULL) AS times_known,
|
|
(l.start_on + coalesce(o.time_out_local, '00:00'::time)) AT TIME ZONE 'Asia/Kabul' AS t0,
|
|
(l.end_on + coalesce(c.time_in_local, '00:00'::time)) AT TIME ZONE 'Asia/Kabul' AS t1
|
|
FROM fm.v_stock_interval l
|
|
JOIN fm.visit o ON o.visit_id = l.open_visit_id
|
|
JOIN fm.visit c ON c.visit_id = l.close_visit_id
|
|
JOIN fm.asset_placement p ON p.site_uid = l.site_uid
|
|
JOIN fm.asset a ON a.asset_uid = p.asset_uid AND a.kind = 'generator'
|
|
WHERE daterange(coalesce(p.occupied_from, '-infinity'::date), p.occupied_to, '[)')
|
|
&& daterange(l.start_on, l.end_on, '[]')
|
|
GROUP BY l.interval_key, l.site_uid, l.start_on, l.end_on, l.consumed_l, l.stock_status, p.asset_uid,
|
|
o.time_out_local, c.time_in_local),
|
|
eng AS (
|
|
SELECT g.interval_key, g.asset_uid, sum(t.net_fuel_l) AS engine_l,
|
|
sum(extract(epoch FROM t.ended_at - t.started_at)) / 3600 AS covered_h,
|
|
sum(extract(epoch FROM t.ended_at - t.started_at)) >= extract(epoch FROM g.t1 - g.t0) AS full_cover
|
|
FROM gen g JOIN fm.engine_fuel_total t ON t.asset_uid = g.asset_uid
|
|
AND t.claim_status IN ('asserted','reviewed')
|
|
AND t.started_at >= g.t0 AND t.ended_at <= g.t1
|
|
GROUP BY g.interval_key, g.asset_uid, g.t0, g.t1),
|
|
tot AS (
|
|
SELECT g.interval_key, count(*) AS n_generators,
|
|
count(*) FILTER (WHERE g.placed_throughout AND e.full_cover) AS n_complete, sum(e.engine_l) AS engine_all_l
|
|
FROM gen g LEFT JOIN eng e ON e.interval_key = g.interval_key AND e.asset_uid = g.asset_uid
|
|
GROUP BY g.interval_key)
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
(to_char(g.start_on, 'YYYY-MM-DD') || ' to ' || to_char(g.end_on, 'YYYY-MM-DD'))::varchar(30) AS interval_label,
|
|
g.start_on, g.end_on, g.stock_status::varchar(40) AS stock_status, g.times_known AS endpoint_times_known,
|
|
g.asset_uid::varchar(40) AS asset_uid, g.placed_throughout,
|
|
round(g.consumed_l, 1)::numeric(9,1) AS stock_balance_l, round(e.engine_l, 1)::numeric(9,1) AS engine_l,
|
|
round(e.covered_h, 1)::numeric(9,1) AS covered_h,
|
|
round(extract(epoch FROM g.t1 - g.t0) / 3600, 1)::numeric(9,1) AS interval_h,
|
|
round(t.engine_all_l, 1)::numeric(9,1) AS engine_all_generators_l,
|
|
(t.n_complete = t.n_generators) AS all_generators_metered,
|
|
(CASE WHEN t.n_complete = t.n_generators AND g.times_known AND g.stock_status = 'ok'
|
|
THEN round(g.consumed_l - t.engine_all_l, 1) END)::numeric(9,1) AS difference_l,
|
|
(CASE WHEN t.n_complete = t.n_generators AND g.times_known AND g.stock_status = 'ok'
|
|
THEN round(100 * (g.consumed_l - t.engine_all_l) / NULLIF(t.engine_all_l, 0), 1) END)::numeric(7,1) AS difference_pct
|
|
FROM gen g
|
|
JOIN eng e ON e.interval_key = g.interval_key AND e.asset_uid = g.asset_uid
|
|
JOIN tot t ON t.interval_key = g.interval_key
|
|
JOIN public.v_site s ON s.site_uid = g.site_uid;
|
|
|
|
-- B6: one row per site and check; rows stay when ok.
|
|
CREATE VIEW public.v_data_quality AS
|
|
WITH c AS (
|
|
SELECT s.site_uid,
|
|
(SELECT count(*) FROM fm.stock_observation o WHERE o.site_uid = s.site_uid
|
|
AND o.litres_basis = 'placeholder_zero' AND o.claim_status IN ('asserted','reviewed','disputed')) AS n_placeholder,
|
|
(SELECT count(*) FROM fm.v_movement_event e JOIN fm.visit v ON v.visit_id = e.visit_id
|
|
WHERE v.site_uid = s.site_uid AND e.spread_l > 0)
|
|
+ (SELECT count(*) FROM fm.v_visit_stock st JOIN fm.visit v ON v.visit_id = st.visit_id
|
|
WHERE v.site_uid = s.site_uid AND st.spread_l > 0) AS n_conflicts,
|
|
(SELECT count(*) FROM fm.hour_meter_reading h JOIN fm.runtime_meter m ON m.meter_id = h.meter_id
|
|
WHERE m.site_uid = s.site_uid AND h.claim_status IN ('asserted','reviewed','disputed')) AS n_readings,
|
|
(SELECT count(*) FROM fm.runtime_meter m WHERE m.site_uid = s.site_uid AND m.mapping_status <> 'confirmed') AS n_unmapped,
|
|
(SELECT count(*) FROM fm.asset_placement p JOIN fm.asset a ON a.asset_uid = p.asset_uid
|
|
WHERE p.site_uid = s.site_uid AND a.kind = 'generator'
|
|
AND daterange(coalesce(p.occupied_from, '-infinity'::date), p.occupied_to, '[)')
|
|
@> (now() AT TIME ZONE 'Asia/Kabul')::date
|
|
AND NOT EXISTS (SELECT 1 FROM fm.runtime_meter m JOIN fm.hour_meter_reading h ON h.meter_id = m.meter_id
|
|
WHERE m.asset_uid = p.asset_uid AND m.mapping_status = 'confirmed'
|
|
AND h.claim_status IN ('asserted','reviewed'))) AS n_gen_no_readings,
|
|
(SELECT count(*) FROM fm.fuel_movement x WHERE x.site_uid = s.site_uid AND x.claim_status = 'asserted')
|
|
+ (SELECT count(*) FROM fm.stock_observation x WHERE x.site_uid = s.site_uid AND x.claim_status = 'asserted')
|
|
+ (SELECT count(*) FROM fm.hour_meter_reading x JOIN fm.runtime_meter m ON m.meter_id = x.meter_id
|
|
WHERE m.site_uid = s.site_uid AND x.claim_status = 'asserted')
|
|
+ (SELECT count(*) FROM fm.electricity_bill x WHERE x.site_uid = s.site_uid AND x.claim_status = 'asserted') AS n_unreviewed,
|
|
(SELECT count(*) FROM fm.v_stock_point p WHERE p.site_uid = s.site_uid
|
|
AND abs(p.after_vs_before_plus_delivered_l) > 0.5) AS n_after_mismatch,
|
|
(SELECT count(*) FROM fm.v_stock_interval i WHERE i.site_uid = s.site_uid
|
|
AND i.stock_status IN ('negative','same_day')) AS n_neg_same_day,
|
|
(SELECT count(*) FROM fm.v_stock_interval i WHERE i.site_uid = s.site_uid
|
|
AND i.stock_status NOT IN ('ok','negative','same_day')) AS n_gaps,
|
|
(SELECT count(*) FROM fm.electricity_bill b WHERE b.site_uid = s.site_uid
|
|
AND b.claim_status IN ('asserted','reviewed','disputed')) AS n_bills,
|
|
(SELECT count(*) FROM fm.electricity_bill b WHERE b.site_uid = s.site_uid
|
|
AND (b.kwh IS NULL OR b.claim_status = 'disputed')
|
|
AND b.claim_status IN ('asserted','reviewed','disputed')) AS n_bills_no_kwh, -- no usable kWh
|
|
(SELECT count(*) FROM fm.fuel_movement m WHERE m.site_uid = s.site_uid
|
|
AND m.kind IN ('withdrawal','drain','spill','loss_reported','internal_transfer')
|
|
AND m.claim_status IN ('asserted','reviewed','disputed')) AS n_transfer_records,
|
|
(SELECT count(*) FROM fm.baseline_version b JOIN fm.v_baseline_validity v ON v.baseline_id = b.baseline_id
|
|
WHERE b.site_uid = s.site_uid AND b.status IN ('proposed','approved') AND NOT v.inputs_unchanged) AS n_drift,
|
|
(SELECT count(*) FROM fm.v_review_stale r WHERE r.site_uid = s.site_uid) AS n_stale,
|
|
(SELECT count(*) FROM fm.v_visit_provenance_only v WHERE v.site_uid = s.site_uid) AS n_provenance_only,
|
|
(SELECT count(*) FROM fm.v_lph_interval l WHERE l.site_uid = s.site_uid
|
|
AND l.exclusion_reasons LIKE '%confirmed for a generator not placed here%') AS n_unplaced_counter,
|
|
(SELECT count(*) FROM fm.v_lph_interval l WHERE l.site_uid = s.site_uid
|
|
AND l.exclusion_reasons LIKE '%meter-coverage assumption % does not name%') AS n_cov_mismatch
|
|
FROM fm.site s)
|
|
SELECT vs.site_uid, vs.site_code, vs.site_name, vs.site_label,
|
|
x.ord::integer AS check_order, x.code::varchar(40) AS check_code, x.label::varchar(80) AS label,
|
|
x.status::varchar(10) AS status, x.detail::varchar(300) AS detail
|
|
FROM c JOIN public.v_site vs ON vs.site_uid = c.site_uid
|
|
CROSS JOIN LATERAL (VALUES
|
|
(1, 'placeholder_rows', 'Placeholder rows',
|
|
CASE WHEN c.n_placeholder > 0 THEN 'warning' ELSE 'ok' END,
|
|
c.n_placeholder || ' row(s) imported as "no stock reading", never as 0'),
|
|
(2, 'claim_conflicts', 'Conflicting claims',
|
|
CASE WHEN c.n_conflicts > 0 THEN 'conflict' ELSE 'ok' END,
|
|
c.n_conflicts || ' movement or stock reading(s) with disagreeing sources'),
|
|
(3, 'runtime_evidence', 'Runtime evidence',
|
|
CASE WHEN c.n_readings = 0 THEN 'missing' ELSE 'ok' END,
|
|
CASE WHEN c.n_readings = 0 THEN 'no hour-meter readings: no L/h can be calculated'
|
|
ELSE c.n_readings || ' timer reading(s)' END),
|
|
(4, 'location', 'Location',
|
|
CASE vs.location_status WHEN 'accepted' THEN 'ok' WHEN 'awaiting acceptance' THEN 'warning' ELSE 'missing' END,
|
|
vs.location_status::text),
|
|
(5, 'unmapped_meters', 'Counters not tied to a generator',
|
|
CASE WHEN c.n_unmapped > 0 THEN 'warning' ELSE 'ok' END, c.n_unmapped || ' counter(s)'),
|
|
(6, 'generators_without_readings', 'Generators without timer readings',
|
|
CASE WHEN c.n_gen_no_readings > 0 THEN 'missing' ELSE 'ok' END, c.n_gen_no_readings || ' generator(s) placed now'),
|
|
(7, 'unreviewed_claims', 'Claims not yet reviewed against the originals',
|
|
CASE WHEN c.n_unreviewed > 0 THEN 'warning' ELSE 'ok' END, c.n_unreviewed || ' claim(s)'),
|
|
(8, 'after_vs_before_plus_delivered', 'Stated after-delivery stock differs from before + delivered',
|
|
CASE WHEN c.n_after_mismatch > 0 THEN 'conflict' ELSE 'ok' END, c.n_after_mismatch || ' visit(s)'),
|
|
(9, 'negative_or_same_day_intervals', 'Negative or same-day stock intervals',
|
|
CASE WHEN c.n_neg_same_day > 0 THEN 'warning' ELSE 'ok' END, c.n_neg_same_day || ' interval(s)'),
|
|
(10, 'interval_gaps', 'Stock intervals excluded for missing or unordered data',
|
|
CASE WHEN c.n_gaps > 0 THEN 'warning' ELSE 'ok' END, c.n_gaps || ' interval(s)'),
|
|
(11, 'no_kwh', 'Grid energy (kWh)',
|
|
CASE WHEN c.n_bills = 0 OR c.n_bills_no_kwh > 0 THEN 'missing' ELSE 'ok' END,
|
|
CASE WHEN c.n_bills = 0 THEN 'no electricity bills'
|
|
WHEN c.n_bills_no_kwh > 0 THEN c.n_bills_no_kwh || ' bill(s) without usable kWh (cost only or disputed)'
|
|
ELSE 'kWh on every bill' END),
|
|
(12, 'transfer_records', 'Transfer, drain or loss records (HD-05)',
|
|
CASE WHEN c.n_transfer_records = 0 THEN 'warning' ELSE 'ok' END,
|
|
CASE WHEN c.n_transfer_records = 0 THEN 'none recorded: every stock balance assumes no unrecorded movements'
|
|
ELSE c.n_transfer_records || ' record(s)' END),
|
|
(13, 'baseline_input_drift', 'Baseline inputs changed since computation',
|
|
CASE WHEN c.n_drift > 0 THEN 'warning' ELSE 'ok' END, c.n_drift || ' proposed or approved version(s)'),
|
|
(14, 'approval_status', 'Baseline approval',
|
|
CASE WHEN vs.baseline_status = 'approved' THEN 'ok' ELSE 'warning' END,
|
|
vs.baseline_status_label || coalesce(' - ' || vs.baseline_validity_note, '')),
|
|
(15, 'stale_reviews', 'Review entries for values changed since',
|
|
CASE WHEN c.n_stale > 0 THEN 'warning' ELSE 'ok' END, c.n_stale || ' entr(y/ies)'),
|
|
(16, 'provenance_only_visits', 'Visits kept for provenance only',
|
|
CASE WHEN c.n_provenance_only > 0 THEN 'warning' ELSE 'ok' END,
|
|
c.n_provenance_only || ' visit(s) whose records were all retracted or superseded'),
|
|
(17, 'counter_on_unplaced_generator', 'Counters confirmed for a generator not placed here',
|
|
CASE WHEN c.n_unplaced_counter > 0 THEN 'conflict' ELSE 'ok' END, c.n_unplaced_counter || ' interval(s)'),
|
|
(18, 'coverage_assumption_mismatch', 'Meter-coverage assumption does not match the counters read',
|
|
CASE WHEN c.n_cov_mismatch > 0 THEN 'conflict' ELSE 'ok' END, c.n_cov_mismatch || ' interval(s)')
|
|
) AS x(ord, code, label, status, detail);
|
|
|
|
-- B10: specifications, labelled as such; live rows only. A disputed specification is listed with its
|
|
-- status and without its value.
|
|
CREATE VIEW public.v_spec_reference AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
p.slot_label::varchar(20) AS slot, e.asset_uid::varchar(40) AS asset_uid, a.kind::varchar(10) AS asset_kind,
|
|
concat_ws(' ', a.make, a.model)::varchar(120) AS make_model,
|
|
e.attribute::varchar(40) AS attribute, e.load_pct::numeric(5,1) AS load_pct,
|
|
(CASE WHEN e.claim_status <> 'disputed' THEN e.value_num END)::numeric(12,3) AS value,
|
|
(CASE WHEN e.claim_status <> 'disputed' THEN e.value_text END)::varchar(120) AS value_text, e.unit::varchar(20) AS unit,
|
|
e.basis::varchar(30) AS basis, e.applicability::varchar(12) AS applicability,
|
|
e.applicability_note::varchar(300) AS applicability_note,
|
|
'specification, not measured'::varchar(30) AS value_kind, e.claim_status::varchar(12) AS claim_status
|
|
FROM fm.equipment_spec e
|
|
JOIN fm.asset a ON a.asset_uid = e.asset_uid
|
|
JOIN (SELECT DISTINCT ON (asset_uid, site_uid) asset_uid, site_uid, slot_label
|
|
FROM fm.asset_placement ORDER BY asset_uid, site_uid, occupied_from DESC NULLS LAST) p ON p.asset_uid = e.asset_uid
|
|
JOIN public.v_site s ON s.site_uid = p.site_uid
|
|
WHERE e.claim_status IN ('asserted','reviewed','disputed');
|
|
|
|
-- M3 sensitivity table, reference tab only (shown only once Q9 approves a runtime assumption).
|
|
CREATE VIEW public.v_modelled_lph AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
m.code::varchar(60) AS assumption_code, m.status::varchar(12) AS status, m.basis::varchar(20) AS basis,
|
|
m.h_per_day::numeric(5,2) AS h_per_day, m.h_per_day_low::numeric(5,2) AS h_per_day_low,
|
|
m.h_per_day_high::numeric(5,2) AS h_per_day_high, m.window_start, m.window_end,
|
|
m.matched_days::integer AS matched_days, round(m.l_per_day, 1)::numeric(7,1) AS l_per_day,
|
|
round(m.modelled_l_per_h, 2)::numeric(6,2) AS modelled_l_per_h,
|
|
round(m.modelled_l_per_h_low, 2)::numeric(6,2) AS modelled_l_per_h_low,
|
|
round(m.modelled_l_per_h_high, 2)::numeric(6,2) AS modelled_l_per_h_high,
|
|
'modelled: L per calendar day / assumed runtime; not measured'::varchar(70) AS value_kind
|
|
FROM fm.v_modelled_lph m JOIN public.v_site s ON s.site_uid = m.site_uid;
|
|
|
|
-- R4: electricity cost per bill month; cost context only, never joined into fuel metrics. A
|
|
-- disputed bill stays listed, without amount or kWh.
|
|
CREATE VIEW public.v_electricity_cost AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
b.bill_month, b.period_start, b.period_end, (b.period_start IS NOT NULL) AS period_known,
|
|
(CASE WHEN b.claim_status <> 'disputed' THEN b.amount END)::numeric(12,2) AS amount, b.currency::varchar(8) AS currency,
|
|
b.currency_basis::varchar(10) AS currency_basis,
|
|
(CASE WHEN b.claim_status <> 'disputed' THEN b.kwh END)::numeric(10,1) AS kwh,
|
|
b.record_ref::varchar(60) AS source_ref, b.claim_status::varchar(12) AS claim_status
|
|
FROM fm.electricity_bill b JOIN public.v_site s ON s.site_uid = b.site_uid
|
|
WHERE b.claim_status IN ('asserted','reviewed','disputed');
|
|
|
|
-- B8: operator workbook calculations and our assessment; live rows only. A disputed transcription is
|
|
-- listed with its status and without its formula or value.
|
|
CREATE VIEW public.v_imported_calculation AS
|
|
SELECT s.site_uid, s.site_code, s.site_name, s.site_label,
|
|
c.cell_ref::varchar(40) AS cell_ref, c.source_label::varchar(80) AS source_label,
|
|
(CASE WHEN c.claim_status <> 'disputed' THEN c.formula END)::varchar(200) AS formula,
|
|
(CASE WHEN c.claim_status <> 'disputed' THEN c.cached_value END)::numeric(14,6) AS cached_value,
|
|
c.unit_as_labelled::varchar(30) AS unit_as_labelled, c.our_reading::varchar(300) AS our_reading,
|
|
c.baseline_use::varchar(12) AS baseline_use, c.exclusion_reason::varchar(300) AS exclusion_reason,
|
|
left(c.source_sha256, 16)::varchar(16) AS document_key, c.claim_status::varchar(12) AS claim_status
|
|
FROM fm.imported_calculation c JOIN public.v_site s ON s.site_uid = c.site_uid
|
|
WHERE c.claim_status IN ('asserted','reviewed','disputed');
|
|
|
|
COMMIT;
|