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