Build spec and host brief carried in from C:\Claude and WRPS/02-env; the plant model (equipment, tags, alarm bitmask, enums, unit conversions) is derived from WRPS/04-plc/register-map.csv, WRPS/05-scada/modbus/scada-points.csv and WRPS-CTL-003. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
163 lines
6.2 KiB
YAML
163 lines
6.2 KiB
YAML
# =============================================================================
|
|
# process_values.yml — sampled analogue history.
|
|
#
|
|
# SOURCE: fixture.process_value_history while USE_FIXTURES=true; the agreed imh
|
|
# process value table from Phase 4.
|
|
#
|
|
# THE THING THAT WILL BITE WHEN imh IS CONNECTED: CI Server historises with a
|
|
# deadband, so real samples are IRREGULAR. The fixtures are regular 1-minute
|
|
# samples. Any measure that averages rows rather than time-weighting them will
|
|
# look correct on fixtures and be wrong on imh - a flat period compresses to
|
|
# one row and a noisy period to hundreds, so a plain avg is weighted by how
|
|
# interesting the signal was. avg_value below is a plain average and is
|
|
# documented as an approximation; time_weighted_avg is the one to trust, and it
|
|
# must be re-verified against imh at the Phase 5 gate.
|
|
#
|
|
# SENTINELS: PS_STN_TIME_TO_SPILL_WEIR and PS_STN_TIME_TO_LSHH use 32767 to
|
|
# mean "drawing down or holding" - it is not a duration. Every measure here
|
|
# excludes it. Do not remove that filter to make a number look tidier.
|
|
#
|
|
# QUALITY: rows with quality other than GOOD are excluded from every measure.
|
|
# A BAD sample from a failed transmitter is not a low reading.
|
|
#
|
|
# UNITS: whatever the historian stores, which is not always what the PLC works
|
|
# in. Wet well level is historised as percent of the spill weir crest (raw mm
|
|
# divided by 60): 100.0 % = 6000 mm. See db/seed/tags.csv for every conversion.
|
|
# =============================================================================
|
|
|
|
cubes:
|
|
- name: process_values
|
|
sql_table: fixture.process_value_history # -> imh PV table at Phase 4
|
|
description: >
|
|
Sampled analogue history - wet well level, inflow, discharge flow, drive
|
|
speed, run hours. This is what makes an advisory question answerable with
|
|
evidence; you cannot answer a flow question from alarms.
|
|
|
|
joins:
|
|
- name: equipment
|
|
sql: "{CUBE}.equipment_id = {equipment}.equipment_id"
|
|
relationship: many_to_one
|
|
|
|
dimensions:
|
|
- name: id
|
|
sql: "{CUBE}.tag_id || '@' || {CUBE}.sample_time"
|
|
type: string
|
|
primary_key: true
|
|
|
|
- name: sample_time
|
|
sql: sample_time
|
|
type: time
|
|
description: Stored UTC, presented in SITE_TIMEZONE. Converted once, here.
|
|
|
|
- name: tag_id
|
|
sql: tag_id
|
|
type: string
|
|
|
|
- name: equipment_id
|
|
sql: equipment_id
|
|
type: string
|
|
|
|
- name: engineering_unit
|
|
sql: engineering_unit
|
|
type: string
|
|
description: >
|
|
Always report this with the number. A level of 86.7 is a percentage
|
|
of the weir crest, not a metre reading.
|
|
|
|
- name: quality
|
|
sql: quality
|
|
type: string
|
|
|
|
- name: is_fixture
|
|
sql: is_fixture
|
|
type: boolean
|
|
|
|
measures:
|
|
- name: sample_count
|
|
type: count
|
|
filters:
|
|
- sql: "{CUBE}.quality = 'GOOD' AND {CUBE}.value <> 32767"
|
|
|
|
- name: avg_value
|
|
sql: value
|
|
type: avg
|
|
filters:
|
|
- sql: "{CUBE}.quality = 'GOOD' AND {CUBE}.value <> 32767"
|
|
description: >
|
|
APPROXIMATION. Plain average of samples. Correct on the regular
|
|
fixture data; biased on deadband-compressed imh data. Prefer
|
|
time_weighted_avg for anything an engineer will check.
|
|
|
|
- name: time_weighted_avg
|
|
sql: >
|
|
SUM({CUBE}.value * EXTRACT(EPOCH FROM (
|
|
LEAD({CUBE}.sample_time) OVER (
|
|
PARTITION BY {CUBE}.tag_id ORDER BY {CUBE}.sample_time
|
|
) - {CUBE}.sample_time)))
|
|
/ NULLIF(SUM(EXTRACT(EPOCH FROM (
|
|
LEAD({CUBE}.sample_time) OVER (
|
|
PARTITION BY {CUBE}.tag_id ORDER BY {CUBE}.sample_time
|
|
) - {CUBE}.sample_time))), 0)
|
|
type: number
|
|
description: >
|
|
Time-weighted average - each sample weighted by how long it stood.
|
|
This is the honest average on deadband-compressed history. Verify it
|
|
against imh by hand at the Phase 5 gate before trusting it in prose.
|
|
|
|
- name: max_value
|
|
sql: value
|
|
type: max
|
|
filters:
|
|
- sql: "{CUBE}.quality = 'GOOD' AND {CUBE}.value <> 32767"
|
|
|
|
- name: min_value
|
|
sql: value
|
|
type: min
|
|
filters:
|
|
- sql: "{CUBE}.quality = 'GOOD' AND {CUBE}.value <> 32767"
|
|
|
|
- name: p95_value
|
|
sql: "PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY {CUBE}.value)"
|
|
type: number
|
|
filters:
|
|
- sql: "{CUBE}.quality = 'GOOD' AND {CUBE}.value <> 32767"
|
|
description: >
|
|
95th percentile. More useful than max for "how high does it normally
|
|
get", because max is one sample and often a transient.
|
|
|
|
- name: seconds_above_high_level_alarm
|
|
sql: >
|
|
SUM(CASE WHEN {CUBE}.tag_id = 'PS_STN_WET_WELL_LEVEL'
|
|
AND {CUBE}.value >= 86.7 THEN 60 ELSE 0 END)
|
|
type: number
|
|
description: >
|
|
Seconds the wet well spent above the high level alarm setpoint
|
|
(86.7 % = 5200 mm, the %MW8 default). ASSUMES A 60 SECOND SAMPLE
|
|
INTERVAL, true of the fixtures and NOT true of imh. When imh is
|
|
connected this must be rewritten to sum actual sample gaps - it is on
|
|
the Phase 5 gate list for exactly that reason. If the setpoint itself
|
|
was changed during the window (PS_STN_HIGH_LEVEL_ALARM_SP), this
|
|
measure is wrong and the answer must say so.
|
|
|
|
- name: seconds_above_lshh
|
|
sql: >
|
|
SUM(CASE WHEN {CUBE}.tag_id = 'PS_STN_WET_WELL_LEVEL'
|
|
AND {CUBE}.value >= 91.7 THEN 60 ELSE 0 END)
|
|
type: number
|
|
description: >
|
|
Seconds above LSHH (91.7 % = 5500 mm). Same 60 second assumption as
|
|
above. Any non-zero value here is worth reporting explicitly.
|
|
|
|
pre_aggregations:
|
|
- name: pv_by_hour
|
|
measures: [avg_value, max_value, min_value, sample_count]
|
|
dimensions: [tag_id, equipment_id, engineering_unit]
|
|
time_dimension: sample_time
|
|
granularity: hour
|
|
partition_granularity: month
|
|
refresh_key:
|
|
every: 10 minutes
|
|
build_range_start:
|
|
sql: "SELECT now() - interval '180 days'"
|
|
build_range_end:
|
|
sql: "SELECT now()"
|