45 of the 65 rows in db/seed/tags.csv had a PS_* name as their PRIMARY KEY, and historian_items.csv, alarm_bits.csv, the fixtures, two Cube models and the exam all referenced them. They are gone. The tag seed is now keyed on the CI Server item for everything the historian carries - AID.WRPS.STN.LEVEL - and on the instrument tag for the 20 field devices that never reach SCADA. gen_historian_items.py reads db/seed/scada-source/ by default, so it runs for anyone with a clone: --wrps is now --source. The tag match is the item name itself, an identity lookup, and the TAG_FOR_ITEM special case is deleted - all 49 item names are unique, which the old point names were not. scada_point in the output is replaced by ci_station, ci_point and poll_group; 001_schema.sql and deploy.sh's upsert follow. Regenerated, and it comes out the same shape it went in: 49 items, 45 answerable, 4 deliberately excluded, three groups at 5 s, 30 s and on change. Every historian_items.tag_id and alarm_bits.tag_id resolves to a tags.csv row. No duplicate keys. THE ONE NAME THAT WAS AMBIGUOUS, AND NEARLY COST US PS_STN_HIGH_LEVEL_ALARM named two different things in the old delivery: the high level alarm STATUS BIT on coil 10, and the alarm SETPOINT on holding register 1032. Building the rename map from that file kept whichever came last, so the status bit was silently renamed onto the setpoint. check_mapping() refused to write and named the item that no longer resolved - which is the only reason this is a paragraph in a commit message rather than a defect. Had it gone through, alarm bit 0 - wet well high level - would have pointed at the setpoint. "How many high level alarms last week" would have counted setpoint changes and returned a small, plausible, confident, wrong number. That check exists because the same ambiguity caused the first Phase 5 finding in August. It has now bitten twice. NOT YET VERIFIED: pytest api/tests could not be run here - this machine has neither fastapi nor psycopg. The tests are unchanged and reference no PS_ name, but they have not been run. pg-ai on lin001 still holds the old keys and must be reloaded, Cube pre-aggregations rebuilt, and the Phase 1 gate re-run. eval/testset.jsonl changed, so the 78-case exam - never yet run - should be run after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
306 lines
9.4 KiB
YAML
306 lines
9.4 KiB
YAML
# =============================================================================
|
|
# equipment.yml — alias resolution at equipment and tag level.
|
|
#
|
|
# SOURCE: pg-ai public.equipment and public.tags. These live in pg-ai whatever
|
|
# happens to imh, so this file does not change at Phase 4.
|
|
#
|
|
# WHY IT IS IN CUBE AT ALL: so that a question about "Pump 02" can be answered
|
|
# without the caller knowing that its data is on AID.WRPS.PU302.RUNNING,
|
|
# AID.WRPS.PU302.TRIPPED and AID.WRPS.PU302.RUN_HOURS. Without equipment-level joins every
|
|
# equipment question fails, because no historian point is called "Pump 02".
|
|
#
|
|
# Alias matching itself is done in the API (api/tools/equipment.py) against the
|
|
# GIN-indexed alias arrays, not here - Cube is the aggregation layer, not the
|
|
# entity resolver. What Cube provides is the join, so that once the API has
|
|
# resolved "Pump 02" to PU-302 the measures in the other cubes can be filtered
|
|
# by equipment rather than by a list of tags the model would have to hardcode.
|
|
# =============================================================================
|
|
|
|
cubes:
|
|
- name: equipment
|
|
sql: >
|
|
SELECT
|
|
equipment_id,
|
|
display_name,
|
|
equipment_type,
|
|
unit_name,
|
|
description,
|
|
array_to_string(aliases, ' | ') AS alias_list
|
|
FROM public.equipment
|
|
description: >
|
|
Plant equipment at the Waterloo Road Pump Station - the station itself,
|
|
the wet well, three pumps, the discharge manifold, the spill weir and the
|
|
switchboard.
|
|
|
|
dimensions:
|
|
- name: equipment_id
|
|
sql: equipment_id
|
|
type: string
|
|
primary_key: true
|
|
description: Canonical identifier - PU-302, WW-101, STN-001.
|
|
|
|
- name: display_name
|
|
sql: display_name
|
|
type: string
|
|
description: What to call it in an answer - "Pump 02", not "PU-302".
|
|
|
|
- name: equipment_type
|
|
sql: equipment_type
|
|
type: string
|
|
|
|
- name: unit_name
|
|
sql: unit_name
|
|
type: string
|
|
|
|
- name: alias_list
|
|
sql: alias_list
|
|
type: string
|
|
description: >
|
|
Pipe-separated aliases, for display and debugging. Resolution happens
|
|
in the API against the array column with a GIN index; this string is
|
|
for showing an operator why "pump2" was understood as PU-302.
|
|
|
|
- name: description
|
|
sql: description
|
|
type: string
|
|
|
|
measures:
|
|
- name: count
|
|
type: count
|
|
|
|
- name: tags
|
|
sql_table: public.tags
|
|
description: >
|
|
Historian points and field instruments, their units, ranges and
|
|
setpoints. Read the description column before interpreting any value -
|
|
it records the historian-versus-PLC unit conversion and, critically,
|
|
whether the tag is historised at all.
|
|
|
|
joins:
|
|
- name: equipment
|
|
sql: "{CUBE}.equipment_id = {equipment}.equipment_id"
|
|
relationship: many_to_one
|
|
|
|
dimensions:
|
|
- name: tag_id
|
|
sql: tag_id
|
|
type: string
|
|
primary_key: true
|
|
|
|
- name: equipment_id
|
|
sql: equipment_id
|
|
type: string
|
|
|
|
- name: display_name
|
|
sql: display_name
|
|
type: string
|
|
|
|
- name: signal_type
|
|
sql: signal_type
|
|
type: string
|
|
description: level, flow, pressure, vibration, status, state, speed, hours.
|
|
|
|
- name: engineering_unit
|
|
sql: engineering_unit
|
|
type: string
|
|
description: >
|
|
The unit the HISTORIAN stores, which is not always the unit the PLC
|
|
works in. Wet well level is historised as percent of the spill weir
|
|
crest (raw mm / 60). Always report the unit with the number.
|
|
|
|
- name: range_low
|
|
sql: range_low
|
|
type: number
|
|
|
|
- name: range_high
|
|
sql: range_high
|
|
type: number
|
|
|
|
- name: alarm_setpoint_hi
|
|
sql: alarm_setpoint_hi
|
|
type: number
|
|
description: >
|
|
The configured alarm setpoint at the time this reference data was
|
|
loaded. Setpoints are writable from SCADA - if a question compares
|
|
two periods, check AID.WRPS.SP.HIGH_ALARM for a change before
|
|
attributing a difference in alarm counts to the process.
|
|
|
|
- name: alarm_setpoint_lo
|
|
sql: alarm_setpoint_lo
|
|
type: number
|
|
|
|
- name: trip_setpoint
|
|
sql: trip_setpoint
|
|
type: number
|
|
|
|
- name: description
|
|
sql: description
|
|
type: string
|
|
|
|
measures:
|
|
- name: count
|
|
type: count
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# historian_items — the CI Server item dictionary.
|
|
#
|
|
# WHY IT IS A CUBE AT ALL: so "what is AID.WRPS.STN.LEVEL" and "which item
|
|
# holds the wet well level" are both answerable, and so an engineer checking
|
|
# a number can walk from the answer back through item -> point -> PLC address
|
|
# without leaving the assistant. Four namespaces name the same measurement
|
|
# and this table is the only place all four appear together.
|
|
#
|
|
# It is also where an item's SAMPLE RATE and RETENTION come from, both taken
|
|
# from the live CI Server historisation groups rather than assumed.
|
|
# ---------------------------------------------------------------------------
|
|
- name: historian_items
|
|
sql_table: public.historian_items
|
|
description: >
|
|
CI Server items - what the historian is actually keyed on - and how each
|
|
maps to a tag, a SCADA point and a PLC register.
|
|
|
|
joins:
|
|
- name: tags
|
|
sql: "{CUBE}.tag_id = {tags}.tag_id"
|
|
relationship: many_to_one
|
|
|
|
dimensions:
|
|
- name: item_name
|
|
sql: item_name
|
|
type: string
|
|
primary_key: true
|
|
description: AID.WRPS.STN.LEVEL. The historian's own key.
|
|
|
|
- name: tag_id
|
|
sql: tag_id
|
|
type: string
|
|
description: >
|
|
NULL only for items deliberately excluded from answering - see
|
|
exclusion_reason. A NULL here with no reason is a build failure.
|
|
|
|
- name: exclusion_reason
|
|
sql: exclusion_reason
|
|
type: string
|
|
description: >
|
|
Why this item cannot be asked about. Present it verbatim rather than
|
|
reporting "no records found" - the two mean very different things.
|
|
|
|
- name: section
|
|
sql: section
|
|
type: string
|
|
description: >
|
|
CI Server's own grouping - STN, PU301, PU302, PU303, SP, SIM. It is
|
|
COARSER than plant equipment: there is no wet well, weir, manifold or
|
|
switchboard section. Use equipment for the plant view, not this.
|
|
|
|
- name: attribute
|
|
sql: attribute
|
|
type: string
|
|
|
|
- name: description
|
|
sql: description
|
|
type: string
|
|
|
|
- name: eng_unit
|
|
sql: eng_unit
|
|
type: string
|
|
description: The unit CI Server presents, which is not the PLC's unit.
|
|
|
|
- name: raw_to_eng
|
|
sql: raw_to_eng
|
|
type: string
|
|
description: >
|
|
How the raw register becomes the engineering value, as written in the
|
|
SCADA point list - "value / 60" for level. Quote this when explaining
|
|
why a level reads 70.3 and not 4217.
|
|
|
|
- name: his_group
|
|
sql: his_group
|
|
type: string
|
|
description: WRPS_ONE_SEC (5 s), WRPS_THIRTY_SEC (30 s), WRPS_EVENT (on change).
|
|
|
|
- name: scan_interval_seconds
|
|
sql: scan_interval_seconds
|
|
type: number
|
|
|
|
- name: life_time
|
|
sql: life_time
|
|
type: string
|
|
description: >
|
|
Retention on the item's history group - "1 weeks" on every WRPS
|
|
group. This is the answer to "why can it not tell me about last
|
|
month".
|
|
|
|
- name: ci_point
|
|
sql: ci_point
|
|
type: string
|
|
|
|
- name: iec_address
|
|
sql: iec_address
|
|
type: string
|
|
description: The PLC address, %QW0. Field inputs (%IW/%IX) are not historised.
|
|
|
|
measures:
|
|
- name: count
|
|
type: count
|
|
|
|
views:
|
|
- name: item_reference
|
|
description: >
|
|
The CI Server item dictionary joined to the tag and equipment each item
|
|
belongs to. This is the lookup behind "which item holds the wet well
|
|
level", "how often is it sampled" and "how far back does it go".
|
|
cubes:
|
|
- join_path: historian_items
|
|
includes:
|
|
- item_name
|
|
- tag_id
|
|
- exclusion_reason
|
|
- section
|
|
- attribute
|
|
- description
|
|
- eng_unit
|
|
- raw_to_eng
|
|
- his_group
|
|
- scan_interval_seconds
|
|
- life_time
|
|
- ci_point
|
|
- iec_address
|
|
- count
|
|
- join_path: historian_items.tags
|
|
prefix: true
|
|
includes:
|
|
- display_name
|
|
- signal_type
|
|
- engineering_unit
|
|
- join_path: historian_items.tags.equipment
|
|
prefix: true
|
|
includes:
|
|
- equipment_id
|
|
- display_name
|
|
- equipment_type
|
|
|
|
- name: equipment_reference
|
|
description: >
|
|
Equipment joined to its tags - the lookup behind "what does the PVHI
|
|
alarm on the wet well mean" and "which pump is PU-302".
|
|
cubes:
|
|
- join_path: tags
|
|
includes:
|
|
- tag_id
|
|
- display_name
|
|
- signal_type
|
|
- engineering_unit
|
|
- range_low
|
|
- range_high
|
|
- alarm_setpoint_hi
|
|
- alarm_setpoint_lo
|
|
- trip_setpoint
|
|
- description
|
|
- join_path: tags.equipment
|
|
prefix: true
|
|
includes:
|
|
- equipment_id
|
|
- display_name
|
|
- equipment_type
|
|
- alias_list
|