Files
server-core/app/park/sim_engine.py
T
Pine 81f4dfd0ad feat(park): 园区多租户 — SQLite(tenants表+companies+kb) + 每租户引擎 + 认证 + 作用域隔离
- tenants.py:park_tenants/park_companies/park_kb_docs 三表(serverdata/park/park.db),
  每园区独立 name/intro(两段式)/auth(账密 salted hash)/data(大屏锚点)/agent/kb;
  ensure_default_tenant 自愈建默认园区+灌 39 家企业。
- sim_engine 重构为 SimEngine(data)——每租户独立实例,get_engine(tid)/refresh_engine(tid)。
- auth.py:/park/auth/login(账密→长效 tenant JWT)、/park/auth/me、require_tenant(401)。
- mqtt:register/publish_command/publish_tick 按 tenant_id 作用域(拓扑为 command/tid/clientId)。
- routers:/park/tenants CRUD + 全部数据/指令端点租户作用域(authorization token 或 ?tenant_id)。
- app.py lifespan init_db + 逐租户 tick/publish;tools._query_companies 读租户库。
- .gitignore 增 serverdata/park/。TestClient:建租户/登录/快照隔离/企业CRUD/指令鉴权(无token401) 全过。
2026-08-24 18:06:51 +08:00

208 lines
11 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 -*-
"""园区数据引擎(后端)—— 真实数据 + 测算演示(每租户独立实例)。
数据锚点由租户 data 注入(可在管理端全量配置);引擎按 tenant_id 缓存独立实例,
实时 TOKEN/工具调用按该租户锚点独立测算。默认锚点为昆明市大学生创业园官方口径。
"""
import random
import threading
import time
# ---------- 默认锚点(昆明市大学生创业园 · 官方口径) ----------
COMPANIES = [
"云南派音人工智能科技有限公司", "米勒克尔蓝宝石珠宝产业化(项目)", "中泰研学合作(项目)",
"云南宸中低空经济有限公司", "昆明智海银高文化科技有限公司", "人工智能机器人大模型训练(项目)",
"云南廷秀文旅康养服务(项目)", "瀚颖AI+教育信息咨询(项目)", "云南大学AI+联合创业服务平台(项目)",
"仰光客厅(项目)", "云南上古绝学文化发展有限公司", "千楣之路(项目)",
"中越生物医疗(项目)", "中越文旅交流(项目)", "绿态环保科技项目",
"联翼航空技术低空经济应用国际化拓展(项目)", "酷享野农AI农业(项目)", "滇缅国际设计(项目)",
"昆明舒诺生物科技有限责任公司", "达岸教育管理(云南)有限公司", "昆明滇油石化有限责任公司",
"花仙子园艺肥料(云南)有限公司", "Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)",
"研X——同行者网络(项目)", "鬼才明AI创意工作室(项目)", "朵哈·玫瑰特色产业链(项目)",
"昆明云韵体育有限责任公司", "昆明舒护安养老服务有限责任公司",
"南菌优培—食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)",
"五华区丽裳文化艺术工作室", "云南星瑞航空(项目)", "综合性云南直播私域服务平台",
"昆明屿澈电子商务有限公司", "中外青少年研学(项目)", "星杭商务信息咨询昆明有限责任公司",
"蓝智科技(项目)", "云品出滇·纸享万家(项目)",
"云迹轻旅—昆明本土一站式轻量化文旅服务工作室(项目)", "启元人工智能科技(项目)",
]
ZONES = [
{"name": "OPC创业空间", "desc": "AI 技术支撑 · 数字经济/AI应用/软件开发/文创设计/电商直播等轻资产领域", "period": "拎包入驻 · 最长2年"},
{"name": "创业苗圃区", "desc": "创意阶段 · 未注册企业团队 · 开放式共享工位", "period": "最长2年"},
{"name": "孵化加速区", "desc": "已工商注册初创企业 · 独立办公空间", "period": "基础2年 · 可延1年(≤3年)"},
{"name": "国际创客区", "desc": "归国留学生 · 外籍留学生 · 国际化配套服务", "period": "参照加速区"},
]
INDUSTRY_MIX = [
{"name": "人工智能", "value": 7}, {"name": "跨境电商", "value": 5}, {"name": "教育培训", "value": 4},
{"name": "文旅康养", "value": 4}, {"name": "文化创意", "value": 4}, {"name": "生物医药", "value": 4},
{"name": "低空经济", "value": 3}, {"name": "现代农业", "value": 3}, {"name": "绿色环保", "value": 2},
{"name": "企业服务", "value": 2}, {"name": "新材料", "value": 1},
]
FEED = [
{"id": 24, "icon": "dna", "text": "园内企业「昆明舒诺生物科技」完成GEO服务体系建设(生成式引擎优化,聚焦宠物营养品牌数字化)", "time": "2026-08"},
{"id": 1, "icon": "activity", "text": "2026年7月运行情况统计表填报完成:累计投入运营资金560万元、带动就业213人", "time": "2026-07-31"},
{"id": 2, "icon": "radio", "text": "2026年第三期入驻创业企业(项目)评审结果在昆明市人社局官网公示", "time": "2026-07-02"},
{"id": 3, "icon": "graduation", "text": "云南师范大学就业创业实践基地落地园区 · 云南旅游职业学院开展就业创业交流", "time": "2026-06-03"},
{"id": 23, "icon": "radio", "text": "「PineSound」完成 ICP 备案(滇ICP备2026008517号)与公安备案,注册地址昆明市民航路301-307号", "time": "2026"},
{"id": 4, "icon": "rocket", "text": "\"滇水逐梦·雨林创航\"昆明·西双版纳创业路演PK赛成功举办", "time": "2026-05-20"},
{"id": 5, "icon": "flag", "text": "昆明市青年企业家协会创新创业基地在园区揭牌", "time": "2026-05-13"},
{"id": 6, "icon": "users", "text": "\"聚力赋能·共创未来\"高校创新创业交流暨项目路演活动圆满举行", "time": "2026-05-08"},
{"id": 7, "icon": "trophy", "text": "\"创赢未来\"2026创业大赛昆明选拔赛暨马兰花创业培训讲师大赛举行(13个项目参赛)", "time": "2026-04-22"},
{"id": 8, "icon": "broadcast", "text": "2026年第二期创业企业(项目)入驻招募公告发布(官网 www.kmyc.gov.cn", "time": "2026-04-15"},
{"id": 21, "icon": "sparkles", "text": "「PineSound」AI驱动音频管理平台上线 pinesound.cn:智能配乐与音效生成,集成100W+音效库、50W+配乐库,全球版权授权(官网)", "time": "2026"},
{"id": 9, "icon": "award", "text": "2026年第一期入驻评审结果在昆明市人社局官网公示(23个优质项目正式入驻)", "time": "2026-03-27"},
{"id": 10, "icon": "users", "text": "2026年第一期招募集中评审完成:38个申请,23个优质项目正式入驻", "time": "2026-03-24"},
{"id": 11, "icon": "cpu", "text": "云南首个人工智能OPC创新人才基地落地昆明,填补省内个体AI创业培育空白(公开报道)", "time": "2026-03-23"},
{"id": 12, "icon": "medal", "text": "第九届\"春城创业荟\"创业创新大赛圆满闭幕,获奖项目名单公布", "time": "2025-09-30"},
]
# ---------- 租户锚点默认数据(管理端可全量覆盖) ----------
DEFAULT_DATA = {
"as_of": "2026-07",
"token": {"monthly_yi": 200, "daily_wan": 66666.7, "rate": 7700, "tools_per_call_tokens": 150000},
"projects": {"inPark": 38, "capacity": 49, "invested": 560},
"jobs": {"total": 213},
"revenue": {"total": 252.15, "tax": 1.03, "today": 0},
"cumulative": {"incubated": 181, "inIncubation": 31, "graduated": 149, "patents": 171, "jobs": 1529, "revenue": 21911.14, "tax": 426.2, "mentors": 50, "successRate": 95.91},
"park": {"area": 3000, "founded": 2009, "provinceLevel": 2012, "address": "昆明市民航路229号(人力资源中心1-2楼)", "phone": "18087174891", "email": "714066050@qq.com"},
"zones": ZONES,
"companies": [{"name": n, "zone": "", "room": "", "industry": "", "bio": "", "founder": ""} for n in COMPANIES],
"industryMix": INDUSTRY_MIX,
"feed": FEED,
}
def _day_secs():
now = time.time()
lt = time.localtime(now)
day_start = time.mktime((lt.tm_year, lt.tm_mon, lt.tm_mday, 0, 0, 0, 0, 0, -1))
return max(0.0, now - day_start)
def _make_series(base, noise, n=30):
out = []
for _ in range(n - 1):
v = base + (random.random() - 0.5) * noise
out.append(round(max(1, v), 1))
out.append(round(base, 1))
return out
def _live_values(prev=None):
r = random.random
steps = (prev or {}).get("steps", 81)
steps += 1 if r() < 0.3 else 0
return {
"firstToken": round(1.3 + (r() - 0.5) * 0.2, 2),
"throughput": round(157 + (r() - 0.5) * 14),
"cacheHit": round(98 + (r() - 0.5) * 1.2, 1),
"inputM": round((prev or {}).get("inputM", 12.5) + 0.004 + r() * 0.008, 2),
"outputM": round((prev or {}).get("outputM", 2.0) + 0.001 + r() * 0.002, 2),
"rounds": max(2, steps // 40),
"steps": steps,
}
class SimEngine:
"""每租户独立快照引擎:按租户 data 锚点测算。"""
def __init__(self, data=None):
self.data = data or DEFAULT_DATA
self._lock = threading.RLock()
self._snap = self._init_snapshot()
# ---- 测算 ----
def _today_values(self, prev_noise=None):
token = self.data.get("token", {})
rate = token.get("rate", 7700)
tpc = token.get("tools_per_call_tokens", 150000)
secs = _day_secs()
today_wan = round(secs * rate / 10000)
base_tools = secs * rate / 10000 * 10000 / tpc
noise = max(-10.0, min(10.0, (prev_noise or 0.0) + (random.random() - 0.5) * 6.0))
return today_wan, max(0, round(base_tools + noise)), noise
def _init_snapshot(self):
d = self.data
token = d.get("token", {})
today_wan, today_tools, noise = self._today_values()
names = [c["name"] if isinstance(c, dict) else c for c in d.get("companies", [])]
return {
"as_of": d.get("as_of", "2026-07"),
"_toolsNoise": noise,
"live": _live_values(),
"token": {
"today": today_wan,
"total": token.get("monthly_yi", 200) * 10000 + today_wan,
"rate": token.get("rate", 7700),
"series": _make_series(token.get("daily_wan", 66666.7), token.get("daily_wan", 66666.7) * 0.16),
},
"tools": {
"today": today_tools,
"total": round(token.get("monthly_yi", 200) * 100000000 / token.get("tools_per_call_tokens", 150000)) + today_tools,
"success": 98.5,
"nodes": 12,
},
"projects": dict(d.get("projects", {})),
"jobs": dict(d.get("jobs", {})),
"revenue": dict(d.get("revenue", {})),
"cumulative": dict(d.get("cumulative", {})),
"park": dict(d.get("park", {})),
"zones": d.get("zones", ZONES),
"companies": names,
"industryMix": d.get("industryMix", INDUSTRY_MIX),
"feed": d.get("feed", FEED),
}
def snapshot(self):
with self._lock:
return dict(self._snap)
def tick(self):
with self._lock:
d = self.data.get("token", {})
today_wan, today_tools, noise = self._today_values(self._snap.get("_toolsNoise"))
self._snap.update({
"_toolsNoise": noise,
"live": _live_values(self._snap.get("live")),
"token": {
"today": today_wan,
"total": d.get("monthly_yi", 200) * 10000 + today_wan,
"rate": d.get("rate", 7700),
"series": self._snap["token"]["series"][:-1] + [today_wan],
},
"tools": {
"today": today_tools,
"total": round(d.get("monthly_yi", 200) * 100000000 / d.get("tools_per_call_tokens", 150000)) + today_tools,
"success": 98.5,
"nodes": 12,
},
})
return dict(self._snap)
# ---- 每租户引擎注册表 ----
_engines: dict[str, SimEngine] = {}
# 兼容:默认全局引擎(未带租户时回退)
sim_engine = SimEngine(DEFAULT_DATA)
def get_engine(tenant_id: str | None = None) -> SimEngine:
if not tenant_id:
return sim_engine
if tenant_id not in _engines:
from .tenants import get_tenant_data
_engines[tenant_id] = SimEngine(get_tenant_data(tenant_id))
return _engines[tenant_id]
def refresh_engine(tenant_id: str) -> SimEngine:
_engines.pop(tenant_id, None)
return get_engine(tenant_id)
def engine_data(tenant_id: str | None = None) -> dict:
return get_engine(tenant_id).data