重构DBN模型

This commit is contained in:
wzy-warehouse
2026-06-12 09:45:35 +08:00
parent b7502954ba
commit 118dbd18cf
12 changed files with 148 additions and 131 deletions
+2 -2
View File
@@ -30,7 +30,7 @@ def _build_prediction_items(results: List[Dict[str, Any]]) -> List[PredictionIte
max_hazard = max(probs, key=probs.get) max_hazard = max(probs, key=probs.get)
items.append(PredictionItem( items.append(PredictionItem(
id=r["point_id"], id=r["source_id"], # 使用 source_id(隐患点/风险点ID)而非 xian_risk_factors.id
type=SOURCE_TYPE_MAP.get(r.get("source_type"), "未知"), type=SOURCE_TYPE_MAP.get(r.get("source_type"), "未知"),
probability=round(probs[max_hazard], 4), probability=round(probs[max_hazard], 4),
level=LEVEL_MAP.get(levels.get(max_hazard, "none"), ""), level=LEVEL_MAP.get(levels.get(max_hazard, "none"), ""),
@@ -70,7 +70,7 @@ def _predict_sync(point_ids: Optional[List[int]], region_code: Optional[str],
save_results = [ save_results = [
{ {
"point_id": r.get("point_id"), "point_id": r.get("source_id"), # 使用 source_id(隐患点/风险点ID)而非 xian_risk_factors.id
"source_type": r.get("source_type"), "source_type": r.get("source_type"),
"lon": r.get("lon"), "lon": r.get("lon"),
"lat": r.get("lat"), "lat": r.get("lat"),
+2 -2
View File
@@ -31,7 +31,7 @@ def _build_prediction_items(results: List[Dict[str, Any]]) -> List[PredictionIte
max_hazard = max(probs, key=probs.get) max_hazard = max(probs, key=probs.get)
items.append(PredictionItem( items.append(PredictionItem(
id=r["point_id"], id=r["source_id"], # 使用 source_id(隐患点/风险点ID)而非 xian_risk_factors.id
type=SOURCE_TYPE_MAP.get(r.get("source_type"), "未知"), type=SOURCE_TYPE_MAP.get(r.get("source_type"), "未知"),
probability=round(probs[max_hazard], 4), probability=round(probs[max_hazard], 4),
level=LEVEL_MAP.get(levels.get(max_hazard, "none"), ""), level=LEVEL_MAP.get(levels.get(max_hazard, "none"), ""),
@@ -73,7 +73,7 @@ def _predict_sync(point_ids: Optional[List[int]], region_code: Optional[str],
} }
save_results = [ save_results = [
{ {
"point_id": r.get("point_id"), "point_id": r.get("source_id"), # 使用 source_id(隐患点/风险点ID)而非 xian_risk_factors.id
"source_type": r.get("source_type"), "source_type": r.get("source_type"),
"lon": r.get("lon"), "lon": r.get("lon"),
"lat": r.get("lat"), "lat": r.get("lat"),
+66 -56
View File
@@ -2,8 +2,8 @@
# 定义所有连续因子的分箱规则 # 定义所有连续因子的分箱规则
# 包含暴雨灾害链和地震灾害链的全部因子 # 包含暴雨灾害链和地震灾害链的全部因子
# #
# 2026-06-06: 基于1201个样本的实际数据分布,采用分位数分箱(等频分箱) # 2026-06-11: 基于1365个样本(796隐患点+569风险点)的实际数据分布
# 替代原有等宽分箱,使每个区间样本量更均匀 # 连续因子采用分位数分箱(等频分箱),分类因子基于实际编码映射
# ============================================ # ============================================
# 暴雨触发层离散化规则(保持气象标准不变) # 暴雨触发层离散化规则(保持气象标准不变)
@@ -62,17 +62,17 @@ seismic_intensity:
elevation: elevation:
description: "高程" description: "高程"
unit: "m" unit: "m"
# 数据: [356, 1934], 均值764.3±317.89, 偏度0.973 # 数据: [354, 1926], 均值789.36±325.64
# 分位数: [356, 470, 624, 792, 1016, 1934] # 分位数: [354, 482, 637, 817, 1074, 1926]
bins: [356, 470, 624, 792, 1016, 1934] bins: [354, 482, 637, 817, 1074, 1926]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
slope: slope:
description: "坡度" description: "坡度"
unit: "度" unit: "度"
# 数据: [0.11, 47.14], 均值9.42±8.57, 偏度1.433 # 数据: [0.0, 47.0], 均值10.46±9.12
# 分位数: [0.11, 1.81, 5.43, 9.48, 15.13, 47.14] # 分位数: [0.0, 2.0, 6.0, 11.0, 17.0, 47.0]
bins: [0.11, 1.81, 5.43, 9.48, 15.13, 47.14] bins: [0.0, 2.0, 6.0, 11.0, 17.0, 47.0]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
aspect: aspect:
@@ -85,13 +85,20 @@ aspect:
soil_type: soil_type:
description: "土壤分类(中国土壤分类系统)" description: "土壤分类(中国土壤分类系统)"
unit: "分类代码" unit: "3位数编码"
# 中国土壤分类系统25个亚类,本数据库出现8种
# 编码规则:1xx淋溶土、2xx钙层/均腐土、4xx初育土
mapping: mapping:
0: ultisol # 老成土 110: brown_soil # 棕壤(淋溶土,秦岭北麓山地)
6: entisol # 初育土 120: brown_soil # 暗棕壤(淋溶土,高海拔山地)
11: fluvo_aquic # 潮土 130: yellow_brown # 黄棕壤(淋溶土,暖温带过渡区)
18: yellow_brown # 黄棕壤 150: yellow_brown # 黄褐土(淋溶土,黄土塬区)
default: entisol 210: cinnamon # 褐土(钙层土,黄土塬区主要旱作土壤)
240: black_lu # 黑垆土(均腐土,古土壤残余)
410: alluvial # 新积土(初育土,渭河冲积平原)
420: aeolian # 风沙土(初育土,风积沙质土壤)
255: unknown # GIS背景值(1个样本)
default: cinnamon # 褐土占比最大(38.8%),作为默认值
lithology: lithology:
description: "岩性(中国地质分类)" description: "岩性(中国地质分类)"
@@ -105,18 +112,19 @@ lithology:
11: mixed_clastic # 混合碎屑沉积岩(砂岩+泥岩互层) 11: mixed_clastic # 混合碎屑沉积岩(砂岩+泥岩互层)
13: terrigenous # 陆源碎屑岩(砂岩、粉砂岩) 13: terrigenous # 陆源碎屑岩(砂岩、粉砂岩)
14: unconsolidated # 松散堆积物(黄土、冲洪积) 14: unconsolidated # 松散堆积物(黄土、冲洪积)
255: unknown # 无数据(GIS栅格背景值)
default: unconsolidated default: unconsolidated
landuse: landuse:
description: "土地利用类型" description: "土地利用类型"
unit: "分类代码" unit: "分类代码"
mapping: mapping:
10: forest # 林地 1: forest # 林地GIS栅格编码1
30: farmland # 农田 2: farmland # 农田GIS栅格编码2
40: urban # 城市 3: urban # 城市GIS栅格编码3
50: water # 水域 4: water # 水域GIS栅格编码4
60: barren # 裸地 5: barren # 裸地GIS栅格编码5
80: farmland # 耕地(合并入农田) 8: farmland # 耕地(GIS栅格编码8合并入农田)
default: farmland default: farmland
terrain: terrain:
@@ -130,80 +138,82 @@ terrain:
5: gentle_hill # 低缓丘陵(塬边过渡带) 5: gentle_hill # 低缓丘陵(塬边过渡带)
6: low_mountain # 低山(骊山等) 6: low_mountain # 低山(骊山等)
7: flat_plain # 平缓平原(冲积平原) 7: flat_plain # 平缓平原(冲积平原)
255: unknown # 无数据(GIS栅格背景值)
default: hill default: hill
impervious: impervious:
description: "不透水率" description: "不透水率"
unit: "百分比" unit: "小数比例(0-1"
# 数据: [0.0, 97.2], 均值16.40±25.99, 偏度1.787 # 数据: [0.0, 1.0], 均值0.31±0.46
# 26.9%为0.0(无硬化地表),非零值右偏分布 # 68.9%为0.0(无硬化地表),非零值右偏分布
# 分箱策略:0单独一类,其余4等分(分位数分箱) # 分箱策略:0单独一类,其余4等分
# 分位数(非零): [2.0, 9.95, 31.8, 97.2] bins: [0.0, 0.01, 0.25, 0.50, 0.75, 1.0]
bins: [0.0, 0.01, 2.0, 10.0, 32.0, 97.2]
labels: [none, very_low, low, medium, high] labels: [none, very_low, low, medium, high]
ndvi: ndvi:
description: "植被指数" description: "植被指数"
unit: "NDVI值" unit: "NDVI值(×1000缩放)"
# 数据: [1.25, 38.68], 均值20.67±5.87, 偏度-0.106 # 数据: [-1.0, 5336.0], 均值2045.95±689.47
# 分位数: [1.25, 17.09, 20.3, 22.4, 25.2, 38.68] # 分位数: [-1.0, 1616.2, 1891.0, 2172.0, 2496.0, 5336.0]
bins: [1.25, 17.09, 20.3, 22.4, 25.2, 38.68] bins: [-1.0, 1616.0, 1891.0, 2172.0, 2496.0, 5336.0]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
sand_content: sand_content:
description: "土壤含沙量" description: "土壤含沙量"
unit: "百分比" unit: "百分比"
# 数据: [23.0, 52.0], 均值34.43±4.29, 偏度0.538 # 数据: [23.0, 255.0], 均值35.14±7.75
# 分位数: [23.0, 31.0, 33.0, 35.0, 37.0, 52.0] # 255为异常值(缺失值编码),正常范围[23, 52]
bins: [23.0, 31.0, 33.0, 35.0, 37.0, 52.0] # 分位数(正常值): [23.0, 31.0, 34.0, 35.0, 38.0, 52.0]
bins: [23.0, 31.0, 34.0, 35.0, 38.0, 255.0]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
ph: ph:
description: "土壤PH值" description: "土壤PH值"
unit: "PH值" unit: "PH值(×10缩放,如71=7.1"
# 数据: [59.0, 81.0], 均值71.79±4.14, 偏度-0.398 # 数据: [60.0, 255.0], 均值71.82±6.91
# 分位数: [59.0, 68.0, 72.0, 74.0, 76.0, 81.0] # 255为异常值(缺失值编码),正常范围[59, 81]
bins: [59.0, 68.0, 72.0, 74.0, 76.0, 81.0] # 分位数(正常值): [60.0, 67.0, 71.0, 74.0, 76.0, 81.0]
bins: [60.0, 67.0, 71.0, 74.0, 76.0, 255.0]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
soil_moisture: soil_moisture:
description: "土壤湿度" description: "土壤湿度"
unit: "百分比" unit: "小数比例(0-1"
# 数据: [0.0, 41.1], 均值32.02±14.92, 偏度-1.676 # 数据: [-1.0, 0.28], 均值0.15±0.08, 约10%为-1(缺失值)
# 约10%为0.0(缺失/极端干燥),其余集中在37-41 # 正常值范围: [0.0, 0.28]
# 分位数: [0.0, 37.7, 38.6, 38.9, 39.4, 41.1] # 分位数(正常值): [0.0, 0.12, 0.14, 0.16, 0.19, 0.28]
bins: [0.0, 37.0, 38.5, 39.5, 41.1] # 分箱策略:-1视为缺失/极端干燥,其余4等分
labels: [very_low, low, medium, high] bins: [-1.0, 0.0, 0.10, 0.14, 0.18, 0.28]
labels: [very_low, low, medium, high, very_high]
organic_carbon: organic_carbon:
description: "有机碳" description: "有机碳"
unit: "百分比" unit: "百分比"
# 数据: [0.0, 73.0], 均值38.36±19.14, 偏度-1.187 # 数据: [0.0, 65.0], 均值39.03±19.13
# 分位数: [0.0, 34.0, 41.0, 47.0, 53.0, 73.0] # 分位数: [0.0, 34.0, 42.0, 48.0, 53.0, 65.0]
bins: [0.0, 34.0, 41.0, 47.0, 53.0, 73.0] bins: [0.0, 34.0, 42.0, 48.0, 53.0, 65.0]
labels: [very_low, low, medium, high, very_high] labels: [very_low, low, medium, high, very_high]
dist_to_river: dist_to_river:
description: "距离河道距离" description: "距离河道距离"
unit: "米" unit: "米"
# 数据: [12.21, 29904.99], 均值11003.92±6582.23, 偏度0.271 # 数据: [12.21, 29968.26], 均值11378.07±6704.59
# 分位数: [12.21, 5165.0, 9003.0, 12424.97, 16431.82, 29904.99] # 分位数: [12.21, 5409.3, 9522.82, 12667.75, 16952.46, 29968.26]
bins: [12.21, 5165.0, 9003.0, 12424.97, 16431.82, 29904.99] bins: [12.21, 5409.3, 9522.82, 12667.75, 16952.46, 29968.26]
labels: [very_close, close, moderate, far, very_far] labels: [very_close, close, moderate, far, very_far]
dist_to_fault: dist_to_fault:
description: "距离断裂带距离" description: "距离断裂带距离"
unit: "米" unit: "米"
# 数据: [1.74, 14542.53], 均值3448.52±3406.56, 偏度1.055 # 数据: [1.72, 14685.31], 均值3527.70±3400.55
# 分位数: [1.74, 476.69, 1433.62, 3334.87, 6502.28, 14542.53] # 分位数: [1.72, 515.98, 1451.71, 3577.36, 6545.45, 14685.31]
bins: [1.74, 476.69, 1433.62, 3334.87, 6502.28, 14542.53] bins: [1.72, 515.98, 1451.71, 3577.36, 6545.45, 14685.31]
labels: [very_close, close, moderate, far, very_far] labels: [very_close, close, moderate, far, very_far]
pipe_density: pipe_density:
description: "供水管网密度" description: "供水管网密度"
unit: "m/m²" unit: "m/m²"
# 数据: [0.0, 0.07], 约80%为0.090%分位数0.01395%分位数0.023 # 数据: [0.0, 0.07], 约83.9%为0.0非零值分位数[0.000438, 0.007136, 0.015399, 0.024523, 0.065431]
# 分箱策略:0单独一类,其余3等分(分位数分箱) # 分箱策略:0单独一类,其余3等分
# 分位数(非零): [0.013, 0.023, 0.065] bins: [0.0, 0.001, 0.010, 0.025, 0.065]
bins: [0.0, 0.001, 0.013, 0.023, 0.065]
labels: [none, low, medium, high] labels: [none, low, medium, high]
+2 -2
View File
@@ -102,8 +102,8 @@ landslide:
# 大地震+近场+丘陵地形:黄土塬边 # 大地震+近场+丘陵地形:黄土塬边
- condition: {magnitude: [major, great], epicenter_distance: [very_near, near], terrain: [hill, gentle_hill]} - condition: {magnitude: [major, great], epicenter_distance: [very_near, near], terrain: [hill, gentle_hill]}
probability: 0.55 probability: 0.55
# 强震+近场+初育土(黄土) # 强震+近场+初育土(新积土/风沙土,黄土)
- condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], soil_type: [entisol]} - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], soil_type: [alluvial, aeolian]}
probability: 0.52 probability: 0.52
# === 中风险(0.20-0.40=== # === 中风险(0.20-0.40===
+5 -5
View File
@@ -147,18 +147,18 @@ node_states:
seismic_intensity: [minor, light, moderate, severe, extreme] seismic_intensity: [minor, light, moderate, severe, extreme]
# 环境层(与暴雨模型共享,状态名与 discretization.yaml 一致) # 环境层(与暴雨模型共享,状态名与 discretization.yaml 一致)
# 基于1201个样本的实际数据分布,2026-06-06更新 # 基于1365个样本的实际数据分布,2026-06-11更新
elevation: [very_low, low, medium, high, very_high] elevation: [very_low, low, medium, high, very_high]
slope: [very_low, low, medium, high, very_high] slope: [very_low, low, medium, high, very_high]
aspect: [flat, north, east, south, west] aspect: [flat, north, east, south, west]
soil_type: [ultisol, entisol, fluvo_aquic, yellow_brown] soil_type: [brown_soil, yellow_brown, cinnamon, black_lu, alluvial, aeolian, unknown]
lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated] lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated, unknown]
landuse: [forest, farmland, urban, water, barren] landuse: [forest, farmland, urban, water, barren]
terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain] terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain, unknown]
ndvi: [very_low, low, medium, high, very_high] ndvi: [very_low, low, medium, high, very_high]
sand_content: [very_low, low, medium, high, very_high] sand_content: [very_low, low, medium, high, very_high]
ph: [very_low, low, medium, high, very_high] ph: [very_low, low, medium, high, very_high]
soil_moisture: [very_low, low, medium, high] soil_moisture: [very_low, low, medium, high, very_high]
organic_carbon: [very_low, low, medium, high, very_high] organic_carbon: [very_low, low, medium, high, very_high]
dist_to_river: [very_close, close, moderate, far, very_far] dist_to_river: [very_close, close, moderate, far, very_far]
dist_to_fault: [very_close, close, moderate, far, very_far] dist_to_fault: [very_close, close, moderate, far, very_far]
+2 -2
View File
@@ -72,8 +72,8 @@ landslide:
# 城区开挖坡脚/弃土加载+暴雨:工程滑坡 # 城区开挖坡脚/弃土加载+暴雨:工程滑坡
- condition: {landuse: [urban], slope: [medium, high, very_high], rain_intensity: [heavy, storm, downpour, extreme]} - condition: {landuse: [urban], slope: [medium, high, very_high], rain_intensity: [heavy, storm, downpour, extreme]}
probability: 0.52 probability: 0.52
# 大雨+陡坡+初育土(黄土):黄土塬边滑坡 # 大雨+陡坡+初育土(新积土/风沙土,黄土):黄土塬边滑坡
- condition: {rain_intensity: [heavy, storm, downpour], slope: [high, very_high], soil_type: [entisol]} - condition: {rain_intensity: [heavy, storm, downpour], slope: [high, very_high], soil_type: [alluvial, aeolian]}
probability: 0.50 probability: 0.50
# === 中风险(0.20-0.40=== # === 中风险(0.20-0.40===
+5 -5
View File
@@ -143,19 +143,19 @@ node_states:
accum_rain: [trace, light, moderate, heavy, extreme] accum_rain: [trace, light, moderate, heavy, extreme]
# 环境层(离散字段状态名与数据库编码一一对应,连续字段用分位数分箱) # 环境层(离散字段状态名与数据库编码一一对应,连续字段用分位数分箱)
# 基于1201个样本的实际数据分布,2026-06-06更新 # 基于1365个样本的实际数据分布,2026-06-11更新
elevation: [very_low, low, medium, high, very_high] elevation: [very_low, low, medium, high, very_high]
slope: [very_low, low, medium, high, very_high] slope: [very_low, low, medium, high, very_high]
aspect: [flat, north, east, south, west] aspect: [flat, north, east, south, west]
soil_type: [ultisol, entisol, fluvo_aquic, yellow_brown] soil_type: [brown_soil, yellow_brown, cinnamon, black_lu, alluvial, aeolian, unknown]
lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated] lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated, unknown]
landuse: [forest, farmland, urban, water, barren] landuse: [forest, farmland, urban, water, barren]
terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain] terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain, unknown]
impervious: [none, very_low, low, medium, high] impervious: [none, very_low, low, medium, high]
ndvi: [very_low, low, medium, high, very_high] ndvi: [very_low, low, medium, high, very_high]
sand_content: [very_low, low, medium, high, very_high] sand_content: [very_low, low, medium, high, very_high]
ph: [very_low, low, medium, high, very_high] ph: [very_low, low, medium, high, very_high]
soil_moisture: [very_low, low, medium, high] soil_moisture: [very_low, low, medium, high, very_high]
organic_carbon: [very_low, low, medium, high, very_high] organic_carbon: [very_low, low, medium, high, very_high]
dist_to_river: [very_close, close, moderate, far, very_far] dist_to_river: [very_close, close, moderate, far, very_far]
dist_to_fault: [very_close, close, moderate, far, very_far] dist_to_fault: [very_close, close, moderate, far, very_far]
+5 -4
View File
@@ -34,9 +34,6 @@ class AppLauncher:
# 检查虚拟环境 # 检查虚拟环境
check_virtualenv(self.project_root) check_virtualenv(self.project_root)
# 检查安装依赖
check_dependencies(self.project_root)
# 检查是否正在使用虚拟环境运行 # 检查是否正在使用虚拟环境运行
import platform import platform
import sys import sys
@@ -57,12 +54,16 @@ class AppLauncher:
print("检测到未使用虚拟环境,正在切换到虚拟环境...") print("检测到未使用虚拟环境,正在切换到虚拟环境...")
print("=" * 50) print("=" * 50)
# 使用虚拟环境的Python重新启动应用 # 使用虚拟环境的Python重新启动应用(不传递参数避免重复检查)
import subprocess import subprocess
cmd = [str(venv_python)] + sys.argv cmd = [str(venv_python)] + sys.argv
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True)
return return
# 以下代码仅在虚拟环境中执行
# 检查安装依赖(只执行一次)
check_dependencies(self.project_root)
# 启动应用 # 启动应用
print("\n" + "=" * 50) print("\n" + "=" * 50)
print("✓ 所有检查通过,准备启动应用...") print("✓ 所有检查通过,准备启动应用...")
+5 -1
View File
@@ -214,11 +214,12 @@ class EarthquakeDBN:
预测结果 预测结果
""" """
point_id = point.get('id') point_id = point.get('id')
source_id = point.get('source_id')
lon = point.get('lon') lon = point.get('lon')
lat = point.get('lat') lat = point.get('lat')
source_type = point.get('source_type') source_type = point.get('source_type')
logger.debug(f"地震预测点 ID={point_id}, source_type={source_type}") logger.debug(f"地震预测点 ID={point_id}, source_id={source_id}, source_type={source_type}")
# 计算震中距(如果未直接提供) # 计算震中距(如果未直接提供)
if epicenter_distance is None: if epicenter_distance is None:
@@ -270,6 +271,7 @@ class EarthquakeDBN:
# 构造输出 # 构造输出
result = { result = {
'point_id': point_id, 'point_id': point_id,
'source_id': source_id, # 隐患点/风险点的真实ID
'source_type': source_type, 'source_type': source_type,
'lon': lon, 'lon': lon,
'lat': lat, 'lat': lat,
@@ -376,6 +378,7 @@ class EarthquakeDBN:
logger.error(f"预测点 {point.get('id')} 失败: {e}") logger.error(f"预测点 {point.get('id')} 失败: {e}")
results.append({ results.append({
'point_id': point.get('id'), 'point_id': point.get('id'),
'source_id': point.get('source_id'),
'source_type': point.get('source_type'), 'source_type': point.get('source_type'),
'lon': point.get('lon'), 'lon': point.get('lon'),
'lat': point.get('lat'), 'lat': point.get('lat'),
@@ -423,6 +426,7 @@ class EarthquakeDBN:
logger.error(f"预测点 {point.get('id')} 失败: {e}") logger.error(f"预测点 {point.get('id')} 失败: {e}")
results.append({ results.append({
'point_id': point.get('id'), 'point_id': point.get('id'),
'source_id': point.get('source_id'),
'source_type': point.get('source_type'), 'source_type': point.get('source_type'),
'lon': point.get('lon'), 'lon': point.get('lon'),
'lat': point.get('lat'), 'lat': point.get('lat'),
+5 -1
View File
@@ -230,11 +230,12 @@ class RainfallDBN:
预测结果 预测结果
""" """
point_id = point.get('id') point_id = point.get('id')
source_id = point.get('source_id')
lon = point.get('lon') lon = point.get('lon')
lat = point.get('lat') lat = point.get('lat')
source_type = point.get('source_type') source_type = point.get('source_type')
logger.debug(f"预测点 ID={point_id}, source_type={source_type}") logger.debug(f"预测点 ID={point_id}, source_id={source_id}, source_type={source_type}")
# 获取降雨数据 # 获取降雨数据
if rainfall is not None and duration is not None: if rainfall is not None and duration is not None:
@@ -287,6 +288,7 @@ class RainfallDBN:
# 构造输出 # 构造输出
result = { result = {
'point_id': point_id, 'point_id': point_id,
'source_id': source_id, # 隐患点/风险点的真实ID
'source_type': source_type, 'source_type': source_type,
'lon': lon, 'lon': lon,
'lat': lat, 'lat': lat,
@@ -369,6 +371,7 @@ class RainfallDBN:
logger.error(f"预测点 {point.get('id')} 失败: {e}") logger.error(f"预测点 {point.get('id')} 失败: {e}")
results.append({ results.append({
'point_id': point.get('id'), 'point_id': point.get('id'),
'source_id': point.get('source_id'),
'source_type': point.get('source_type'), 'source_type': point.get('source_type'),
'lon': point.get('lon'), 'lon': point.get('lon'),
'lat': point.get('lat'), 'lat': point.get('lat'),
@@ -417,6 +420,7 @@ class RainfallDBN:
logger.error(f"预测点 {point.get('id')} 失败: {e}") logger.error(f"预测点 {point.get('id')} 失败: {e}")
results.append({ results.append({
'point_id': point.get('id'), 'point_id': point.get('id'),
'source_id': point.get('source_id'),
'source_type': point.get('source_type'), 'source_type': point.get('source_type'),
'lon': point.get('lon'), 'lon': point.get('lon'),
'lat': point.get('lat'), 'lat': point.get('lat'),
+47 -50
View File
@@ -1,81 +1,78 @@
""" """
日志工具类 日志工具类
支持按天分割、自动清理过期日志 使用 loguru 提供增强的日志功能,支持按天分割、自动清理过期日志
""" """
import logging import sys
from pathlib import Path from pathlib import Path
from logging.handlers import TimedRotatingFileHandler from loguru import logger
from datetime import datetime, timedelta
class LoggerManager: class LoggerManager:
"""日志管理器""" """日志管理器 - 基于 loguru"""
_loggers = {} _configured = False
@classmethod @classmethod
def get_logger(cls, name: str = "algorithm", log_dir: str = "logs") -> logging.Logger: def get_logger(cls, name: str = "algorithm", log_dir: str = "logs"):
""" """
获取日志记录器 获取日志记录器(loguru 不需要传统意义上的 logger 实例)
Args:
name: 日志名称(用于文件命名)
log_dir: 日志目录
Returns:
loguru.logger 实例
"""
if not cls._configured:
cls._configure_logger(name, log_dir)
return logger
@classmethod
def _configure_logger(cls, name: str, log_dir: str):
"""
配置 loguru 日志处理器
Args: Args:
name: 日志名称 name: 日志名称
log_dir: 日志目录 log_dir: 日志目录
Returns:
logging.Logger 实例
""" """
if name in cls._loggers: # 移除默认的 stderr handler
return cls._loggers[name] logger.remove()
# 创建日志目录 # 创建日志目录
log_path = Path(log_dir) log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True) log_path.mkdir(parents=True, exist_ok=True)
# 创建 logger # 控制台 Handler - 彩色输出
logger = logging.getLogger(name) logger.add(
logger.setLevel(logging.DEBUG) sink=sys.stderr,
level="INFO",
# 避免重复添加 handler format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> [<cyan>{thread.name}</cyan>] <level>{level: <5}</level> <blue>{name}</blue> - <level>{message}</level>",
if logger.handlers: colorize=True
cls._loggers[name] = logger
return logger
# 日志格式
formatter = logging.Formatter(
'%(asctime)s [%(threadName)s] %(levelname)-5s %(name)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
) )
# 控制台 Handler # 文件 Handler - 按大小分割,Windows 兼容
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 文件 Handler - 按天分割
log_file = log_path / f"{name}.log" log_file = log_path / f"{name}.log"
file_handler = TimedRotatingFileHandler( logger.add(
filename=str(log_file), sink=str(log_file),
when='midnight', level="DEBUG",
interval=1, format="{time:YYYY-MM-DD HH:mm:ss} [{thread.name}] {level: <5} {name} - {message}",
backupCount=7, rotation="50 MB", # 按大小轮转,避免Windows文件锁定问题
encoding='utf-8' retention="7 days", # 保留7天
compression="zip", # 压缩旧日志文件
encoding="utf-8",
enqueue=True, # 异步写入,避免文件锁定
delay=True, # 延迟打开文件,减少锁定时间
backtrace=True, # 完整堆栈跟踪
diagnose=True # 详细错误诊断
) )
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# 设置日志文件命名格式 cls._configured = True
file_handler.suffix = "%Y-%m-%d.log"
logger.addHandler(file_handler)
cls._loggers[name] = logger
return logger
# 便捷函数 # 便捷函数
def get_logger(name: str = "algorithm", log_dir: str = "logs") -> logging.Logger: def get_logger(name: str = "algorithm", log_dir: str = "logs"):
""" """
获取日志记录器的便捷函数 获取日志记录器的便捷函数
@@ -84,6 +81,6 @@ def get_logger(name: str = "algorithm", log_dir: str = "logs") -> logging.Logger
log_dir: 日志目录 log_dir: 日志目录
Returns: Returns:
logging.Logger 实例 loguru.logger 实例
""" """
return LoggerManager.get_logger(name, log_dir) return LoggerManager.get_logger(name, log_dir)
+1
View File
@@ -8,3 +8,4 @@ Pillow == 12.2.0
pyyaml == 6.0.3 pyyaml == 6.0.3
fastapi == 0.136.3 fastapi == 0.136.3
uvicorn[standard] == 0.48.0 uvicorn[standard] == 0.48.0
loguru == 0.7.3