feat: 实现干扰小时指标自动处理
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
from datetime import datetime
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
from openpyxl import Workbook
|
||||
|
||||
import main
|
||||
|
||||
|
||||
COMPLETE_WINDOW = "2026073110001100"
|
||||
INCOMPLETE_WINDOW = "2026073111001200"
|
||||
|
||||
|
||||
class PipelineTest(unittest.TestCase):
|
||||
def test_latest_complete_window_is_converted_and_merged(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp) / "source"
|
||||
output = Path(temp) / "output"
|
||||
for source_type in main.EXPECTED_TYPES:
|
||||
create_archive(root, source_type, COMPLETE_WINDOW)
|
||||
create_archive(root, main.EXPECTED_TYPES[0], INCOMPLETE_WINDOW)
|
||||
|
||||
result = main.process(main.LocalSource(root), output, lookback_days=3)
|
||||
|
||||
self.assertEqual(result.name, COMPLETE_WINDOW)
|
||||
converted = sorted((result / "converted").glob("*.csv"))
|
||||
self.assertEqual(len(converted), 7)
|
||||
with (result / f"interference_summary_{COMPLETE_WINDOW}.csv").open(encoding="utf-8-sig", newline="") as file:
|
||||
rows = list(csv.DictReader(file))
|
||||
self.assertEqual(len(rows), 7)
|
||||
self.assertEqual({row["source_type"] for row in rows}, set(main.EXPECTED_TYPES))
|
||||
self.assertIn("460-00-100-1", {row["cgi"] for row in rows})
|
||||
self.assertIn("46000-100-1", {row["cgi"] for row in rows})
|
||||
self.assertTrue(all(row["interference_dbm"] == "-100.5" for row in rows))
|
||||
|
||||
manifest = json.loads((result / "manifest.json").read_text(encoding="utf-8"))
|
||||
self.assertEqual(manifest["source_file_count"], 7)
|
||||
self.assertEqual(manifest["summary_rows"], 7)
|
||||
self.assertEqual(len(manifest["warnings"]), 1)
|
||||
self.assertIn(INCOMPLETE_WINDOW, manifest["warnings"][0])
|
||||
|
||||
def test_requested_incomplete_window_is_rejected(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp) / "source"
|
||||
create_archive(root, main.EXPECTED_TYPES[0], INCOMPLETE_WINDOW)
|
||||
|
||||
with self.assertRaisesRegex(main.ProcessingError, "Requested window is incomplete"):
|
||||
main.process(main.LocalSource(root), Path(temp) / "output", 3, INCOMPLETE_WINDOW)
|
||||
|
||||
def test_schema_change_is_rejected(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp) / "source"
|
||||
for source_type in main.EXPECTED_TYPES:
|
||||
create_archive(root, source_type, COMPLETE_WINDOW, bad_header=source_type == main.EXPECTED_TYPES[0])
|
||||
|
||||
with self.assertRaisesRegex(main.ProcessingError, "Unexpected Sheet0 header"):
|
||||
main.process(main.LocalSource(root), Path(temp) / "output", 3)
|
||||
|
||||
def test_cross_midnight_window(self) -> None:
|
||||
self.assertEqual(
|
||||
main.window_bounds("2026073023000000"),
|
||||
("2026-07-30 23:00:00", "2026-07-31 00:00:00"),
|
||||
)
|
||||
|
||||
|
||||
def create_archive(root: Path, source_type: str, window: str, bad_header: bool = False) -> None:
|
||||
date_dir = root / f"{window[:4]}-{window[4:6]}-{window[6:8]}"
|
||||
date_dir.mkdir(parents=True, exist_ok=True)
|
||||
filename = f"{source_type}_LWP_每小时_过滤110_{window}"
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Sheet0"
|
||||
header = list(main.EXPECTED_HEADERS[source_type])
|
||||
if bad_header:
|
||||
header[-1] = "unexpected"
|
||||
sheet.append(header)
|
||||
sheet.append(mock_row(source_type, window))
|
||||
metadata = workbook.create_sheet("指标(计数器)")
|
||||
metadata.append(["指标或计数器", "指标或计数器描述", "指标公式", "指标或计数器状态"])
|
||||
content = io.BytesIO()
|
||||
workbook.save(content)
|
||||
workbook.close()
|
||||
with zipfile.ZipFile(date_dir / f"{filename}.zip", "w", zipfile.ZIP_DEFLATED) as archive:
|
||||
archive.writestr(f"{filename}.xlsx", content.getvalue())
|
||||
|
||||
|
||||
def mock_row(source_type: str, window: str) -> list[object]:
|
||||
header = main.EXPECTED_HEADERS[source_type]
|
||||
values: dict[str, object] = {column: "mock" for column in header}
|
||||
values.update(
|
||||
{
|
||||
"开始时间": datetime.strptime(window[:12], "%Y%m%d%H%M"),
|
||||
"粒度": "1 小时",
|
||||
"eNodeBId": 100,
|
||||
"eNodeBID": 100,
|
||||
"cellId": 1,
|
||||
"小区ID": 1,
|
||||
"masterOperatorId": "46000-100-1",
|
||||
"E-UTRAN FDD小区名称": f"{source_type}-小区",
|
||||
"E-UTRAN TDD小区名称": f"{source_type}-小区",
|
||||
"CU小区配置名称": f"{source_type}-小区",
|
||||
"小区名称": f"{source_type}-小区",
|
||||
"载波平均噪声干扰(dBm)": -100.5,
|
||||
"小区上行平均干扰电平(dBm)": -100.5,
|
||||
}
|
||||
)
|
||||
return [values[column] for column in header]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user