前后端基本架构和完全excel表的解析及统计图表的生成以及excel表的到出

This commit is contained in:
2026-03-19 01:51:34 +08:00
parent c23b93bb70
commit 2f630695ff
194 changed files with 23354 additions and 174 deletions

View File

@@ -0,0 +1,218 @@
"""
文本分析服务 - 从 AI 分析结果中提取结构化数据用于可视化
"""
import logging
from typing import Dict, Any, List, Optional
import re
import json
from app.services.llm_service import llm_service
logger = logging.getLogger(__name__)
class TextAnalysisService:
"""文本分析服务类"""
def __init__(self):
self.llm_service = llm_service
async def extract_structured_data(
self,
analysis_text: str,
original_filename: str = "",
file_type: str = "text"
) -> Dict[str, Any]:
"""
从 AI 分析结果文本中提取结构化数据
Args:
analysis_text: AI 分析结果文本
original_filename: 原始文件名
file_type: 文件类型
Returns:
Dict[str, Any]: 提取的结构化数据
"""
# 限制分析的文本长度,避免 token 超限
max_text_length = 8000
truncated_text = analysis_text[:max_text_length]
system_prompt = """你是一个专业的数据提取助手。你的任务是从AI分析结果中提取结构化数据用于生成图表。
请按照以下要求提取数据:
1. 数值型数据:
- 提取所有的数值、统计信息、百分比等
- 为每个数值创建一个条目,包含:名称、值、单位(如果有)
- 格式示例:{"name": "销售额", "value": 123456.78, "unit": ""}
2. 分类数据:
- 提取所有的类别、状态、枚举值等
- 为每个类别创建一个条目,包含:名称、值、数量(如果有)
- 格式示例:{"name": "产品类别", "value": "电子产品", "count": 25}
3. 时间序列数据:
- 提取所有的时间相关数据(年月、季度、日期等)
- 格式示例:{"name": "2025年1月", "value": 12345}
4. 对比数据:
- 提取所有的对比、排名、趋势等数据
- 格式示例:{"name": "同比增长", "value": 15.3, "unit": "%"}
5. 表格数据:
- 如果分析结果中包含表格或列表形式的数据,提取出来
- 格式:{"columns": ["列1", "列2"], "rows": [{"列1": "值1", "列2": "值2"}]}
重要规则:
- 只提取明确提到的数据和数值
- 如果某种类型的数据不存在,返回空数组 []
- 确保所有数值都是有效的数字类型
- 保持数据的原始精度
- 返回的 JSON 必须完整且格式正确
- 表格数据最多提取 20 行
请以 JSON 格式返回,不要添加任何 Markdown 标记或解释文字,只返回纯 JSON
{
"success": true,
"data": {
"numeric_data": [
{"name": string, "value": number, "unit": string|null}
],
"categorical_data": [
{"name": string, "value": string, "count": number|null}
],
"time_series_data": [
{"name": string, "value": number}
],
"comparison_data": [
{"name": string, "value": number, "unit": string|null}
],
"table_data": {
"columns": string[],
"rows": object[]
} | null
},
"metadata": {
"total_items": number,
"data_types": string[]
}
}"""
user_message = f"""请从以下 AI 分析结果中提取结构化数据:
原始文件名:{original_filename}
文件类型:{file_type}
AI 分析结果:
{truncated_text}
请按照系统提示的要求提取数据并返回纯 JSON 格式。"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
try:
logger.info(f"开始提取结构化数据,文本长度: {len(truncated_text)}")
response = await self.llm_service.chat(
messages=messages,
temperature=0.1,
max_tokens=4000
)
content = self.llm_service.extract_message_content(response)
logger.info(f"LLM 返回内容长度: {len(content)}")
# 使用简单的方法提取 JSON
result = self._extract_json_simple(content)
if not result:
logger.error("无法从 LLM 响应中提取有效的 JSON")
return {
"success": False,
"error": "AI 返回的数据格式不正确或被截断",
"raw_content": content[:500]
}
logger.info(f"成功提取结构化数据")
return result
except Exception as e:
logger.error(f"提取结构化数据失败: {str(e)}")
return {
"success": False,
"error": str(e)
}
def _extract_json_simple(self, content: str) -> Optional[Dict[str, Any]]:
"""
简化的 JSON 提取方法
Args:
content: LLM 返回的内容
Returns:
Optional[Dict[str, Any]]: 解析后的 JSON失败返回 None
"""
try:
# 方法 1: 查找 ```json 代码块
code_block_match = re.search(r'```json\n{[\s\S]*?}[\s\S]*?}\n```', content, re.DOTALL)
if code_block_match:
json_str = code_block_match.group(1)
logger.info("从代码块中提取 JSON")
return json.loads(json_str)
# 方法 2: 查找第一个完整的 { } 对象
brace_count = 0
json_start = -1
for i in range(len(content)):
if content[i] == '{':
if brace_count == 0:
json_start = i
brace_count += 1
elif content[i] == '}':
brace_count -= 1
if brace_count == 0:
# 找到了完整的 JSON 对象
json_end = i + 1
json_str = content[json_start:json_end]
logger.info(f"从大括号中提取 JSON")
return json.loads(json_str)
# 方法 3: 尝试直接解析
logger.info("尝试直接解析整个内容")
return json.loads(content)
except json.JSONDecodeError as e:
logger.error(f"JSON 解析失败: {str(e)}")
logger.error(f"原始内容(前 500 字符): {content[:500]}...")
return None
except Exception as e:
logger.error(f"提取 JSON 失败: {str(e)}")
return None
def detect_data_types(self, data: Dict[str, Any]) -> List[str]:
"""检测数据中包含的类型"""
types = []
d = data.get("data", {})
if d.get("numeric_data") and len(d["numeric_data"]) > 0:
types.append("numeric")
if d.get("categorical_data") and len(d["categorical_data"]) > 0:
types.append("categorical")
if d.get("time_series_data") and len(d["time_series_data"]) > 0:
types.append("time_series")
if d.get("comparison_data") and len(d["comparison_data"]) > 0:
types.append("comparison")
if d.get("table_data") and d["table_data"]:
types.append("table")
return types
# 全局单例
text_analysis_service = TextAnalysisService()