fix(15): correct SQL column names in trend detector and alert evaluator
- Replace `collected_at` with `time` (actual hypertable column) in 5 queries - Remove non-existent `rule_type` column from site_alert_events INSERTs - Fix trend dedup query to use `rule_id IS NULL` instead of `rule_type` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ v9.7 transforms TOD from a flat device list into a site-aware fleet management p
|
|||||||
- [x] **Phase 12: Per-Client Wireless Collection** - Poller extension to collect registration table and per-interface RF stats (completed 2026-03-19)
|
- [x] **Phase 12: Per-Client Wireless Collection** - Poller extension to collect registration table and per-interface RF stats (completed 2026-03-19)
|
||||||
- [x] **Phase 13: Link Discovery + Registration Ingestion** - Backend NATS consumer, MAC resolution, AP-CPE link state machine (completed 2026-03-19)
|
- [x] **Phase 13: Link Discovery + Registration Ingestion** - Backend NATS consumer, MAC resolution, AP-CPE link state machine (completed 2026-03-19)
|
||||||
- [x] **Phase 14: Site Dashboard + Sector Views + Wireless UI** - Site detail page, sector-centric view, per-station wireless tables (completed 2026-03-19)
|
- [x] **Phase 14: Site Dashboard + Sector Views + Wireless UI** - Site detail page, sector-centric view, per-station wireless tables (completed 2026-03-19)
|
||||||
- [ ] **Phase 15: Signal Trending + Site Alerting** - Signal history charts, degradation detection, site/sector alert rules
|
- [x] **Phase 15: Signal Trending + Site Alerting** - Signal history charts, degradation detection, site/sector alert rules (completed 2026-03-19)
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ Plans:
|
|||||||
2. System detects and surfaces signal degradation trends (e.g., "signal dropped 8dB over 2 weeks")
|
2. System detects and surfaces signal degradation trends (e.g., "signal dropped 8dB over 2 weeks")
|
||||||
3. Operator can create site-scoped alert rules (e.g., "alert when >20% of devices at this site go offline")
|
3. Operator can create site-scoped alert rules (e.g., "alert when >20% of devices at this site go offline")
|
||||||
4. Operator can create sector-scoped alert rules (e.g., "alert when sector average signal drops below -75dBm")
|
4. Operator can create sector-scoped alert rules (e.g., "alert when sector average signal drops below -75dBm")
|
||||||
**Plans:** 2/3 plans executed
|
**Plans:** 3/3 plans complete
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 15-01-PLAN.md — Backend data model, services, and REST API for site alert rules, alert events, and signal history
|
- [ ] 15-01-PLAN.md — Backend data model, services, and REST API for site alert rules, alert events, and signal history
|
||||||
@@ -131,7 +131,7 @@ Plans:
|
|||||||
| Sites | SITE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06 | 11 | 3/3 | Complete | 2026-03-19 | DASH-01 | 11 | 1 |
|
| Sites | SITE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06 | 11 | 3/3 | Complete | 2026-03-19 | DASH-01 | 11 | 1 |
|
||||||
| Site Dashboard | DASH-02, DASH-03, DASH-04 | 14 | 3/3 | Complete | 2026-03-19 | SECT-01, SECT-02, SECT-03 | 14 | 3 |
|
| Site Dashboard | DASH-02, DASH-03, DASH-04 | 14 | 3/3 | Complete | 2026-03-19 | SECT-01, SECT-02, SECT-03 | 14 | 3 |
|
||||||
| Wireless Collection | WRCL-01, WRCL-02, WRCL-03, WRCL-04, WRCL-05, WRCL-06 | 12 | 2/2 | Complete | 2026-03-19 | LINK-01, LINK-02, LINK-03, LINK-04 | 13 | 3/3 | Complete | 2026-03-19 | WRUI-01, WRUI-02, WRUI-03 | 14 | 3 |
|
| Wireless Collection | WRCL-01, WRCL-02, WRCL-03, WRCL-04, WRCL-05, WRCL-06 | 12 | 2/2 | Complete | 2026-03-19 | LINK-01, LINK-02, LINK-03, LINK-04 | 13 | 3/3 | Complete | 2026-03-19 | WRUI-01, WRUI-02, WRUI-03 | 14 | 3 |
|
||||||
| Signal Trending | TRND-01, TRND-02 | 15 | 2/3 | In Progress| | ALRT-01, ALRT-02 | 15 | 2 |
|
| Signal Trending | TRND-01, TRND-02 | 15 | 3/3 | Complete | 2026-03-19 | ALRT-01, ALRT-02 | 15 | 2 |
|
||||||
| **Total** | | | **30** |
|
| **Total** | | | **30** |
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ gsd_state_version: 1.0
|
|||||||
milestone: v9.7
|
milestone: v9.7
|
||||||
milestone_name: Tower & Site Management
|
milestone_name: Tower & Site Management
|
||||||
status: unknown
|
status: unknown
|
||||||
stopped_at: Completed 15-01-PLAN.md
|
stopped_at: Completed 15-03-PLAN.md
|
||||||
last_updated: "2026-03-19T12:19:31.850Z"
|
last_updated: "2026-03-19T12:26:34.674Z"
|
||||||
progress:
|
progress:
|
||||||
total_phases: 5
|
total_phases: 5
|
||||||
completed_phases: 4
|
completed_phases: 5
|
||||||
total_plans: 14
|
total_plans: 14
|
||||||
completed_plans: 13
|
completed_plans: 14
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -48,6 +48,7 @@ Plan: 2 of 3
|
|||||||
| Phase 14 P03 | 3min | 2 tasks | 6 files |
|
| Phase 14 P03 | 3min | 2 tasks | 6 files |
|
||||||
| Phase 15 P02 | 3min | 2 tasks | 4 files |
|
| Phase 15 P02 | 3min | 2 tasks | 4 files |
|
||||||
| Phase 15 P01 | 4min | 2 tasks | 10 files |
|
| Phase 15 P01 | 4min | 2 tasks | 10 files |
|
||||||
|
| Phase 15 P03 | 5min | 3 tasks | 9 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -91,6 +92,9 @@ Decisions are logged in PROJECT.md Key Decisions table.
|
|||||||
- [Phase 15]: Site alert tables are separate from device-level alert_rules/alert_events (no coupling between systems)
|
- [Phase 15]: Site alert tables are separate from device-level alert_rules/alert_events (no coupling between systems)
|
||||||
- [Phase 15]: Signal history uses TimescaleDB time_bucket with 3 range presets (5min/1h/4h buckets)
|
- [Phase 15]: Signal history uses TimescaleDB time_bucket with 3 range presets (5min/1h/4h buckets)
|
||||||
- [Phase 15]: Alert event count endpoint returns simple JSON for notification bell badge
|
- [Phase 15]: Alert event count endpoint returns simple JSON for notification bell badge
|
||||||
|
- [Phase 15]: NotificationBell placed in ContextStrip for consistent header integration
|
||||||
|
- [Phase 15]: Expandable chart rows use React.Fragment pattern with per-component state
|
||||||
|
- [Phase 15]: Alert rule type selector is context-aware (sector types when sectorId provided, site types otherwise)
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -104,6 +108,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-19T12:19:31.847Z
|
Last session: 2026-03-19T12:26:34.671Z
|
||||||
Stopped at: Completed 15-01-PLAN.md
|
Stopped at: Completed 15-03-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
|
|||||||
FROM wireless_registrations wr
|
FROM wireless_registrations wr
|
||||||
JOIN devices d ON d.id = wr.device_id
|
JOIN devices d ON d.id = wr.device_id
|
||||||
WHERE d.sector_id = :sector_id
|
WHERE d.sector_id = :sector_id
|
||||||
AND wr.collected_at > now() - interval '10 minutes'
|
AND wr.time > now() - interval '10 minutes'
|
||||||
"""),
|
"""),
|
||||||
{"sector_id": sector_id},
|
{"sector_id": sector_id},
|
||||||
)
|
)
|
||||||
@@ -93,7 +93,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
|
|||||||
FROM wireless_registrations wr
|
FROM wireless_registrations wr
|
||||||
JOIN devices d ON d.id = wr.device_id
|
JOIN devices d ON d.id = wr.device_id
|
||||||
WHERE d.sector_id = :sector_id
|
WHERE d.sector_id = :sector_id
|
||||||
AND wr.collected_at > now() - interval '10 minutes'
|
AND wr.time > now() - interval '10 minutes'
|
||||||
"""),
|
"""),
|
||||||
{"sector_id": sector_id},
|
{"sector_id": sector_id},
|
||||||
)
|
)
|
||||||
@@ -106,7 +106,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
|
|||||||
FROM wireless_registrations wr
|
FROM wireless_registrations wr
|
||||||
JOIN devices d ON d.id = wr.device_id
|
JOIN devices d ON d.id = wr.device_id
|
||||||
WHERE d.sector_id = :sector_id
|
WHERE d.sector_id = :sector_id
|
||||||
AND wr.collected_at BETWEEN now() - interval '70 minutes'
|
AND wr.time BETWEEN now() - interval '70 minutes'
|
||||||
AND now() - interval '60 minutes'
|
AND now() - interval '60 minutes'
|
||||||
"""),
|
"""),
|
||||||
{"sector_id": sector_id},
|
{"sector_id": sector_id},
|
||||||
@@ -178,10 +178,10 @@ async def _evaluate_rules() -> None:
|
|||||||
await session.execute(
|
await session.execute(
|
||||||
text("""
|
text("""
|
||||||
INSERT INTO site_alert_events
|
INSERT INTO site_alert_events
|
||||||
(tenant_id, site_id, sector_id, rule_id, rule_type,
|
(tenant_id, site_id, sector_id, rule_id,
|
||||||
severity, message, state, consecutive_hits, triggered_at)
|
severity, message, state, consecutive_hits, triggered_at)
|
||||||
VALUES
|
VALUES
|
||||||
(:tenant_id, :site_id, :sector_id, :rule_id, :rule_type,
|
(:tenant_id, :site_id, :sector_id, :rule_id,
|
||||||
:severity, :message, 'active', 1, now())
|
:severity, :message, 'active', 1, now())
|
||||||
"""),
|
"""),
|
||||||
{
|
{
|
||||||
@@ -189,7 +189,6 @@ async def _evaluate_rules() -> None:
|
|||||||
"site_id": str(rule.site_id),
|
"site_id": str(rule.site_id),
|
||||||
"sector_id": str(rule.sector_id) if rule.sector_id else None,
|
"sector_id": str(rule.sector_id) if rule.sector_id else None,
|
||||||
"rule_id": rule_id,
|
"rule_id": rule_id,
|
||||||
"rule_type": rule.rule_type,
|
|
||||||
"severity": severity,
|
"severity": severity,
|
||||||
"message": f"Alert rule '{rule.name}' condition met",
|
"message": f"Alert rule '{rule.name}' condition met",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ async def _detect_trends() -> None:
|
|||||||
FROM wireless_registrations
|
FROM wireless_registrations
|
||||||
WHERE mac_address = :mac
|
WHERE mac_address = :mac
|
||||||
AND device_id = :ap_device_id
|
AND device_id = :ap_device_id
|
||||||
AND collected_at > now() - interval '7 days'
|
AND time > now() - interval '7 days'
|
||||||
"""),
|
"""),
|
||||||
{"mac": mac, "ap_device_id": str(ap_device_id)},
|
{"mac": mac, "ap_device_id": str(ap_device_id)},
|
||||||
)
|
)
|
||||||
@@ -67,7 +67,7 @@ async def _detect_trends() -> None:
|
|||||||
FROM wireless_registrations
|
FROM wireless_registrations
|
||||||
WHERE mac_address = :mac
|
WHERE mac_address = :mac
|
||||||
AND device_id = :ap_device_id
|
AND device_id = :ap_device_id
|
||||||
AND collected_at > now() - interval '14 days'
|
AND time > now() - interval '14 days'
|
||||||
"""),
|
"""),
|
||||||
{"mac": mac, "ap_device_id": str(ap_device_id)},
|
{"mac": mac, "ap_device_id": str(ap_device_id)},
|
||||||
)
|
)
|
||||||
@@ -88,7 +88,7 @@ async def _detect_trends() -> None:
|
|||||||
text("""
|
text("""
|
||||||
SELECT id FROM site_alert_events
|
SELECT id FROM site_alert_events
|
||||||
WHERE link_id = :link_id
|
WHERE link_id = :link_id
|
||||||
AND rule_type = 'signal_degradation'
|
AND rule_id IS NULL
|
||||||
AND state = 'active'
|
AND state = 'active'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""),
|
"""),
|
||||||
@@ -105,10 +105,10 @@ async def _detect_trends() -> None:
|
|||||||
await session.execute(
|
await session.execute(
|
||||||
text("""
|
text("""
|
||||||
INSERT INTO site_alert_events
|
INSERT INTO site_alert_events
|
||||||
(tenant_id, site_id, link_id, rule_type, severity, message, state,
|
(tenant_id, site_id, link_id, severity, message, state,
|
||||||
consecutive_hits, triggered_at)
|
consecutive_hits, triggered_at)
|
||||||
VALUES
|
VALUES
|
||||||
(:tenant_id, :site_id, :link_id, 'signal_degradation', 'warning',
|
(:tenant_id, :site_id, :link_id, 'warning',
|
||||||
:message, 'active', 1, now())
|
:message, 'active', 1, now())
|
||||||
"""),
|
"""),
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user