mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-08 23:33:16 +08:00
Polish ops skills from field Q&A and tighten fiber_cut vs optical-power presets.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4660cc7507
commit
2f1d3983b8
5 changed files with 143 additions and 24 deletions
|
|
@ -24,9 +24,32 @@ _LIST_FIELDS = [
|
|||
]
|
||||
|
||||
# Server keyword is single-substring; presets filter client-side after a wider pull.
|
||||
# fiber_cut: do NOT use bare "optical" — that pulls optical-power threshold (field CSV noise).
|
||||
# Default server keyword (when caller omits keyword) biases the page toward real cut/LOS rows.
|
||||
_PRESET_CLIENT_TERMS: dict[str, tuple[str, ...]] = {
|
||||
"fiber_cut": ("los", "fiber", "断纤", "光缆", "光路", "optical"),
|
||||
"offline": ("离线", "offline", "通信中断", "单板离线", "ne communication"),
|
||||
"fiber_cut": (
|
||||
"los",
|
||||
"fiber break",
|
||||
"fiber",
|
||||
"断纤",
|
||||
"光缆",
|
||||
"光路",
|
||||
"missing laser",
|
||||
"optical module is faulty",
|
||||
),
|
||||
"offline": (
|
||||
"离线",
|
||||
"offline",
|
||||
"通信中断",
|
||||
"单板离线",
|
||||
"ne communication",
|
||||
"bn ems",
|
||||
"communication failure",
|
||||
),
|
||||
}
|
||||
_PRESET_DEFAULT_KEYWORD: dict[str, str] = {
|
||||
"fiber_cut": "LOS",
|
||||
"offline": "BN EMS",
|
||||
}
|
||||
|
||||
_MODE_DEFAULT_NAMES: dict[str, str] = {
|
||||
|
|
@ -196,9 +219,14 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
time_from = str(args.get("time_from") or "").strip()
|
||||
time_to = str(args.get("time_to") or "").strip()
|
||||
page_size = max(1, min(500, int(args.get("page_size") or args.get("limit") or 100)))
|
||||
# Preset modes: bias API keyword so the page is not drowned by PW/BGP noise (~80k rows).
|
||||
preset_kw_applied = False
|
||||
if mode in _PRESET_DEFAULT_KEYWORD and not keyword:
|
||||
keyword = _PRESET_DEFAULT_KEYWORD[mode]
|
||||
preset_kw_applied = True
|
||||
# Preset modes pull a wider page then filter client-side (API keyword is single substring).
|
||||
fetch_size = page_size
|
||||
if mode in _PRESET_CLIENT_TERMS and not keyword:
|
||||
if mode in _PRESET_CLIENT_TERMS:
|
||||
fetch_size = max(page_size, min(500, page_size * 3))
|
||||
deliverable = _truthy(args.get("deliverable"), default=True)
|
||||
filename = str(args.get("name") or args.get("filename") or "").strip() or _MODE_DEFAULT_NAMES[mode]
|
||||
|
|
@ -206,7 +234,12 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
filename = f"{filename}.xlsx"
|
||||
|
||||
sheet_name = "alarms"
|
||||
summary_meta: dict[str, Any] = {"mode": mode, "keyword": keyword or None, "severity": severity or None}
|
||||
summary_meta: dict[str, Any] = {
|
||||
"mode": mode,
|
||||
"keyword": keyword or None,
|
||||
"severity": severity or None,
|
||||
"preset_default_keyword": preset_kw_applied,
|
||||
}
|
||||
|
||||
if mode == "aggregate_by_host":
|
||||
upstream = _fetch_aggregate_by_host(
|
||||
|
|
@ -235,7 +268,7 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
return {"ok": False, "error": "ume_query_failed", "upstream": upstream}
|
||||
data = upstream.get("data") if isinstance(upstream.get("data"), dict) else {}
|
||||
items = data.get("items") if isinstance(data.get("items"), list) else []
|
||||
if mode in _PRESET_CLIENT_TERMS and not keyword:
|
||||
if mode in _PRESET_CLIENT_TERMS:
|
||||
items = _filter_preset_items(mode, items)[:page_size]
|
||||
headers, rows = _rows_from_list_items(items)
|
||||
summary_meta["total"] = data.get("total")
|
||||
|
|
@ -278,6 +311,8 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
description=(
|
||||
"One-shot UME alarm Excel for WhatsApp ops: query netx alarms and build .xlsx "
|
||||
"(optionally mark deliverable). Modes: list, aggregate_by_host, fiber_cut, offline. "
|
||||
"fiber_cut defaults keyword=LOS (ETPI LOS / Fiber Break family; not optical-power threshold). "
|
||||
"offline defaults keyword=BN EMS. Pass keyword=Fiber Break for explicit fiber-break rows. "
|
||||
"Prefer this over query+write_xlsx+save_deliverable_attachment for short WA requests."
|
||||
),
|
||||
parameters={
|
||||
|
|
@ -289,7 +324,8 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
"default": "list",
|
||||
"description": (
|
||||
"list=raw alarm rows; aggregate_by_host=count by host_name; "
|
||||
"fiber_cut/offline=preset keyword filters for common WA intents."
|
||||
"fiber_cut defaults keyword=LOS (not optical-power threshold); "
|
||||
"offline defaults keyword=BN EMS."
|
||||
),
|
||||
},
|
||||
"severity": {
|
||||
|
|
@ -298,7 +334,10 @@ def ume_alarm_xlsx_report_tool() -> ToolSpec:
|
|||
},
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"description": "Optional keyword; fiber_cut/offline apply defaults when omitted.",
|
||||
"description": (
|
||||
"Optional keyword. If omitted: fiber_cut→LOS, offline→BN EMS. "
|
||||
"Pass Fiber Break for explicit fiber-break rows."
|
||||
),
|
||||
},
|
||||
"time_from": {"type": "string", "description": "ISO time; filters last_seen_at >="},
|
||||
"time_to": {"type": "string", "description": "ISO time; filters last_seen_at <="},
|
||||
|
|
|
|||
|
|
@ -35,12 +35,17 @@ description: 面向 ops 专家的 netx 纳管网元(网元管理)作业手
|
|||
|
||||
### Capacity / optical power between two names
|
||||
|
||||
When user says **capacity**, **bandwidth between A and B**, or **optical power A <> B** (prod vocabulary):
|
||||
When user says **capacity**, **bandwidth between A and B**, **optical power A <> B**, or site pairs (`SEMBAWA <> ANGKATAN_EP`, `SMD-PSB <> SMD-PNTE`):
|
||||
|
||||
1. Resolve nicknames → real `host_name` via inventory/wiki (`SEMBAWA` → e.g. `PLG-SMW-EN1-…`).
|
||||
2. Find interconnect: `findTopologyPaths` and/or LLDP (`show lldp …` / vendor equivalent) — identify **both ports**.
|
||||
3. Read optics on **both** ends with the correct vendor command (below). Summarize: interface, RX/TX power, threshold, whether link is up.
|
||||
4. Do **not** answer with only UME bandwidth-usage-rate alarms unless the user asked for congestion alarms.
|
||||
4. Do **not** answer with only UME bandwidth-usage-rate **or** optical-power-threshold alarm tallies unless the user asked for those alarm lists.
|
||||
5. CRC + optical on a link: same path — resolve ports once, then optic CLI (+ CRC counters if allowlisted) on **both** ends in **one** batch when possible (`targets` if vendors differ).
|
||||
|
||||
### Area optical-power **alarm** list (UME only)
|
||||
|
||||
`optical power threshold crossed` under area BPP/PBR/PAL/… → UME keyword=`optical power` + hostname prefix — **not** this CLI recipe and **not** fiber_cut.
|
||||
|
||||
### ZTE optical CLI (prod corrections)
|
||||
|
||||
|
|
|
|||
|
|
@ -59,15 +59,20 @@ Prefer these fixed paths for short group/DM asks (EN first; ZH aliases still wor
|
|||
|
||||
| User says (examples) | Recipe |
|
||||
|----------------------|--------|
|
||||
| fiber cut / LOS / cable cut / 断纤 | Prefer `ume_alarm_xlsx_report(mode=fiber_cut)` (xlsx + deliver); or summarize via `queryUmeAlarmsRaw` |
|
||||
| offline NE / board offline / 离线 | Prefer `ume_alarm_xlsx_report(mode=offline)` |
|
||||
| fiber cut / LOS / cable cut / 断纤 / sitelist | Prefer `ume_alarm_xlsx_report(mode=fiber_cut)` (defaults keyword=`LOS` → ETPI LOS; not optical-power threshold). For explicit **Fiber Break** rows also/instead `keyword=Fiber Break` or `queryUmeAlarmsRaw(keyword=Fiber Break)`. Reply with **host_name list** + counts |
|
||||
| offline NE / board offline / unmanaged / 离线 | Prefer `ume_alarm_xlsx_report(mode=offline)` (defaults `BN EMS` / NE communication failure). Unmanaged ME list → same family + clarify BN EMS / unreachable |
|
||||
| Critical Top / alarm tally | ① `aggregateUmeAlarms(severity=critical, top_ne=20)`; for file: `ume_alarm_xlsx_report(mode=aggregate_by_host, severity=critical)` |
|
||||
| how many alarms / tally | ① `runUmeDiagnostics` or `aggregateUmeAlarms`; ② report by_severity + freshness |
|
||||
| how many alarms / tally / 现网告警数量 | ① `runUmeDiagnostics` or `aggregateUmeAlarms`; ② report by_severity + freshness |
|
||||
| export Excel / send spreadsheet | `ume_alarm_xlsx_report` **or** `write_xlsx(..., deliverable=true)`; never split into 3 steps |
|
||||
| CRC in area PAD / ACH / … | `queryUmeAlarmsRaw(keyword=CRC)` then keep rows whose `alarm_host_name` / `ne_host_name` starts with area prefix (`PAD-`, `ACH-`, …). Optional xlsx via `write_xlsx(deliverable=true)` |
|
||||
| bandwidth / congestion / usage rate (+ area) | keyword=`bandwidth` (do **not** require event_type unless user asks); filter hostname prefix for area; if CLI confirm false positives: top 3–5 NEs in **one** batch — same show → `ume_ne_ids=[…]`+`commands`; mixed vendors → `targets=[{ume_ne_id, commands},…]` — never one-NE loops |
|
||||
| BN EMS / dying gasp / unmanaged (+ area) | keyword or native cause match (`BN EMS` / `dying gasp`); filter area prefix; short EN summary + optional xlsx |
|
||||
| power / temperature / fan alarms (+ area/NE) | keyword=`power` / `temperature` / `fan`; scope to host or area prefix |
|
||||
| bandwidth / congestion / usage rate (+ area) | keyword=`bandwidth` (matches *Send/Receive bandwidth usage rate threshold crossed*; do **not** require event_type). Filter hostname prefix for area; if CLI confirm: top 3–5 NEs in **one** batch |
|
||||
| optical power **threshold** in area (BPP/PBR/PAL/…) | keyword=`optical power` (or `Input optical power`) + keep `AREA-` hosts. **Not** fiber_cut mode. Distinct from capacity A<>B CLI |
|
||||
| BN EMS / dying gasp / unmanaged (+ area) | See **Dying gasp / BN EMS correlation** below — do not stop at one NE |
|
||||
| power / temperature / fan / undervoltage / System Power off | keyword=`power` / `temperature` / `fan` / `undervoltage` / `Power off`; scope to host or area prefix. Optical *power(dBm)* ≠ board voltage |
|
||||
| license | keyword=`License` (causes: *Permanent license abnormal*, *No enough license resource*) |
|
||||
| BGP / OSPF / ISIS / LDP / PW / Tunnel on host | `queryUmeAlarmsRaw(host or keyword=BGP\|OSPF\|LDP\|…)` on named host(s); for peer correlation see below |
|
||||
| Port down / ETPI / which segment cut? | keyword=`Port down` or `LOS` on the named host; use `object_name` + `findTopologyPaths` / LLDP to name the far end |
|
||||
| alarm code NNNN | `queryUmeAlarmsRaw` / diagnostics `top_alarm_codes`; keyword or raw filter on code; return **host_name** list |
|
||||
| alarm on **one hostname** (e.g. `MDN-PLSP`, `MKS-SWBP-EN1`) | `queryUmeAlarms` / `queryUmeAlarmsRaw` with `host_name` / keyword=hostname. **Never** start a scheduled License/daily playbook |
|
||||
| alarm history / time range (e.g. `17.50-18.15`) | Resolve **WIB (UTC+7)** wall clock → `time_from`/`time_to` on `last_seen_at` / history fields; first check freshness; name hosts exactly (`MKS-KIM-CN1`) |
|
||||
| is NE rebooted? / alarm history for NE | Host-scoped alarm history (reboot/reload/power related causes); answer yes/no + evidence times |
|
||||
|
|
@ -80,11 +85,49 @@ Delivery rules:
|
|||
|
||||
### Field vocabulary (prod-learned; enforce)
|
||||
|
||||
- **Area** = hostname **prefix** before first extra segment: `BTM-`, `ACH-`, `MKS-`, `PAD-`, `MDN-`, `KND-`, `SMD-`, `MDO-`, `PLG-`, … Case-insensitive starts-with.
|
||||
- **Capacity / bandwidth between A and B** (user correction in field): means **SFP/optical link capacity between two hostnames**, not UME bandwidth-usage-rate alarms alone. Resolve both NEs → interconnect ports (`findTopologyPaths` / LLDP) → optical/SFP CLI. See `ops-netx-managed-ne-playbook`.
|
||||
- **Site nicknames** (SEMBAWA, ANGKATAN_EP, …): resolve via inventory/wiki/`queryUmeNeInventory(keyword=…)` **before** CLI; never invent hostnames.
|
||||
- **Area** = hostname **prefix** before first `-`: `MDN-`, `LPG-`, `MKS-`, `PLG-`, `BJM-`, `PTK-`, `ACH-`, `PBR-`, `MDO-`, `SMD-`, `PAD-`, `BTM-`, `PLK-`, `BPP-`, `BKL-`, `JBI-`, `KND-`, `PAL-`, `GRO-`, `JAP-`, … Case-insensitive starts-with.
|
||||
- **Capacity / bandwidth between A and B** / `A <> B` / site nicknames (SEMBAWA, ANGKATAN_EP): means **SFP/optical link** on the interconnect — **not** UME *bandwidth usage rate* alarms alone. Resolve both NEs → ports (`findTopologyPaths` / LLDP) → optic CLI. See `ops-netx-managed-ne-playbook`.
|
||||
- **Optical power threshold crossed** (area list): UME cause *Input/Output optical power(dBm) threshold crossed* — keyword=`optical power`; **not** `mode=fiber_cut`.
|
||||
- **Fiber cut / LOS sitelist**: causes *Ethernet physical (ETPI) LOS*, *Fiber Break*, *Missing laser module* — report `mode=fiber_cut` (LOS-biased) and/or `keyword=Fiber Break`.
|
||||
- **Site nicknames**: resolve via inventory/wiki/`queryUmeNeInventory(keyword=…)` **before** CLI; never invent hostnames.
|
||||
- **Local clock phrases** (`17.50`, `today`, `yesterday`): treat as **Asia/Jakarta (WIB, UTC+7)** unless user says otherwise.
|
||||
|
||||
### Field cause cheat-sheet (2026-08 snapshot vocabulary)
|
||||
|
||||
Use these as `keyword` / evidence labels (exact strings appear in `native_probable_cause`):
|
||||
|
||||
| Intent | Typical cause substrings |
|
||||
|--------|---------------------------|
|
||||
| Fiber / LOS | `ETPI) LOS`, `Fiber Break`, `Missing laser module` |
|
||||
| Optical threshold | `Input optical power(dBm) threshold crossed`, `Output optical power` |
|
||||
| Congestion | `Send bandwidth usage rate`, `Receive bandwidth usage rate` |
|
||||
| CRC | `Receive CRC error frames`, `Received CRC error packet` |
|
||||
| Offline / unmanaged | `BN EMS alarm NE communication failure` |
|
||||
| Dying gasp | `Remote dying gasp event` |
|
||||
| License | `Permanent license abnormal`, `No enough license resource` |
|
||||
| Power / env | `System Power off`, `Input undervoltage`, `temperature`, `Fan module` |
|
||||
| Control-plane (noisy) | `BGP Neighbour down`, `OSPF Neighbour`, `ISIS Neighbour`, `LDP Neighbour`, `State of PW in L2VPN`, `Tunnel down`, `NTP server` |
|
||||
|
||||
Do **not** treat PW/BGP volume leaders as “fiber cut” unless the user asked for those families.
|
||||
|
||||
### Dying gasp / BN EMS correlation (field-mandated)
|
||||
|
||||
When user mentions **dying gasp** (or correlates BN EMS with a port):
|
||||
|
||||
1. On the named NE: `queryUmeAlarmsRaw` with keyword=`dying gasp` (and/or host_name) — note `object_name` / slot-port and `last_seen_at`.
|
||||
2. Find peer: `findTopologyPaths` and/or LLDP/CLI on that port; identify far-end `host_name`.
|
||||
3. On the **peer**: look for **BN EMS** / `NE communication failure` (and related offline) with **near timestamp** (± window from step 1).
|
||||
4. Reply with both sides + times + whether correlation holds. Save this as the default dying-gasp playbook — do not answer only one NE.
|
||||
|
||||
### Peer / protocol correlation (BGP·OSPF·LDP)
|
||||
|
||||
Field pattern: alarms on `HOST-A` with peer IP → confirm on `HOST-B` (or peer from topology).
|
||||
|
||||
1. Query both hosts (or keyword + both host filters) for the protocol family.
|
||||
2. Align **occurrence / clear** times when asked.
|
||||
3. Peer match: prefer exact peer / router-id; if user says so, also match **identical 3rd+4th octet** of the peer address from the alarm text.
|
||||
4. Keep answers scoped to the named link (`A <> B`); do not dump unrelated area noise.
|
||||
|
||||
### Anti-patterns seen in field (do not repeat)
|
||||
|
||||
1. **Wrong playbook hijack** — User: `query alarm on MDN-AHJ-AN1` → must NOT run License/daily scheduled playbook. Answer that host’s current alarms only.
|
||||
|
|
@ -93,6 +136,8 @@ Delivery rules:
|
|||
4. **Apology loops** — If user asks “are you still running / why no response?”, resume the **quoted task** immediately; one short status line, then results. Do not ask what a Run ID might mean if `schedule_list` / job tools can answer.
|
||||
5. **Group noise** — Pure emoji / mention-only / “hi” with no ops ask: stay minimal or silent per group policy; do not give a long “how can I help” menu.
|
||||
6. **Blind CLI retries** — Wrong ZTE optic command once → switch to `show opticalinfo brief` (see managed-ne skill); do not retry the failed spelling.
|
||||
7. **fiber_cut vs optical power** — Area “optical power threshold” lists must **not** use `mode=fiber_cut` (that is LOS/Fiber Break biased).
|
||||
8. **Unfiltered dump** — Snapshot has ~80k uncleared rows; never list without severity/keyword/host/area/time.
|
||||
|
||||
### Answer shape (WhatsApp EN) — strict ops bot
|
||||
|
||||
|
|
|
|||
|
|
@ -31,10 +31,13 @@
|
|||
|------|------|
|
||||
| Critical Top | `aggregateUmeAlarms(severity=critical, top_ne=20)` |
|
||||
| 按 host 统计+Excel | `ume_alarm_xlsx_report(mode=aggregate_by_host, severity=critical)` |
|
||||
| 断纤/离线清单+Excel | `ume_alarm_xlsx_report(mode=fiber_cut\|offline)` |
|
||||
| 断纤/LOS 清单+Excel | `ume_alarm_xlsx_report(mode=fiber_cut)`(默认 keyword=`LOS`);纯 Fiber Break 再加 `keyword=Fiber Break` |
|
||||
| 离线/BN EMS+Excel | `ume_alarm_xlsx_report(mode=offline)`(默认 `BN EMS`) |
|
||||
| 区域光功率门限 | `queryUmeAlarmsRaw(keyword=optical power)` → 保留 `AREA-` 前缀;**不要** fiber_cut |
|
||||
| 发 Excel(已有表数据) | `write_xlsx(..., deliverable=true)` |
|
||||
| 区域 + 关键字(CRC/bandwidth/power) | `queryUmeAlarmsRaw(keyword=…)` → 过滤 `host` 前缀 `AREA-` |
|
||||
| 区域 + 关键字(CRC/bandwidth/license) | `queryUmeAlarmsRaw(keyword=…)` → 过滤 `host` 前缀 `AREA-` |
|
||||
| 单网元当前告警 | `queryUmeAlarms(host_name=…)` — **禁止**误跑 License 定时 playbook |
|
||||
| dying gasp | 本端 dying gasp → 对端 BN EMS(近时间窗)+ 端口/拓扑;见 SKILL |
|
||||
| 两端 capacity/optical | 解析两端 hostname → `findTopologyPaths` / LLDP → CLI optic(见 managed-ne) |
|
||||
| 时间窗历史(WIB) | freshness → `time_from`/`time_to`(按 Asia/Jakarta) |
|
||||
|
||||
|
|
@ -42,11 +45,28 @@
|
|||
|
||||
| 用户说法 | 正确理解 |
|
||||
|----------|----------|
|
||||
| congestion / bandwidth usage in ACH | UME keyword bandwidth + hostname `ACH-`;要验真再 CLI top N |
|
||||
| capacity A to B / optical power A <> B | **链路口 SFP/光功率**,不是单独告警 tally |
|
||||
| congestion / bandwidth usage in ACH | UME keyword=`bandwidth` + hostname `ACH-`;要验真再 CLI top N |
|
||||
| optical power threshold in BPP/PBR/PAL | keyword=`optical power` + 区域前缀;≠ fiber cut |
|
||||
| fiber cut / LOS sitelist | `mode=fiber_cut` / keyword=`LOS` 或 `Fiber Break`;回 host 列表 |
|
||||
| capacity A to B / optical power A <> B / SEMBAWA <> ANGKATAN | **链路口 SFP/光功率 CLI**,不是单独告警 tally |
|
||||
| dying gasp on HOST + port | 关联对端 **BN EMS** near timestamp(现场强制配方) |
|
||||
| which segment cut? + LOS host | `object_name` + topology/LLDP 找对端 |
|
||||
| BGP/OSPF/LDP on HOST / A <> B | 双端协议告警 + 时间对齐;peer 可按后两段 octet |
|
||||
| site SEMBAWA / ANGKATAN_EP | 先 inventory/wiki 解析成真实 `host_name` |
|
||||
| `17.50 - 18.15` | WIB 当天 17:50–18:15 |
|
||||
| check alarm on MDN-xxx | **仅该 host**;勿触发 daily license 等无关 playbook |
|
||||
| alarm code 4758 | 按 code 过滤;列出 **host_name** |
|
||||
|
||||
## 3d) 现场 cause 关键字(CSV 高频)
|
||||
|
||||
- LOS / Fiber Break / Missing laser → 断纤类
|
||||
- Input/Output optical power(dBm) threshold → 光功率门限(区域清单)
|
||||
- bandwidth usage rate threshold → 拥塞
|
||||
- CRC error → CRC
|
||||
- BN EMS … communication failure → 离线/非管
|
||||
- Remote dying gasp → 临终掉电类,必做对端关联
|
||||
- Permanent license / No enough license → license
|
||||
- BGP/OSPF/ISIS/LDP Neighbour down、State of PW、Tunnel down → 控制面/伪线噪声,勿当断纤
|
||||
|
||||
## 4) 诊断
|
||||
|
||||
|
|
|
|||
|
|
@ -113,12 +113,22 @@ def test_filter_preset_and_row_helpers() -> None:
|
|||
items = [
|
||||
{"alarm_event_type": "Communication LOS", "alarm_host_name": "A"},
|
||||
{"alarm_event_type": "fan fail", "alarm_host_name": "B"},
|
||||
{
|
||||
"alarm_native_probable_cause": "Ethernet physical (ETPI) Input optical power(dBm) threshold crossed",
|
||||
"alarm_host_name": "C",
|
||||
},
|
||||
{"alarm_native_probable_cause": "Fiber Break", "alarm_host_name": "D"},
|
||||
{"alarm_native_probable_cause": "BN EMS alarm NE communication failure", "alarm_host_name": "E"},
|
||||
]
|
||||
filtered = _filter_preset_items("fiber_cut", items)
|
||||
assert len(filtered) == 1
|
||||
hosts = {r["alarm_host_name"] for r in filtered}
|
||||
assert hosts == {"A", "D"}
|
||||
assert "C" not in hosts # optical-power threshold is not fiber_cut
|
||||
offline = _filter_preset_items("offline", items)
|
||||
assert {r["alarm_host_name"] for r in offline} == {"E"}
|
||||
headers, rows = _rows_from_list_items(filtered)
|
||||
assert headers[0] == "host_name"
|
||||
assert rows[0][0] == "A"
|
||||
assert rows[0][0] in {"A", "D"}
|
||||
_, agg_rows, meta = _rows_from_aggregate_buckets(
|
||||
{"buckets": [{"key": "H1", "count": 2}], "total": 2, "by_ne_missing": 0}
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue