lin001 is 2 vCPU with 3.8 GiB and no swap, shared with 27 other containers.
Cube was rebuilding three pre-aggregations over static fixture data on 10, 10
and 30 minute keys, and its own log shows what that cost:
"Previous interval #19593 was not finished with 30000 interval"
"Interval #19594 finished after 00:02:50"
A refresh taking 2m50s scheduled every 30 seconds, at interval #19594 -
overlapping and never catching up, for days. Every rebuild produced a
byte-identical result, because the fixtures do not change.
24 hours until imh makes the data genuinely live, at which point these get
tuned deliberately rather than left at a number that was never chosen. Cube
also suggests fewer partitions via rollup_lambda; that is a modelling change,
not a demo-day one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
193 lines
7.1 KiB
YAML
193 lines
7.1 KiB
YAML
# =============================================================================
|
|
# alarms.yml — alarm and event history.
|
|
#
|
|
# SOURCE: fixture.alarm_history while USE_FIXTURES=true. When imh is live this
|
|
# becomes the agreed imh alarm table and the column names below change with it.
|
|
# Nothing else in this file should need to change; that is the point of it.
|
|
#
|
|
# THREE DEFINITIONS THAT DECIDE WHETHER THE ANSWERS ARE RIGHT. They are here in
|
|
# comments because the person checking the number needs to read them, and they
|
|
# are not obvious from the measure names.
|
|
#
|
|
# 1. AN ALARM IS A TRANSITION INTO THE ACTIVE STATE.
|
|
# state = 'ACTIVE' only. RTN is the return-to-normal of the activation that
|
|
# preceded it, and ACK is an operator acknowledging one. Counting every row
|
|
# roughly doubles every answer. "6 times last week" must mean six
|
|
# activations.
|
|
#
|
|
# 2. "LAST WEEK" IS A ROLLING 7 x 24 h WINDOW IN SITE_TIMEZONE.
|
|
# Not the previous calendar week, not 7 calendar days. Storage is UTC and
|
|
# the conversion happens here, once. If someone means the calendar week they
|
|
# have to say so, and the answer must state the window it used.
|
|
#
|
|
# 3. CHATTERING IS 3 OR MORE ACTIVATIONS OF THE SAME TAG WITHIN 60 MINUTES.
|
|
# An arbitrary threshold, chosen to match the site's alarm rationalisation
|
|
# convention. It is stated in the answer whenever chattering is reported,
|
|
# because a different threshold gives a different story.
|
|
# =============================================================================
|
|
|
|
cubes:
|
|
- name: alarms
|
|
sql_table: fixture.alarm_history # -> imh alarm table at Phase 4
|
|
description: >
|
|
Alarm and event history for the Waterloo Road Pump Station. One row per
|
|
state transition. Activations only are counted as alarms.
|
|
|
|
joins:
|
|
- name: equipment
|
|
sql: "{CUBE}.equipment_id = {equipment}.equipment_id"
|
|
relationship: many_to_one
|
|
|
|
dimensions:
|
|
- name: alarm_id
|
|
sql: alarm_id
|
|
type: number
|
|
primary_key: true
|
|
|
|
- name: event_time
|
|
sql: event_time
|
|
type: time
|
|
description: Transition time. Stored UTC, presented in SITE_TIMEZONE.
|
|
|
|
- name: tag_id
|
|
sql: tag_id
|
|
type: string
|
|
|
|
- name: equipment_id
|
|
sql: equipment_id
|
|
type: string
|
|
|
|
- name: alarm_type
|
|
sql: alarm_type
|
|
type: string
|
|
description: >
|
|
HIGH_LEVEL, HIGH_HIGH_LEVEL, LOW_LOW_LEVEL, SPILL, PUMP_TRIP,
|
|
SEAL_LEAK, HIGH_VIBRATION, LEVEL_SIGNAL_FAULT, MAINS_FAILURE,
|
|
SETPOINT_REJECTED. These correspond to the bits of the PLC alarm
|
|
bitmask %QW17 - see db/seed/tags.csv, PS_STN_ALARM_BITMASK.
|
|
|
|
- name: state
|
|
sql: state
|
|
type: string
|
|
description: ACTIVE, RTN or ACK. Only ACTIVE counts as an alarm.
|
|
|
|
- name: priority
|
|
sql: priority
|
|
type: number
|
|
description: >
|
|
1 highest, 3 lowest. Priority 1 is SPILL, PUMP_TRIP and
|
|
LEVEL_SIGNAL_FAULT - losing the level signal on a well that can spill
|
|
is a priority 1 condition, and the fixtures already treat it as one.
|
|
This comment previously named only SPILL and PUMP_TRIP and disagreed
|
|
with the data, which matters because this is the line an engineer
|
|
reads when checking a priority_1_count.
|
|
|
|
- name: value
|
|
sql: value
|
|
type: number
|
|
description: Process value at the transition, in engineering_unit.
|
|
|
|
- name: is_fixture
|
|
sql: is_fixture
|
|
type: boolean
|
|
description: >
|
|
TRUE means this row came from db/002_fixtures.sql and is generated
|
|
test data, not plant history. The API surfaces this to the operator.
|
|
|
|
measures:
|
|
- name: alarm_count
|
|
type: count
|
|
filters:
|
|
- sql: "{CUBE}.state = 'ACTIVE'"
|
|
description: >
|
|
Number of alarm ACTIVATIONS. Definition 1 above. This is the measure
|
|
behind "how many times did X alarm come up".
|
|
|
|
- name: transition_count
|
|
type: count
|
|
description: >
|
|
Every row including RTN and ACK. Diagnostics only - do not answer an
|
|
operator question with this.
|
|
|
|
- name: distinct_tags
|
|
sql: tag_id
|
|
type: count_distinct
|
|
filters:
|
|
- sql: "{CUBE}.state = 'ACTIVE'"
|
|
description: How many different tags alarmed in the window.
|
|
|
|
# DEFERRED DEFECT - these two return UTC, not SITE_TIMEZONE. Cube converts
|
|
# time DIMENSIONS to the query timezone; a min/max MEASURE over a
|
|
# timestamp comes back unconverted. In a Sydney day bucket for
|
|
# 2026-08-01, first_alarm returns 2026-07-31T20:00:35 - the right
|
|
# instant, ten hours and one calendar day out, next to a bucket label
|
|
# that IS in site time.
|
|
#
|
|
# Left unfixed on purpose until imh is connected: the fix must keep the
|
|
# conversion inside Cube, and which fix is right depends on whether imh
|
|
# stores UTC or local time (Phase 4, task 4). Until then, do NOT quote
|
|
# either of these to an operator as a clock time. See Phase 4,
|
|
# "Deferred from Phase 5", finding (b) in BUILD-AI-CONTAINERS.md.
|
|
- name: first_alarm
|
|
sql: event_time
|
|
type: min
|
|
filters:
|
|
- sql: "{CUBE}.state = 'ACTIVE'"
|
|
|
|
- name: last_alarm
|
|
sql: event_time
|
|
type: max
|
|
filters:
|
|
- sql: "{CUBE}.state = 'ACTIVE'"
|
|
|
|
- name: priority_1_count
|
|
type: count
|
|
filters:
|
|
- sql: "{CUBE}.state = 'ACTIVE' AND {CUBE}.priority = 1"
|
|
description: Priority 1 activations - trips and spills.
|
|
|
|
pre_aggregations:
|
|
# Keeps "count alarms last week" fast without repeatedly scanning imh.
|
|
# Materialised into pg-ai schema cube_preagg. Watch its growth on
|
|
# /datadisk; the retention policy is the refresh_key plus manual pruning.
|
|
- name: alarms_by_hour
|
|
measures: [alarm_count, distinct_tags, priority_1_count]
|
|
dimensions: [alarm_type, equipment_id, tag_id]
|
|
time_dimension: event_time
|
|
granularity: hour
|
|
partition_granularity: month
|
|
refresh_key:
|
|
# 24h, not minutes: the fixtures are static, so a shorter
|
|
# interval rebuilds a byte-identical result on a 2-vCPU host
|
|
# and was a standing CPU load for no gain. Tune this back
|
|
# deliberately when imh makes the data genuinely live.
|
|
every: 24 hours
|
|
build_range_start:
|
|
sql: "SELECT now() - interval '180 days'"
|
|
build_range_end:
|
|
sql: "SELECT now()"
|
|
|
|
views:
|
|
- name: alarm_activity
|
|
description: >
|
|
Alarm activations joined to equipment, so a question about "Pump 02" can
|
|
be answered without the caller knowing which tags belong to it.
|
|
cubes:
|
|
- join_path: alarms
|
|
includes:
|
|
- event_time
|
|
- alarm_type
|
|
- tag_id
|
|
- state
|
|
- priority
|
|
- value
|
|
- is_fixture
|
|
- alarm_count
|
|
- distinct_tags
|
|
- priority_1_count
|
|
- join_path: alarms.equipment
|
|
prefix: true
|
|
includes:
|
|
- equipment_id
|
|
- display_name
|
|
- equipment_type
|