Files
DPM/backend/app/sim_engine.py
T
Pine 856ff88440 feat: 拆出 FastAPI 独立后端 + MQTT 控制 + 阿里云 AI
后端(backend/,FastAPI :10085):
- 全部数据 REST 接口:dashboard 快照 / 企业分区 / 播放列表 / 媒体 / 设置
- MQTT 控制通道:opc/display/command(切页/播放/卡片/通知),
  管理端 POST /api/display/command → MQTT 广播 → 所有大屏同步响应
- 阿里云 DashScope:通义千问 LLM + 函数调用(工具经 MQTT 广播),
  paraformer-realtime-v2 语音识别 /api/ai/asr
- 媒体资源统一由后端存储返回(上传/列表/静态服务)
- SSE /api/events 保留作 MQTT 不可用时的兼容回退

前端:
- config.js + .env.local 配置后端地址与 MQTT 账号(前端 dpm / 服务端 dpmserver)
- mqtt.js 客户端 + useMqttControl(MQTT 驱动切页/媒体/卡片/通知)
- DpmOverlays 全局覆盖层(通知 toast + 企业/分区/总览卡片)
- useParkSim 改为后端 API 数据源(离线回退本地模拟)
- AiChatPanel:对话走后端 LLM(工具调用),语音走本地录音 + 后端 ASR
- MediaScreen:媒体控制走 MQTT(保留 SSE 回退),修复 useEffect TDZ

Rust:lib.rs 移除内嵌 HTTP 服务器,只保留薄壳(窗口/权限/自启/npc 隧道)
安全:backend/.env、.env.local、media、data.json 已 gitignore
2026-08-17 21:26:16 +08:00

157 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""园区数据模拟引擎 —— 由前端 parkData.js 移植,后端统一产生数据
前端通过 GET /api/dashboard/snapshot 获取,或订阅 MQTT opc/dashboard/tick
"""
import random
import threading
import time
MODEL_NAMES = ["DeepSeek-V3", "通义千问", "智谱 GLM-4", "豆包", "讯飞星火"]
TOOL_NAMES = ["文档生成", "数据查询", "图像创作", "代码执行", "语音合成"]
COMPANY_NAMES = [
"云南派音人工智能科技", "米勒克尔蓝宝石珠宝", "中泰研学合作", "云南宸中低空经济",
"昆明智海银高文化科技", "云南廷秀文旅康养", "瀚颖AI+教育信息咨询",
"仰光客厅", "云南上古绝学文化", "中越生物医疗", "酷享野农AI农业",
"滇缅国际设计", "昆明舒诺生物科技", "达岸教育管理", "花仙子园艺肥料",
"研X同行者网络", "鬼才明AI创意工作室", "朵哈·玫瑰特色产业链", "昆明云韵体育",
"南菌优培食用菌", "五华区丽裳文化", "云南星瑞航空", "综合直播私域平台",
"昆明屿澈电商", "蓝智科技", "云品出滇·纸享万家", "启元人工智能科技",
]
def _pick(arr):
return arr[random.randrange(len(arr))]
def _now_time():
return time.strftime("%H:%M:%S")
def make_series(base, growth, noise, n=30):
out = []
v = base
for _ in range(n):
v = v * (1 + growth) + (random.random() - 0.5) * noise
out.append(round(max(1, v * 100) / 100, 2))
return out
def gen_event():
r = random.random
pool = [
{"icon": "bolt", "text": f"AI 推理任务完成 · 消耗 {round(800 + r() * 9000):,} tokens{_pick(MODEL_NAMES)}"},
{"icon": "wrench", "text": f"工具「{_pick(TOOL_NAMES)}」被调用 {round(10 + r() * 90)}"},
{"icon": "building", "text": f"{_pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程"},
{"icon": "users", "text": f"{_pick(COMPANY_NAMES)}」新增招聘岗位 {round(1 + r() * 5)}"},
{"icon": "coin", "text": f"园区企业完成一笔 ¥{(0.5 + r() * 9):.1f}万 交易"},
{"icon": "robot", "text": f"{_pick(MODEL_NAMES)}」模型完成一次微调任务"},
]
e = _pick(pool)
return {"id": f"{int(time.time()*1000)}-{random.random()}", "icon": e["icon"], "text": e["text"], "time": _now_time()}
def init_feed():
return [
{"id": 1, "icon": "bolt", "text": "云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens", "time": _now_time()},
{"id": 2, "icon": "building", "text": "「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间", "time": _now_time()},
{"id": 3, "icon": "users", "text": "「昆明舒护安养老服务」新增招聘岗位 2 个", "time": _now_time()},
{"id": 4, "icon": "coin", "text": "园区企业完成一笔 ¥3.6万 交易", "time": _now_time()},
]
def init_snapshot():
return {
"t": 0,
"token": {"today": 128.64, "total": 12840, "rate": 84.6, "series": make_series(82, 0.012, 9)},
"tools": {"today": 3568, "total": 365204, "success": 98.7},
"projects": {"inPark": 158, "cum": 208, "todayNew": 2},
"jobs": {"total": 2186, "todayNew": 3},
"revenue": {"today": 38.6, "total": 20800, "growth": 8.2, "series": make_series(30, 0.006, 4)},
"park": {"devices": 98.6, "energy": 386, "people": 127, "desk": 76, "meeting": 3, "nodes": 12},
"feed": init_feed(),
}
def next_snapshot(s):
r = random.random
t_delta = round(0.26 + r() * 0.34, 2)
token = {
"today": round(s["token"]["today"] + t_delta, 2),
"total": round(s["token"]["total"] + t_delta, 2),
"rate": round(76 + r() * 20, 1),
}
tool_delta = round(13 + r() * 22)
tools = {
"today": s["tools"]["today"] + tool_delta,
"total": s["tools"]["total"] + tool_delta,
"success": round(98.1 + r() * 1.2, 1),
}
rev_delta = round(0.6 + r() * 1.7, 1)
revenue = {
"today": round(s["revenue"]["today"] + rev_delta, 1),
"total": round(s["revenue"]["total"] + rev_delta, 1),
"growth": round(7.2 + r() * 2.2, 1),
}
park = {
"devices": round(97.6 + r() * 1.6, 1),
"energy": round(320 + r() * 130),
"people": round(80 + r() * 95),
"desk": round(62 + r() * 24),
"meeting": round(2 + r() * 4),
"nodes": 12,
}
projects = dict(s["projects"])
if r() < 0.055:
projects["todayNew"] += 1
if r() < 0.035:
projects["inPark"] += 1
projects["cum"] += 1
jobs = dict(s["jobs"])
if r() < 0.08:
jobs["todayNew"] += 1
if r() < 0.05:
jobs["total"] += 1
t_series = list(s["token"]["series"])
t_series[-1] = round(t_series[-1] + t_delta, 2)
if s["t"] % 12 == 11:
t_series = t_series[1:] + [round(72 + r() * 30, 2)]
token["series"] = t_series
r_series = list(s["revenue"]["series"])
r_series[-1] = round(r_series[-1] + rev_delta, 1)
if s["t"] % 12 == 11:
r_series = r_series[1:] + [round(26 + r() * 9, 1)]
revenue["series"] = r_series
feed = [gen_event()] + s["feed"][:6] if s["t"] % 3 == 2 else s["feed"]
return {
"t": s["t"] + 1,
"token": token, "tools": tools, "revenue": revenue,
"park": park, "projects": projects, "jobs": jobs, "feed": feed,
}
class SimEngine:
"""带锁的快照引擎:单例供 API 与 MQTT tick 共用"""
def __init__(self):
self._lock = threading.RLock()
self._snap = init_snapshot()
def snapshot(self):
with self._lock:
return dict(self._snap)
def tick(self):
with self._lock:
self._snap = next_snapshot(self._snap)
return dict(self._snap)
sim_engine = SimEngine()