refactor(db)!: adopt project-routed pooled databases

Reorganize WNDB by responsibility and remove legacy scheme endpoints.\n\nRoute analysis and time-series access through project pools, preserve transactional realtime replacement, and refresh GIS materialized views after writes.\n\nAdd database architecture documentation, live pooling coverage, API contract updates, and executable container verification.\n\nBREAKING CHANGE: legacy scheme APIs and flat app.native.wndb module imports are removed.
This commit is contained in:
2026-08-25 18:35:05 +08:00
parent fdbcc5c033
commit fa188af0b1
181 changed files with 8446 additions and 33546 deletions
+210 -574
View File
@@ -1,43 +1,23 @@
import ast
import json
from datetime import date, datetime
from typing import Any
from uuid import UUID, uuid4
import geopandas as gpd
import pandas as pd
import psycopg
from sqlalchemy import create_engine
from psycopg.types.json import Jsonb
from app.infra.db.project_routing import get_project_pgconn_string
from app.native.wndb.core.connection import project_connection
from app.services.time_api import parse_utc_time
# 2025/03/23
def scheme_name_exists(name: str, scheme_name: str) -> bool:
"""
判断传入的 scheme_name 是否已存在于 scheme_list 表中,用于输入框判断
:param name: 数据库名称
:param scheme_name: 需要判断的方案名称
:return: 如果存在返回 True,否则返回 False
"""
try:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT COUNT(*) FROM scheme_list WHERE scheme_name = %s",
(scheme_name,),
)
result = cur.fetchone()
if result is not None and result[0] > 0:
return True
else:
return False
except Exception as e:
print(f"查询 scheme_name 时出错:{e}")
return False
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"select exists(select 1 from analysis.runs where name = %s)",
(scheme_name,),
)
row = cur.fetchone()
return bool(row and row[0])
# 2025/03/23
def store_scheme_info(
name: str,
scheme_name: str,
@@ -45,202 +25,165 @@ def store_scheme_info(
username: str,
scheme_start_time: datetime | str,
scheme_detail: dict,
):
"""
将一条方案记录插入 scheme_list 表中
:param name: 数据库名称
:param scheme_name: 方案名称
:param scheme_type: 方案类型
:param username: MetaDB 中的用户名快照
:param scheme_start_time: 带时区的方案起始时间;写入前统一转换为 UTC
:param scheme_detail: 方案详情(字典,会转换为 JSON)
:return:
"""
try:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
sql = """
INSERT INTO scheme_list (scheme_name, scheme_type, username, scheme_start_time, scheme_detail)
VALUES (%s, %s, %s, %s, %s)
"""
# 将字典转换为 JSON 字符串
scheme_detail_json = json.dumps(scheme_detail)
normalized_scheme_start_time = parse_utc_time(
scheme_start_time, field_name="scheme_start_time"
)
cur.execute(
sql,
(
scheme_name,
scheme_type,
username,
normalized_scheme_start_time,
scheme_detail_json,
),
)
conn.commit()
print("方案信息存储成功!")
except Exception as e:
print(f"存储方案信息时出错:{e}")
) -> UUID:
"""Create one completed, immutable analysis run."""
return create_analysis_run(
name=name,
scheme_name=scheme_name,
scheme_type=scheme_type,
username=username,
scheme_start_time=scheme_start_time,
scheme_detail=scheme_detail,
status="completed",
)
# 2025/03/23
def delete_scheme_info(name: str, scheme_name: str) -> None:
"""
从 scheme_list 表中删除指定的方案
:param name: 数据库名称
:param scheme_name: 要删除的方案名称
"""
try:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
# 使用参数化查询删除方案记录
cur.execute(
"DELETE FROM scheme_list WHERE scheme_name = %s", (scheme_name,)
)
conn.commit()
print(f"方案 {scheme_name} 删除成功!")
except Exception as e:
print(f"删除方案时出错:{e}")
def create_analysis_run(
name: str,
scheme_name: str,
scheme_type: str,
username: str,
scheme_start_time: datetime | str,
scheme_detail: dict,
*,
status: str = "running",
) -> UUID:
"""Create a distinct execution record; names are labels, not identities."""
started_at = parse_utc_time(scheme_start_time, field_name="scheme_start_time")
run_id = uuid4()
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"""
insert into analysis.runs
(run_id, name, run_type, created_by, created_at, started_at, status, parameters)
values (%s, %s, %s, %s, now(), %s, %s, %s)
""",
(
run_id,
scheme_name,
scheme_type,
username,
started_at,
status,
Jsonb(scheme_detail),
),
)
return run_id
def update_analysis_run(
name: str,
run_id: UUID,
*,
status: str,
username: str,
scheme_detail: dict,
) -> None:
"""Update lifecycle state and metadata for one execution identity."""
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"""
update analysis.runs
set created_by = %s, status = %s, parameters = %s
where run_id = %s
""",
(username, status, Jsonb(scheme_detail), run_id),
)
if cur.rowcount != 1:
raise LookupError(f"analysis run {run_id} does not exist")
def _run_row(row: dict[str, Any]) -> dict[str, Any]:
parameters = row.get("parameters") if isinstance(row.get("parameters"), dict) else {}
return {
"run_id": row["run_id"],
"name": row["name"],
"run_type": row["run_type"],
"created_by": row["created_by"],
"created_at": row["created_at"],
"started_at": row["started_at"],
"status": row["status"],
"parameters": parameters,
}
def _list_runs(
name: str,
run_type: str | None = None,
query_date: date | None = None,
) -> list[dict[str, Any]]:
clauses: list[str] = []
params: list[Any] = []
if run_type:
clauses.append("run_type = %s")
params.append(run_type)
if query_date is not None:
clauses.append("created_at::date = %s")
params.append(query_date)
where = f"where {' and '.join(clauses)}" if clauses else ""
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
f"select run_id, name, run_type, created_by, created_at, started_at, status, parameters from analysis.runs {where} order by created_at desc",
params,
)
return [_run_row(row) for row in cur.fetchall()]
# 2025/03/23
def query_scheme_list(
name: str,
scheme_type: str | None = None,
query_date: date | None = None,
) -> list:
"""
查询pg数据库中的scheme_list,按照 create_time 降序排列,离现在时间最近的记录排在最前面
:param name: 项目名称(数据库名称)
:param scheme_type: 方案类型;为空时返回全部类型
:param query_date: 查询日期;为空时不按日期过滤
:return: 返回查询结果的所有行
"""
try:
# 动态替换数据库名称
conn_string = get_project_pgconn_string(db_name=name)
# 连接到 PostgreSQL 数据库(这里是数据库 "bb"
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
if scheme_type and query_date is not None:
cur.execute(
"""
SELECT *
FROM scheme_list
WHERE scheme_type = %s AND DATE(create_time) = %s
ORDER BY create_time DESC
""",
(scheme_type, query_date),
)
elif scheme_type:
cur.execute(
"""
SELECT *
FROM scheme_list
WHERE scheme_type = %s
ORDER BY create_time DESC
""",
(scheme_type,),
)
elif query_date is not None:
cur.execute(
"""
SELECT *
FROM scheme_list
WHERE DATE(create_time) = %s
ORDER BY create_time DESC
""",
(query_date,),
)
else:
cur.execute("SELECT * FROM scheme_list ORDER BY create_time DESC")
rows = cur.fetchall()
return rows
except Exception as e:
print(f"查询错误:{e}")
) -> list[dict[str, Any]]:
return _list_runs(name, scheme_type, query_date)
def _filter_scheme_detail_scope(
result: dict,
def _get_run_by_name(
name: str,
scheme_type: str | None = None,
) -> dict:
if not result:
return {}
if scheme_type and result.get("scheme_type") != scheme_type:
return {}
network = result.get("network")
if network not in (None, name):
return {}
return result
run_name: str,
run_type: str | None = None,
) -> dict[str, Any]:
params: list[Any] = [run_name]
type_clause = ""
if run_type:
type_clause = "and run_type = %s"
params.append(run_type)
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
f"""
select run_id, name, run_type, created_by, created_at, started_at,
status, parameters
from analysis.runs
where name = %s {type_clause}
order by created_at desc
limit 1
""",
params,
)
row = cur.fetchone()
return _run_row(row) if row else {}
def get_analysis_run(name: str, run_id: UUID) -> dict[str, Any]:
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"""
select run_id, name, run_type, created_by, created_at, started_at,
status, parameters
from analysis.runs
where run_id = %s
""",
(run_id,),
)
row = cur.fetchone()
return _run_row(row) if row else {}
def query_scheme_detail(
name: str,
scheme_name: str,
scheme_type: str | None = None,
) -> dict:
if scheme_type == "dma_leak_identification":
return _filter_scheme_detail_scope(
query_leakage_identify_scheme_detail(name, scheme_name),
name,
scheme_type,
)
if scheme_type == "burst_detection":
return _filter_scheme_detail_scope(
query_burst_detection_scheme_detail(name, scheme_name),
name,
scheme_type,
)
if scheme_type == "burst_location":
return _filter_scheme_detail_scope(
query_burst_location_scheme_detail(name, scheme_name),
name,
scheme_type,
)
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
if scheme_type:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_name = %s AND scheme_type = %s
LIMIT 1
""",
(scheme_name, scheme_type),
)
else:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_name = %s
LIMIT 1
""",
(scheme_name,),
)
row = cur.fetchone()
if row is None:
return {}
detail = row[6] if isinstance(row[6], dict) else {}
return _filter_scheme_detail_scope({
"scheme_id": row[0],
"scheme_name": row[1],
"scheme_type": row[2],
"username": row[3],
"create_time": row[4],
"scheme_start_time": row[5],
"scheme_detail": detail,
"network": detail.get("network"),
"result_payload": detail.get("result_payload", {}),
}, name, scheme_type)
) -> dict[str, Any]:
return _get_run_by_name(name, scheme_name, scheme_type)
def store_leakage_identify_result(
@@ -255,42 +198,51 @@ def store_leakage_identify_result(
run_status: str = "completed",
error_message: str | None = None,
) -> None:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO public.leakage_identify_result
(
scheme_name, network, run_status, error_message,
sensor_nodes, result_rows, node_area_map, areas, drawing_payload
)
VALUES (%s, %s, %s, %s, %s::jsonb, %s::jsonb, %s::jsonb, %s::jsonb, %s::jsonb)
ON CONFLICT (scheme_name)
DO UPDATE SET
network = EXCLUDED.network,
run_status = EXCLUDED.run_status,
error_message = EXCLUDED.error_message,
sensor_nodes = EXCLUDED.sensor_nodes,
result_rows = EXCLUDED.result_rows,
node_area_map = EXCLUDED.node_area_map,
areas = EXCLUDED.areas,
drawing_payload = EXCLUDED.drawing_payload,
created_at = NOW();
""",
(
scheme_name,
network,
run_status,
error_message,
json.dumps(sensor_nodes),
json.dumps(result_rows),
json.dumps(node_area_map),
json.dumps(areas),
json.dumps(drawing_payload or {}),
),
)
conn.commit()
run = _get_run_by_name(name, scheme_name, "dma_leak_identification")
if not run:
raise LookupError(f"analysis run {scheme_name!r} does not exist")
payload = {
"network": network,
"run_status": run_status,
"error_message": error_message,
"sensor_nodes": sensor_nodes,
"rows": result_rows,
"node_area_map": node_area_map,
"areas": areas,
"drawing_payload": drawing_payload or {},
}
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"insert into analysis.results (run_id, result_type, payload) values (%s, 'leakage_identification', %s)",
(run["run_id"], Jsonb(payload)),
)
def _list_typed_runs(
name: str,
network: str,
run_type: str,
query_date: date | None,
) -> list[dict[str, Any]]:
rows = _list_runs(name, run_type, query_date)
return [
row
for row in rows
if not network or row["parameters"].get("network") in (None, network)
]
def _typed_run_detail(name: str, run_name: str, run_type: str) -> dict[str, Any]:
run = _get_run_by_name(name, run_name, run_type)
if not run:
return {}
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute(
"select result_type, payload, created_at from analysis.results where run_id = %s order by created_at, result_id",
(run["run_id"],),
)
results = [dict(row) for row in cur.fetchall()]
return run | {"results": results}
def query_leakage_identify_schemes(
@@ -298,100 +250,12 @@ def query_leakage_identify_schemes(
network: str,
scheme_type: str = "dma_leak_identification",
query_date: date | None = None,
) -> list[dict]:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
if query_date is None:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s
ORDER BY create_time DESC
""",
(scheme_type,),
)
else:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s AND DATE(create_time) = %s
ORDER BY create_time DESC
""",
(scheme_type, query_date),
)
rows = cur.fetchall()
result = []
for row in rows:
detail = row[6] if isinstance(row[6], dict) else {}
if network and detail.get("network") not in (None, network):
continue
result.append(
{
"scheme_id": row[0],
"scheme_name": row[1],
"scheme_type": row[2],
"username": row[3],
"create_time": row[4],
"scheme_start_time": row[5],
"scheme_detail": detail,
}
)
return result
) -> list[dict[str, Any]]:
return _list_typed_runs(name, network, scheme_type, query_date)
def query_leakage_identify_scheme_detail(name: str, scheme_name: str) -> dict:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_name = %s
LIMIT 1
""",
(scheme_name,),
)
base_row = cur.fetchone()
if base_row is None:
return {}
cur.execute(
"""
SELECT network, created_at, run_status, error_message, sensor_nodes, result_rows, node_area_map, areas, drawing_payload
FROM public.leakage_identify_result
WHERE scheme_name = %s
LIMIT 1
""",
(scheme_name,),
)
result_row = cur.fetchone()
if result_row is None:
return {}
return {
"scheme_id": base_row[0],
"scheme_name": base_row[1],
"scheme_type": base_row[2],
"username": base_row[3],
"create_time": base_row[4],
"scheme_start_time": base_row[5],
"scheme_detail": base_row[6] if isinstance(base_row[6], dict) else {},
"network": result_row[0],
"result_created_at": result_row[1],
"run_status": result_row[2],
"error_message": result_row[3],
"sensor_nodes": result_row[4] if isinstance(result_row[4], list) else [],
"rows": result_row[5] if isinstance(result_row[5], list) else [],
"node_area_map": result_row[6] if isinstance(result_row[6], dict) else {},
"areas": result_row[7] if isinstance(result_row[7], list) else [],
"drawing_payload": (
result_row[8]
if isinstance(result_row[8], dict)
else {"type": "FeatureCollection", "features": []}
),
}
def query_leakage_identify_scheme_detail(name: str, scheme_name: str) -> dict[str, Any]:
return _typed_run_detail(name, scheme_name, "dma_leak_identification")
def query_burst_location_schemes(
@@ -399,78 +263,12 @@ def query_burst_location_schemes(
network: str,
scheme_type: str = "burst_location",
query_date: date | None = None,
) -> list[dict]:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
if query_date is None:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s
ORDER BY create_time DESC
""",
(scheme_type,),
)
else:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s AND DATE(create_time) = %s
ORDER BY create_time DESC
""",
(scheme_type, query_date),
)
rows = cur.fetchall()
result = []
for row in rows:
detail = row[6] if isinstance(row[6], dict) else {}
if network and detail.get("network") not in (None, network):
continue
result.append(
{
"scheme_id": row[0],
"scheme_name": row[1],
"scheme_type": row[2],
"username": row[3],
"create_time": row[4],
"scheme_start_time": row[5],
"scheme_detail": detail,
}
)
return result
) -> list[dict[str, Any]]:
return _list_typed_runs(name, network, scheme_type, query_date)
def query_burst_location_scheme_detail(name: str, scheme_name: str) -> dict:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_name = %s
LIMIT 1
""",
(scheme_name,),
)
base_row = cur.fetchone()
if base_row is None:
return {}
detail = base_row[6] if isinstance(base_row[6], dict) else {}
return {
"scheme_id": base_row[0],
"scheme_name": base_row[1],
"scheme_type": base_row[2],
"username": base_row[3],
"create_time": base_row[4],
"scheme_start_time": base_row[5],
"scheme_detail": detail,
"network": detail.get("network"),
"result_payload": detail.get("result_payload", {}),
}
def query_burst_location_scheme_detail(name: str, scheme_name: str) -> dict[str, Any]:
return _typed_run_detail(name, scheme_name, "burst_location")
def query_burst_detection_schemes(
@@ -478,171 +276,9 @@ def query_burst_detection_schemes(
network: str,
scheme_type: str = "burst_detection",
query_date: date | None = None,
) -> list[dict]:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
if query_date is None:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s
ORDER BY create_time DESC
""",
(scheme_type,),
)
else:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_type = %s AND DATE(create_time) = %s
ORDER BY create_time DESC
""",
(scheme_type, query_date),
)
rows = cur.fetchall()
result = []
for row in rows:
detail = row[6] if isinstance(row[6], dict) else {}
if network and detail.get("network") not in (None, network):
continue
result.append(
{
"scheme_id": row[0],
"scheme_name": row[1],
"scheme_type": row[2],
"username": row[3],
"create_time": row[4],
"scheme_start_time": row[5],
"scheme_detail": detail,
}
)
return result
) -> list[dict[str, Any]]:
return _list_typed_runs(name, network, scheme_type, query_date)
def query_burst_detection_scheme_detail(name: str, scheme_name: str) -> dict:
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"""
SELECT scheme_id, scheme_name, scheme_type, username, create_time, scheme_start_time, scheme_detail
FROM public.scheme_list
WHERE scheme_name = %s
LIMIT 1
""",
(scheme_name,),
)
base_row = cur.fetchone()
if base_row is None:
return {}
detail = base_row[6] if isinstance(base_row[6], dict) else {}
return {
"scheme_id": base_row[0],
"scheme_name": base_row[1],
"scheme_type": base_row[2],
"username": base_row[3],
"create_time": base_row[4],
"scheme_start_time": base_row[5],
"scheme_detail": detail,
"network": detail.get("network"),
"result_payload": detail.get("result_payload", {}),
}
# 2025/03/23
def upload_shp_to_pg(name: str, table_name: str, role: str, shp_file_path: str):
"""
将 Shapefile 文件上传到 PostgreSQL 数据库
:param name: 项目名称(数据库名称)
:param table_name: 创建表的名字
:param role: 数据库角色名,位于c盘user中查看
:param shp_file_path: shp文件的路径
:return:
"""
try:
# 动态连接到指定的数据库
conn_string = get_project_pgconn_string(db_name=name)
with psycopg.connect(conn_string) as conn:
# 读取 Shapefile 文件
gdf = gpd.read_file(shp_file_path)
# 检查投影坐标系(CRS),并确保是 EPSG:4326
if gdf.crs.to_string() != "EPSG:4490":
gdf = gdf.to_crs(epsg=4490)
# 使用 GeoDataFrame 的 .to_postgis 方法将数据写入 PostgreSQL
# 需要在数据库中提前安装 PostGIS 扩展
engine = create_engine(f"postgresql+psycopg2://{role}:@127.0.0.1/{name}")
gdf.to_postgis(
table_name, engine, if_exists="replace", index=True, index_label="id"
)
print(
f"Shapefile 文件成功上传到 PostgreSQL 数据库 '{name}' 的表 '{table_name}'."
)
except Exception as e:
print(f"上传 Shapefile 到 PostgreSQL 时出错:{e}")
def submit_risk_probability_result(name: str, result_file_path: str) -> None:
"""
将管网风险评估结果导入pg数据库
:param name: 项目名称(数据库名称)
:param result_file_path: 结果文件路径
:return:
"""
# 自动检测文件编码
# with open({result_file_path}, 'rb') as file:
# raw_data = file.read()
# detected = chardet.detect(raw_data)
# file_encoding = detected['encoding']
# print(f"检测到的文件编码:{file_encoding}")
try:
# 动态替换数据库名称
conn_string = get_project_pgconn_string(db_name=name)
# 连接到 PostgreSQL 数据库
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
# 检查 scada_info 表是否为空
cur.execute("SELECT COUNT(*) FROM pipe_risk_probability;")
count = cur.fetchone()[0]
if count > 0:
print("pipe_risk_probability表中已有数据,正在清空记录...")
cur.execute("DELETE FROM pipe_risk_probability;")
print("表记录已清空。")
# 读取Excel并转换x/y列为列表
df = pd.read_excel(result_file_path, sheet_name="Sheet1")
df["x"] = df["x"].apply(ast.literal_eval)
df["y"] = df["y"].apply(ast.literal_eval)
# 批量插入数据
for index, row in df.iterrows():
insert_query = """
INSERT INTO pipe_risk_probability
(pipeID, pipeage, risk_probability_now, x, y)
VALUES (%s, %s, %s, %s, %s)
"""
cur.execute(
insert_query,
(
row["pipeID"],
row["pipeage"],
row["risk_probability_now"],
row["x"], # 直接传递列表
row["y"], # 同上
),
)
conn.commit()
print("风险评估结果导入成功")
except Exception as e:
print(f"导入时出错:{e}")
def query_burst_detection_scheme_detail(name: str, scheme_name: str) -> dict[str, Any]:
return _typed_run_detail(name, scheme_name, "burst_detection")