5752b3816d
- tenants.py 改为 async SQLAlchemy(ParkTenant/Company/KbDoc/Screen,共享 _ASYNC_ENGINE), 不再开独立 park.db、不再 init_db;建表/种子走 alembic+seed.py。 - 同步只读 accessor(list_tenants_sync/list_companies_sync/get_tenant_data_sync/get_agent_sync) 供 sim_engine 同步 tick/tools 用。 - sim_engine.get_engine 改读 get_tenant_data_sync;refresh_engine(tenant_id, data)。 - routers 全部 tenants 调用改 await,_resolve_tenant 改 async;app.py 移除 init_db; tools._query_companies 用 list_companies_sync。修复 ensure_default_tenant(T001)、Row→dict。 - 验证(migrate+seed+TestClient):tenants/companies/screens/screen/data/agent/kb/snapshot/display 全 200。
212 lines
12 KiB
Python
212 lines
12 KiB
Python
# -*- 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_sync
|
||
_engines[tenant_id] = SimEngine(get_tenant_data_sync(tenant_id))
|
||
return _engines[tenant_id]
|
||
|
||
|
||
def refresh_engine(tenant_id: str, data: dict | None = None) -> SimEngine:
|
||
"""按新数据重建该租户引擎;data 缺省时同步读库。"""
|
||
_engines.pop(tenant_id, None)
|
||
if data is not None:
|
||
_engines[tenant_id] = SimEngine(data)
|
||
return _engines[tenant_id]
|
||
return get_engine(tenant_id)
|
||
|
||
|
||
def engine_data(tenant_id: str | None = None) -> dict:
|
||
return get_engine(tenant_id).data
|