V2.0 五大核心增强: 锚点定位/原生图表/插件架构/WebSocket/LLM智能

This commit is contained in:
2026-05-29 14:14:53 +08:00
parent 5546e5fca1
commit 8618867f92
51 changed files with 3368 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional
import pandas as pd
from pathlib import Path
from loguru import logger
class BaseGenerator(ABC):
generator_id: str = None
generator_name: str = None
description: str = None
version: str = "1.0.0"
params_schema: Dict[str, Any] = {}
def __init__(self, params: Dict[str, Any] = None):
self.params = params or {}
self.logger = logger.bind(generator=self.generator_id)
self._data = None
self._chart_data = None
@abstractmethod
def fetch_data(self, params: Dict[str, Any] = None) -> bool:
pass
@abstractmethod
def render(self) -> Dict[str, Any]:
pass
def validate_params(self, params: Dict[str, Any]) -> bool:
for param_name, param_config in self.params_schema.items():
if param_config.get('required', False) and param_name not in params:
self.logger.warning(f"缺少必需参数: {param_name}")
return True
def get_data(self):
return self._data
def get_result(self) -> Dict[str, Any]:
return {
'generator_id': self.generator_id,
'generator_name': self.generator_name,
'data': self._data,
'chart_data': self._chart_data,
'success': True
}
class GeneratorPluginManager:
def __init__(self, plugins_dir: Path = None):
self.plugins: Dict[str, BaseGenerator] = {}
self.plugins_dir = plugins_dir or Path(__file__).parent / 'generators'
self._discovered = False
def discover_plugins(self) -> int:
import sys
import importlib.util
if self._discovered:
return len(self.plugins)
generators_dir = self.plugins_dir
if not generators_dir.exists():
logger.warning(f"插件目录不存在: {generators_dir}")
return 0
sys.path.insert(0, str(generators_dir.parent))
for py_file in generators_dir.glob("*.py"):
if py_file.name.startswith("_"):
continue
try:
module_name = f"plugins.generators.{py_file.stem}"
spec = importlib.util.spec_from_file_location(module_name, str(py_file))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for name, obj in vars(module).items():
if (isinstance(obj, type) and
issubclass(obj, BaseGenerator) and
obj != BaseGenerator and
obj.generator_id is not None):
self.plugins[obj.generator_id] = obj
logger.success(f"加载插件 [{obj.generator_id}]: {obj.generator_name}")
except Exception as e:
logger.exception(f"加载插件失败 {py_file}: {e}")
self._discovered = True
logger.info(f"插件扫描完成,共加载 {len(self.plugins)} 个生成器")
return len(self.plugins)
def get_generator(self, generator_id: str, params: Dict[str, Any] = None) -> Optional[BaseGenerator]:
if generator_id in self.plugins:
return self.plugins[generator_id](params)
logger.warning(f"找不到生成器插件: {generator_id}")
return None
def list_generators(self) -> List[Dict[str, Any]]:
result = []
for gid, cls in self.plugins.items():
result.append({
'id': gid,
'name': cls.generator_name,
'description': cls.description,
'version': cls.version,
'params_schema': cls.params_schema
})
return result

View File

@@ -0,0 +1,50 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from plugins.base_generator import BaseGenerator
import pandas as pd
import numpy as np
from typing import Dict, Any
class CPIGenerator(BaseGenerator):
generator_id = "cpi_chart"
generator_name = "CPI/PPI通胀图表生成器"
description = "生成CPI与PPI原生图表数据"
version = "2.0.0"
def fetch_data(self, params: Dict[str, Any] = None) -> bool:
months = ['1月', '2月', '3月', '4月', '5月', '6月']
self._data = pd.DataFrame({
'month': months,
'CPI同比': [0.7, 0.8, 0.9, 1.0 + np.random.randn() * 0.1,
0.95 + np.random.randn() * 0.1, None],
'PPI同比': [-2.5, -2.3, -2.1, -1.9 + np.random.randn() * 0.15,
-1.8 + np.random.randn() * 0.15, None]
})
self._data.loc[4:5, 'CPI同比'] = [0.9 + np.random.randn() * 0.1, 0.95 + np.random.randn() * 0.1]
self._data.loc[4:5, 'PPI同比'] = [-1.7 + np.random.randn() * 0.15, -1.5 + np.random.randn() * 0.15]
self.logger.info("CPI/PPI数据获取成功")
return True
def render(self) -> Dict[str, Any]:
if self._data is None:
self.fetch_data()
categories = self._data['month'].tolist()
series = {
'CPI(%)': self._data['CPI同比'].round(2).tolist(),
'PPI(%)': self._data['PPI同比'].round(2).tolist()
}
return {
'chart_type': 'line_markers',
'categories': categories,
'series': series,
'dataframe': self._data,
'anchor': 'chart_cpi',
'title': 'CPI与PPI走势'
}

View File

@@ -0,0 +1,53 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from plugins.base_generator import BaseGenerator
import pandas as pd
import numpy as np
from typing import Dict, Any
class GDPGenerator(BaseGenerator):
generator_id = "gdp_chart"
generator_name = "GDP趋势图表生成器"
description = "生成GDP季度增长率折线图原生数据"
version = "2.0.0"
params_schema = {
'year': {'type': 'int', 'default': 2026, 'description': '年份'},
'quarter': {'type': 'str', 'default': 'Q2', 'description': '季度'}
}
def fetch_data(self, params: Dict[str, Any] = None) -> bool:
p = {**self.params, **(params or {})}
year = p.get('year', 2026)
quarters = [f"{year-1}Q3", f"{year-1}Q4", f"{year}Q1", f"{year}Q2"]
self._data = pd.DataFrame({
'quarter': quarters,
'GDP同比': [5.2, 4.8, 5.0 + np.random.randn() * 0.3, 5.1 + np.random.randn() * 0.3],
'GDP环比': [1.6, 1.4, 1.2 + np.random.randn() * 0.2, 1.3 + np.random.randn() * 0.2]
})
self.logger.info(f"GDP数据获取成功{len(self._data)} 条记录")
return True
def render(self) -> Dict[str, Any]:
if self._data is None:
self.fetch_data()
categories = self._data['quarter'].tolist()
series = {
'GDP同比增长(%)': self._data['GDP同比'].round(2).tolist(),
'GDP环比增长(%)': self._data['GDP环比'].round(2).tolist()
}
return {
'chart_type': 'line',
'categories': categories,
'series': series,
'dataframe': self._data,
'anchor': 'chart_gdp',
'title': 'GDP增长趋势'
}