SQLite 持久连接 — sandbox 不再每次查询开关连接,改为 __init__ 时建连、close() 时释放
Explorer 的 system prompt 明确告知 sandbox 规则 — "每条 SQL 必须包含聚合函数或 LIMIT",减少 LLM 生成违规 SQL 浪费轮次 LLM 客户端单例 — 所有组件共享一个 openai.OpenAI 实例,不再各建各的 sanitize 顺序修复 — 小样本抑制放在 float round 之前,避免被 round 干扰 quick_detect 从 O(n²) 改为 O(n) — 按列聚合一次,加去重,不再对每行重复算整列统计 历史上下文实际生效 — get_context_for 的结果现在会注入到 Explorer 的初始 prompt 里,多轮分析时 LLM 能看到之前的发现
This commit is contained in:
83
README.md
83
README.md
@@ -8,20 +8,21 @@
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Agent (编排层) │
|
||||
│ 接收问题 → 调度四层 → 输出报告 │
|
||||
└──────┬──────────┬──────────┬──────────┬─────────────────┘
|
||||
│ │ │ │
|
||||
┌────▼────┐ ┌──▼─────┐ ┌─▼──────┐ ┌▼─────────┐
|
||||
│ Planner │ │Explorer│ │Insight │ │ Context │
|
||||
│ 意图规划 │ │探索循环 │ │异常检测 │ │ 上下文记忆 │
|
||||
└─────────┘ └────────┘ └────────┘ └──────────┘
|
||||
└──┬──────────┬──────────┬──────────┬──────────┬──────────┘
|
||||
│ │ │ │ │
|
||||
┌──▼───┐ ┌───▼────┐ ┌───▼───┐ ┌───▼────┐ ┌───▼─────┐
|
||||
│Planner│ │Playbook│ │Explorer│ │Insight │ │ Context │
|
||||
│意图规划│ │预设匹配 │ │探索循环 │ │异常检测 │ │上下文记忆│
|
||||
└──────┘ └────────┘ └───────┘ └────────┘ └─────────┘
|
||||
```
|
||||
|
||||
### 四层分工
|
||||
### 分层分工
|
||||
|
||||
| 层 | 组件 | 职责 |
|
||||
|---|---|---|
|
||||
| L1 | **Planner** | 理解用户意图,生成结构化分析计划(类型、维度、指标) |
|
||||
| L2 | **Explorer** | 基于计划多轮迭代探索,每轮根据上一轮结果决定下一步 |
|
||||
| L1.5 | **Playbook** | 匹配预设分析剧本,提供确定性查询保底 |
|
||||
| L2 | **Explorer** | 先执行预设查询(如有),再基于结果多轮迭代发散探索 |
|
||||
| L3 | **InsightEngine** | 从探索结果中检测异常、趋势、关联,输出主动洞察 |
|
||||
| L4 | **ContextManager** | 管理多轮对话历史,后续问题可引用之前的分析 |
|
||||
|
||||
@@ -77,34 +78,78 @@ python3 cli.py /path/to/your.db
|
||||
├── schema_extractor.py # Schema 提取器(只提取结构)
|
||||
├── sandbox_executor.py # 沙箱执行器(SQL 验证 + 结果脱敏)
|
||||
├── planner.py # [L1] 意图规划器
|
||||
├── explorer.py # [L2] 自适应探索器
|
||||
├── playbook.py # [L1.5] 预设分析剧本匹配
|
||||
├── explorer.py # [L2] 自适应探索器(预设 + 发散)
|
||||
├── insights.py # [L3] 洞察引擎(异常检测)
|
||||
├── context.py # [L4] 上下文管理器
|
||||
├── reporter.py # 报告生成器
|
||||
├── agent.py # Agent 编排层
|
||||
├── demo.py # 一键演示
|
||||
├── cli.py # 交互式 CLI
|
||||
├── playbooks/ # 预设分析剧本
|
||||
│ ├── regional_sales.json # 地区销售分析
|
||||
│ ├── category_analysis.json # 商品类别分析
|
||||
│ └── order_health.json # 订单健康度分析
|
||||
├── requirements.txt # 依赖
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 对比预制脚本
|
||||
## 预设分析剧本(Playbook)
|
||||
|
||||
| | 预制脚本 / 模板 | 本方案(四层架构) |
|
||||
|---|---|---|
|
||||
| SQL 生成 | 模板拼接 | LLM 动态生成 |
|
||||
| 查询数量 | 固定 | 1-6 轮,AI 自适应 |
|
||||
| 后续追问 | 无 | AI 看到结果后判断是否深挖 |
|
||||
| 异常发现 | 无 | 主动检测 + 主动输出 |
|
||||
| 多轮对话 | 无 | 上下文记忆,可引用历史分析 |
|
||||
| 适用场景 | 已知分析模式 | 探索性分析、开放性问题 |
|
||||
Playbook 是"确定性保底 + AI 自由发散"的结合:
|
||||
|
||||
- 为常见分析场景预定义一组确定性 SQL 查询
|
||||
- Planner 生成计划后,LLM 自动判断是否匹配某个 Playbook
|
||||
- 匹配到:先执行预设 SQL(结果可控),再让 Explorer 基于结果自由发散
|
||||
- 没匹配到:走纯自适应路径(和之前一样)
|
||||
|
||||
### 创建 Playbook
|
||||
|
||||
在 `playbooks/` 目录下创建 `.json` 文件:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "地区销售分析",
|
||||
"description": "按地区维度分析销售额、订单量、客单价",
|
||||
"tags": ["地区", "区域", "销售"],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "各地区销售总额",
|
||||
"sql": "SELECT {{region_column}} AS region, ROUND(SUM({{amount_column}}), 2) AS total FROM {{table}} GROUP BY {{region_column}} ORDER BY total DESC"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "请关注地区间的增长差异和客单价异常",
|
||||
"placeholders": {
|
||||
"table": "orders",
|
||||
"region_column": "region",
|
||||
"amount_column": "amount"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `preset_queries`: 确定性 SQL,用 `{{占位符}}` 标记可变部分
|
||||
- `placeholders`: 默认值,LLM 匹配时会根据实际 Schema 自动替换
|
||||
- `exploration_hints`: 给 Explorer 的提示,引导发散方向
|
||||
- `tags`: 帮助 LLM 判断是否匹配
|
||||
|
||||
## 对比
|
||||
|
||||
| | 预制脚本 / 模板 | 纯自适应 | 本方案(Playbook + 自适应) |
|
||||
|---|---|---|---|
|
||||
| SQL 生成 | 模板拼接 | LLM 动态生成 | 预设保底 + LLM 发散 |
|
||||
| 结果可控性 | 高 | 低 | 核心数据确定,发散部分灵活 |
|
||||
| 查询数量 | 固定 | 1-6 轮 | 预设 N 条 + 自适应 M 轮 |
|
||||
| 后续追问 | 无 | AI 自主判断 | 基于预设结果深挖 |
|
||||
| 异常发现 | 无 | 主动检测 | 预设结果 + 主动检测 |
|
||||
| 适用场景 | 已知分析模式 | 开放性问题 | 两者兼顾 |
|
||||
|
||||
## CLI 命令
|
||||
|
||||
```
|
||||
📊 > 帮我分析各地区的销售表现 # 分析问题
|
||||
📊 > 帮我分析各地区的销售表现 # 分析问题(自动匹配 Playbook)
|
||||
📊 > rounds=3 最近的趋势怎么样 # 限制探索轮数
|
||||
📊 > schema # 查看数据库 Schema
|
||||
📊 > playbooks # 查看已加载的预设剧本
|
||||
📊 > history # 查看分析历史
|
||||
📊 > audit # 查看 SQL 审计日志
|
||||
📊 > clear # 清空历史
|
||||
|
||||
182
agent.py
182
agent.py
@@ -2,138 +2,174 @@
|
||||
Agent 编排层 —— 调度四层架构完成分析
|
||||
|
||||
Layer 1: Planner 意图规划
|
||||
Layer 1.5: Playbook 预设匹配
|
||||
Layer 2: Explorer 自适应探索
|
||||
Layer 3: Insight 异常洞察
|
||||
Layer 3: Insight 异常洞察(与图表并行)
|
||||
Layer 4: Context 上下文记忆
|
||||
Output: Reporter + Chart + Consolidator
|
||||
"""
|
||||
import os
|
||||
from typing import Optional
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
from config import DB_PATH, MAX_EXPLORATION_ROUNDS
|
||||
from schema_extractor import extract_schema, schema_to_text
|
||||
from sandbox_executor import SandboxExecutor
|
||||
from planner import Planner
|
||||
from explorer import Explorer
|
||||
from insights import InsightEngine, quick_detect
|
||||
from reporter import ReportGenerator
|
||||
from context import ContextManager
|
||||
from core.config import DB_PATH, MAX_EXPLORATION_ROUNDS, PLAYBOOK_DIR, CHARTS_DIR
|
||||
from core.schema import extract_schema, schema_to_text
|
||||
from core.sandbox import SandboxExecutor
|
||||
from layers.planner import Planner
|
||||
from layers.explorer import Explorer
|
||||
from layers.insights import InsightEngine, quick_detect
|
||||
from layers.context import ContextManager
|
||||
from layers.playbook import PlaybookManager
|
||||
from output.reporter import ReportGenerator
|
||||
from output.chart import ChartGenerator
|
||||
from output.consolidator import ReportConsolidator
|
||||
|
||||
|
||||
class DataAnalysisAgent:
|
||||
"""
|
||||
数据分析 Agent
|
||||
"""数据分析 Agent —— 四层架构编排"""
|
||||
|
||||
四层架构:
|
||||
1. Planner - 理解用户意图,生成分析计划
|
||||
2. Explorer - 基于计划多轮迭代探索
|
||||
3. Insights - 从结果中检测异常、输出主动洞察
|
||||
4. Context - 管理多轮对话上下文
|
||||
|
||||
Agent 负责编排这四层,从问题到报告。
|
||||
"""
|
||||
|
||||
def __init__(self, db_path: str):
|
||||
# 数据层
|
||||
def __init__(self, db_path: str = DB_PATH):
|
||||
self.db_path = db_path
|
||||
self.schema = extract_schema(db_path)
|
||||
self.schema_text = schema_to_text(self.schema)
|
||||
self.executor = SandboxExecutor(db_path)
|
||||
|
||||
# 四层组件
|
||||
# 各层组件
|
||||
self.planner = Planner()
|
||||
self.explorer = Explorer(self.executor)
|
||||
self.insight_engine = InsightEngine()
|
||||
self.reporter = ReportGenerator()
|
||||
self.context = ContextManager()
|
||||
self.playbook_mgr = PlaybookManager(PLAYBOOK_DIR)
|
||||
self.chart_gen = ChartGenerator(output_dir=CHARTS_DIR)
|
||||
self.consolidator = ReportConsolidator()
|
||||
|
||||
# 累积图表
|
||||
self._all_charts: list[dict] = []
|
||||
|
||||
# 自动生成 Playbook
|
||||
if not self.playbook_mgr.playbooks:
|
||||
print("\n🤖 [Playbook] 未发现预设剧本,AI 自动生成中...")
|
||||
generated = self.playbook_mgr.auto_generate(self.schema_text, save_dir=PLAYBOOK_DIR)
|
||||
if generated:
|
||||
print(f" ✅ 自动生成 {len(generated)} 个剧本:")
|
||||
for pb in generated:
|
||||
print(f" 📋 {pb.name} — {pb.description}")
|
||||
else:
|
||||
print(" ⚠️ 自动生成失败,将使用纯自适应模式")
|
||||
|
||||
def analyze(self, question: str, max_rounds: Optional[int] = None) -> str:
|
||||
"""
|
||||
完整分析流程
|
||||
|
||||
Args:
|
||||
question: 用户分析问题
|
||||
max_rounds: 最大探索轮数(默认用配置值)
|
||||
|
||||
Returns:
|
||||
格式化的分析报告
|
||||
"""
|
||||
"""完整分析流程"""
|
||||
max_rounds = max_rounds or MAX_EXPLORATION_ROUNDS
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📊 {question}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# ── Layer 0: 检查上下文 ──────────────────────────
|
||||
prev_context = self.context.get_context_for(question)
|
||||
if prev_context:
|
||||
print("📎 发现历史分析上下文,将结合之前的发现")
|
||||
# Layer 0: 上下文
|
||||
prev = self.context.get_context_for(question)
|
||||
if prev:
|
||||
print("📎 发现历史上下文")
|
||||
|
||||
# ── Layer 1: 意图规划 ────────────────────────────
|
||||
# Layer 1: 意图规划
|
||||
print("\n🎯 [Layer 1] 意图规划...")
|
||||
plan = self.planner.plan(question, self.schema_text)
|
||||
|
||||
analysis_type = plan.get("analysis_type", "unknown")
|
||||
dimensions = plan.get("dimensions", [])
|
||||
# 注入历史上下文到 plan 中,让 Explorer 能看到
|
||||
if prev:
|
||||
plan["_prev_context"] = prev
|
||||
print(f" 类型: {plan.get('analysis_type', 'unknown')}")
|
||||
print(f" 维度: {', '.join(plan.get('dimensions', [])) or '自动发现'}")
|
||||
rationale = plan.get("rationale", "")
|
||||
print(f" 类型: {analysis_type}")
|
||||
print(f" 维度: {', '.join(dimensions) if dimensions else '自动发现'}")
|
||||
print(f" 理由: {rationale[:80]}{'...' if len(rationale) > 80 else ''}")
|
||||
|
||||
# ── Layer 2: 自适应探索 ──────────────────────────
|
||||
print(f"\n🔍 [Layer 2] 自适应探索 (最多 {max_rounds} 轮)...")
|
||||
steps = self.explorer.explore(plan, self.schema_text, max_rounds=max_rounds)
|
||||
# Layer 1.5: Playbook 匹配
|
||||
playbook_result = None
|
||||
if self.playbook_mgr.playbooks:
|
||||
print(f"\n📋 [Playbook] 匹配预设剧本 ({len(self.playbook_mgr.playbooks)} 个可用)...")
|
||||
playbook_result = self.playbook_mgr.match(plan, self.schema_text)
|
||||
if playbook_result:
|
||||
n = len(playbook_result.get("preset_queries", []))
|
||||
print(f" ✅ 匹配: {playbook_result['playbook_name']} ({n} 条预设查询)")
|
||||
else:
|
||||
print(" ❌ 无匹配,走纯自适应路径")
|
||||
|
||||
# Layer 2: 自适应探索
|
||||
print(f"\n🔍 [Layer 2] 自适应探索 (最多 {max_rounds} 轮)...")
|
||||
steps = self.explorer.explore(plan, self.schema_text, max_rounds=max_rounds, playbook_result=playbook_result)
|
||||
successful = sum(1 for s in steps if s.success)
|
||||
print(f"\n 完成: {len(steps)} 轮, {successful} 条成功查询")
|
||||
|
||||
# ── Layer 3: 异常洞察 ────────────────────────────
|
||||
print("\n🔎 [Layer 3] 异常洞察...")
|
||||
|
||||
# 先做规则检测
|
||||
# Layer 3 + Charts: 并行执行洞察和图表生成
|
||||
print("\n🔎 [Layer 3] 异常洞察 + 📊 图表生成(并行)...")
|
||||
rule_alerts = quick_detect(steps)
|
||||
for alert in rule_alerts:
|
||||
print(f" {alert}")
|
||||
|
||||
# 再做 LLM 深度分析
|
||||
insights = self.insight_engine.analyze(steps, question)
|
||||
insights = []
|
||||
charts = []
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
future_insights = pool.submit(self.insight_engine.analyze, steps, question)
|
||||
future_charts = pool.submit(self.chart_gen.generate, steps, question)
|
||||
|
||||
for future in as_completed([future_insights, future_charts]):
|
||||
try:
|
||||
result = future.result()
|
||||
if future == future_insights:
|
||||
insights = result
|
||||
if insights:
|
||||
print(f" 发现 {len(insights)} 条洞察")
|
||||
for insight in insights:
|
||||
print(f" {insight}")
|
||||
print(f" 🔎 发现 {len(insights)} 条洞察")
|
||||
else:
|
||||
print(" 未发现异常")
|
||||
print(" 🔎 未发现异常")
|
||||
else:
|
||||
charts = result
|
||||
if charts:
|
||||
print(f" 📊 生成 {len(charts)} 张图表:")
|
||||
for c in charts:
|
||||
print(f" 🖼️ {c['title']} → {c['path']}")
|
||||
else:
|
||||
print(" 📊 无需生成图表")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 并行任务出错: {e}")
|
||||
|
||||
# ── 生成报告 ────────────────────────────────────
|
||||
if charts:
|
||||
self._all_charts.extend(charts)
|
||||
|
||||
# 生成报告
|
||||
print("\n📝 正在生成报告...")
|
||||
report = self.reporter.generate(question, plan, steps, insights)
|
||||
report = self.reporter.generate(question, plan, steps, insights, charts=charts)
|
||||
|
||||
# 追加主动洞察
|
||||
if insights:
|
||||
insight_text = self.insight_engine.format_insights(insights)
|
||||
report += f"\n\n---\n\n{insight_text}"
|
||||
report += f"\n\n---\n\n{self.insight_engine.format_insights(insights)}"
|
||||
|
||||
# ── Layer 4: 记录上下文 ──────────────────────────
|
||||
self.context.add_session(
|
||||
question=question,
|
||||
plan=plan,
|
||||
steps=steps,
|
||||
insights=insights,
|
||||
report=report,
|
||||
)
|
||||
# Layer 4: 记录上下文
|
||||
self.context.add_session(question=question, plan=plan, steps=steps, insights=insights, report=report)
|
||||
|
||||
return report
|
||||
|
||||
def full_report(self, question: str = "") -> str:
|
||||
"""整合所有历史分析为综合报告"""
|
||||
sessions = self.context.sessions
|
||||
if not sessions:
|
||||
return "(还没有分析记录,请先执行分析)"
|
||||
print(f"\n📑 整合 {len(sessions)} 次分析为综合报告...")
|
||||
report = self.consolidator.consolidate(sessions=sessions, question=question, charts=self._all_charts or None)
|
||||
print(" ✅ 综合报告生成完成")
|
||||
return report
|
||||
|
||||
def get_schema(self) -> str:
|
||||
"""获取 Schema 文本"""
|
||||
return self.schema_text
|
||||
|
||||
def get_history(self) -> str:
|
||||
"""获取分析历史摘要"""
|
||||
return self.context.get_history_summary()
|
||||
|
||||
def get_audit(self) -> str:
|
||||
"""获取执行审计日志"""
|
||||
return self.executor.get_execution_summary()
|
||||
|
||||
def clear_history(self):
|
||||
"""清空分析历史"""
|
||||
self.context.clear()
|
||||
self._all_charts.clear()
|
||||
|
||||
def close(self):
|
||||
"""释放资源"""
|
||||
self.executor.close()
|
||||
|
||||
BIN
charts/chart_1.png
Normal file
BIN
charts/chart_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
charts/chart_2.png
Normal file
BIN
charts/chart_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
charts/chart_3.png
Normal file
BIN
charts/chart_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
BIN
charts/chart_4.png
Normal file
BIN
charts/chart_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
charts/chart_5.png
Normal file
BIN
charts/chart_5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
849
cleaned_data.csv
Normal file
849
cleaned_data.csv
Normal file
@@ -0,0 +1,849 @@
|
||||
工单号,来源,创建日期,问题类型,问题描述,处理过程,跟踪记录,严重程度,工单状态,模块,责任人,关闭日期,车型,VIN,SIM,Notes,Attachment,创建人,关闭时长(天),创建日期_解析,关闭日期_解析
|
||||
TR320,Telegram bot,02/01/2025,Remote control ,After TBOX december update remote control stopped working ,"0319:等待OTA
|
||||
0102:
|
||||
1. APP中无法正确显示汽车的状态和位置。
|
||||
2. 远控无法使用
|
||||
3. 导航无法在 GU 上运行
|
||||
0108:TSP: 内外网都无法使用,排查OTA平台升级状态","24/04: TBOX SW has been upgraded with DMC SW OTA. No issues since upgrade => TR closed
|
||||
1704: Source version for OTA not right
|
||||
03/04: there is still no connection to the server. Wait for downloading.
|
||||
march 06:waiting ota
|
||||
08/01: TSP : the apn1 &apn2 don't work
|
||||
02/01:
|
||||
1. The status and location of the car is not displayed correctly in the application.
|
||||
2. The remote startup function does not work
|
||||
3. Navigation does not work on the GU",Low,close,TBOX,Evgeniy,24/04/2025,EXEED RX(T22),LVTDD24BXRG023494,,,,Evgeniy,112.0,2025-01-02,2025-04-24
|
||||
TR342,Telegram bot,02/01/2025,Remote control ,The customer can't use remote control,"0211:长时间未等到用户反馈,默认关闭,新问题则提交新工单进行处理
|
||||
没有问题发生详细时间","Feb 11:If user feedback is not received for a long period of time, it will be closed by default, and new issues will be submitted to a new work order for processing.
|
||||
08/01 TSP has not recieved the remote command at the time of picture on 02.01 . Collect the customer’s operating scenario, operating time after reproducing the remote control problem",Low,close,local O&M,Evgeniy,11/02/2025,EXEED VX FL(M36T),LVTDD24B9RD030308,,,"image.png,image.png",Evgeniy,40.0,2025-01-02,2025-02-11
|
||||
TR343,Telegram bot,02/01/2025,PKI problem,The customer can't use navi. ,"0218 关闭问题,IHUID修改问题
|
||||
0211:专题群讨论","11/02:make a group to anlayse the reason
|
||||
07/01: please change the hu-sn on tsp. replace it using the hu-sn in the ihu-system
|
||||
02/01: After checking I found problems with PKI ",Low,close,local O&M,Evgeniy,18/02/2025,EXEED RX(T22),LVTDD24B1RG031497,,,"image.png,image.png,image.png",Evgeniy,47.0,2025-01-02,2025-02-18
|
||||
TR344,Telegram bot,02/01/2025,OTA,"There is impossible to put settings in Navi after OTA update. After customers put ""settings"" - application crashed. ",,"02/11 该问题已经SOTA修复,问题关闭
|
||||
02/06 项目定的方案走主机OTA更新,座舱的应用包已提交给主机集成,@待会后少龙更新OTA时间计划
|
||||
01/21 东软正在排查OTA系统是否修改Jar包混肴逻辑
|
||||
02/05 已更换东软提供的新jar包以及jar包的集成方式,测试验证后OTA更新",Low,close,DMC,张少龙,11/02/2025,EXEED VX FL(M36T),"LVTDD24B2RD089359
|
||||
LVTDD24B3PD625958
|
||||
LVTDD24B6RD032517
|
||||
LVTDD24B7PD635067
|
||||
LVTDD24B3PD626379
|
||||
LVTDD24B7PD635067",,"TGR0000145, TGR0000159, TGR0000205",,Evgeniy,40.0,2025-01-02,2025-02-11
|
||||
TR345,Telegram bot,02/01/2025,Remote control ,The customer can't use remote control,0319:等待OTA0108:图片显示时间,还未收到远控指令,等待收集信息,"13/05: upgrade success- close
|
||||
24/04: upgrade downloaded. Still wait for upgrade.
|
||||
1704: downloaded but not installed
|
||||
03/04: OTA upgrade downloaded. Wait for upgrading.
|
||||
08/01 TSP: TSP has not recieved the remote command at the time of picture. Collect the customer’s operating scenario, operating time after reproducing the remote control problem",Low,close,local O&M,Evgeniy,13/05/2025,EXEED RX(T22),LVTDD24B6RG020639,,,image.png,Evgeniy,131.0,2025-01-02,2025-05-13
|
||||
TR346,Telegram bot,02/01/2025,Remote control ,The customer can't use remote control,"0201:Tbox日志无法下载
|
||||
0108:唤醒失败,Tbox在1月2日无注册记录","24/04: upgrade TBOX SW OTA. No issues since the upgrade => TR closed
|
||||
08/01: wake up failed. tbox has no login record on 01.02.
|
||||
02/01: Opetation date/time: 02.01.2025 16:26. TBOX LOG from tsp can't download",Low,close,local O&M,Evgeniy,24/04/2025,EXEED RX(T22),LVTDD24B7RG033223,,,image.png,Evgeniy,112.0,2025-01-02,2025-04-24
|
||||
TR358,Telegram bot,03/01/2025,APN 2 problem,"After 1st of January the customer can't use any applications in car, but with WIFI everything ok.",,,Low,close,local O&M,Evgeniy,21/01/2025,EXEED VX FL(M36T),LVTDD24B9PD578824,,,"img_v3_02i6_fc34b5b6-46d9-4812-b442-b89b5469d6hu.jpg,img_v3_02i6_1ec7ce4b-4beb-4693-908b-ff2f75c80dhu.jpg",Evgeniy,18.0,2025-01-03,2025-01-21
|
||||
TR359,Telegram bot,03/01/2025,doesn't exist on TSP,Local produced car don't exist on TSP platrom. ,,"Feb 27:waiting infromation
|
||||
03/01: Asked autosales team to invite customers for collecting TBOX info",Low,temporary close,local O&M,Evgeniy,27/02/2025,EXEED VX FL(M36T),"XEYDD14B1RA006337,
|
||||
XEYDD14B1RA004158",,,,Evgeniy,55.0,2025-01-03,2025-02-27
|
||||
TR360,Telegram bot,03/01/2025,Navi,Navi doesn't work ,"0107:Cabin-team has fixed it , pls ask the customer to retry",03/01: Operation date 03.01 / time on the screen,Medium,close,local O&M,Evgeniy,26/02/2025,JAECOO J7(T1EJ),LVVDD21B6RC065115,,,"image.png,image.png",Evgeniy,54.0,2025-01-03,2025-02-26
|
||||
TR361,Telegram bot,06/01/2025,Navi,Navi doesn't work ,,0107:Cabin-team has fixed it ,Medium,close,生态/ecologically,何韬,08/01/2025,EXEED VX FL(M36T),LVTDD24B5RD075097,,,b5c001dd-c234-4610-a809-1169d5205350.jpeg,Evgeniy,2.0,2025-01-06,2025-01-08
|
||||
TR362,Telegram bot,06/01/2025,Navi,Navi doesn't work ,,0107:Cabin-team has fixed it ,Medium,close,生态/ecologically,何韬,10/01/2025,JAECOO J7(T1EJ),LVVDD21B4RC077618,,,,Evgeniy,4.0,2025-01-06,2025-01-10
|
||||
TR363,Telegram bot,06/01/2025,Navi,Navi doesn't work ,,0107:Cabin-team has fixed it ,Medium,close,生态/ecologically,何韬,10/01/2025,JAECOO J7(T1EJ),LVVDD21B9RC053170,,,image.png,Evgeniy,4.0,2025-01-06,2025-01-10
|
||||
TR364,Telegram bot,06/01/2025,Navi,Navi doesn't work ,,0107:Cabin-team has fixed it ,Medium,close,生态/ecologically,何韬,10/01/2025,JAECOO J7(T1EJ),LVVDB21B6RC071017,,,,,4.0,2025-01-06,2025-01-10
|
||||
TR365,Mail,06/01/2025,Network,"VK services such as music, video, Navi etc. do not work because of the internet connection issue. However, the internet traffic is available and OK in MNO. Status in Simba is also OK. Customer was checking the apps operation using the internet shared via hot spot from a smartphone and confirmed that everything worked well.",,"14/01: customer's feed-back ""Cheked it out. VK services started working"". TR closed.
|
||||
09/01 :According to TBOX LOG ,TBOX feedback device were assigned two IP address,based on the info provived by TBOX already raised a ticket to MTS for further checking.
|
||||
10/01:As per MTS feedback as below:
|
||||
The technicians have found out that your device is running two active sessions in the same APN at the same time. Two static IP addresses are assigned at once. This is a feature of the device itself, the MTS network is not related to this error.
|
||||
10/01:As per the requirements from PDC, SIMBA had reset the SIM card, pls contact the owner to retry it .
|
||||
13/01:The traffic record is normal on MNO platform .Need to ask for the latest feedback from the customer,if customer feedback have no issue on network, SIMBA recommand to close this ticket",Low,close,local O&M,,14/01/2025,JAECOO J7(T1EJ),LVVDD21B8RC053161,,ohrana737@mail.ru,"LOG_LVVDD21B8RC05316120250110051751.tar,Screenshot_error_message.jpg,Simba_status.JPG",Vsevolod,8.0,2025-01-06,2025-01-14
|
||||
TR366,Telegram bot,06/01/2025,Remote control ,"Commands are not executed in remote control app. Last apn1&2 on Jan, 6th. However, there was no more apn2 since Jan, 6th although SIMBA is OK.",,"14/01: customer's feed-back remote control started working again after updating of firmware.
|
||||
08/01: Operation time&date: Jan, 8th at 16:41 (Moscow time), error message attached. Tbox log will be provided as soon as it's available.
|
||||
|
||||
10/01:Please confirm with the car owner whether the location has MTS network coverage?The sim card state and package are all vailable.
|
||||
|
||||
13/01:The traffic record is normal on MNO platform .Need to ask for the latest feedback from the customer,if customer feedback have no issue on network, SIMBA recommand to close this ticket",Low,close,MNO,胡纯静,14/01/2025,EXEED RX(T22),LVTDD24B1RG023450,,TGR0000107,"LOG_LVTDD24B1RG023450_27_01_2025.tar,Simba_status.JPG,Picture_3.jpg",Vsevolod,8.0,2025-01-06,2025-01-14
|
||||
TR367,Telegram bot,08/01/2025,Application,"VK video app stopped working. A message ""check your network connection"" comes up. VK video app is up to date. Others apps such as VK music, weather, Navi etc. work well. Customer was trying to launch the app with an internet newtwork shared via smartphone - it does not work either. MNO and SIMBA are OK.",09/01 视频账号需重新登陆,"16/10: on the request to relogin in VK app, the issue has been fixed.",Low,close,生态/ecologically,颜廷晓,16/01/2025,EXEED VX FL(M36T),LVTDD24B5RD069476,,TGR0000131,,Vsevolod,8.0,2025-01-08,2025-01-16
|
||||
TR370,Telegram bot,10/01/2025,Network,"VK apps do not work. Message ""Check network connection"". Customer was checking with network shared from smartphone - all the apps work well. SIMBA is OK. MNO: apn2 activated but there is no apn2 available.","0311:下次例会分享进度
|
||||
0227:需要属地运维抓取日志","11/03:Sharing progress at next regular meeting
|
||||
05/03: customer was asked to log out of VK video app and re-log in. Wait for feedback if app started working.
|
||||
Feb 27:need logs
|
||||
10/01: tbox log attached
|
||||
10/01:Pls collect the latest tbox log
|
||||
13/01:The traffic record is normal on MNO platform , if customer feedback have no issue on network, SIMBA recommand to close this ticket",Medium,close,local O&M,Vsevolod,18/03/2025,JAECOO J7(T1EJ),LVVDD21B8RC054021,,TGR0000129,"LOG_LVVDD21B8RC054021_10_01_2025.tar,Picture_1.jpg,Picture_2.jpg",Vsevolod,67.0,2025-01-10,2025-03-18
|
||||
TR371,Mail,10/01/2025,Remote control ,Remote control doesn't work + bad status,,"26/02: solved
|
||||
14/01 Collect the customer’s operating scenario, operating time @Evgeniy",Low,close,local O&M,Evgeniy,26/02/2025,EXEED RX(T22),LVTDD24B6RG023539,,+79044989820 bound,"LVTDD24B6RG023539.tar,image.png,image.png",Evgeniy,47.0,2025-01-10,2025-02-26
|
||||
TR372,Telegram bot,10/01/2025,Network,No any TBOX connect since 06.01.2025,0211:无信息更新,"13.02 Solved by SW updated at dealer centre
|
||||
11/02:no anyinformation upadated
|
||||
Waiting for feedback
|
||||
01/14 anything is ok on tsp
|
||||
Collecting data from customer",Low,close,local O&M,Kostya,13/02/2025,EXEED RX(T22),LVTDD24B1RG023450,,TGR0000016,,Kostya,34.0,2025-01-10,2025-02-13
|
||||
TR374,Mail,13/01/2025,Remote control ,The customer can't use remote control since 05.01.2025,"0319:等待OTA0226:问题依旧未解决
|
||||
0114:收集客户的操作场景、重现问题的操作时间,收集客户数据","13/05: downloaded, not installed - temporary close
|
||||
1704: downloaded, not installed
|
||||
03/04: OTA upgrade downloaded. Wait for upgrading.
|
||||
Feb 26: still doesn't work
|
||||
14/01 Collect the customer’s operating scenario, operating time after reproducing the problem
|
||||
Collecting data from customer",Low,temporary close,local O&M,Evgeniy,13/05/2025,EXEED RX(T22),LVTDD24B1RG019902,,,image.png,Evgeniy,120.0,2025-01-13,2025-05-13
|
||||
TR375,Mail,13/01/2025,doesn't exist on TSP,"Vehicle is not in the TSP platform however it has VK services available in IHU. Thus, customer can't activate remote control as well as VK services",,"13/02: Vehicle had manually been imported in the TSP platform with VIN XEYDD14B1RA004189.
|
||||
20/01: the vehicle had been produced in China.
|
||||
01/14 this car doesnt exist on TSP& MES, can you confirm whether it was produced in Russian",Low,close,local O&M,,13/02/2025,EXEED VX FL(M36T),"LVTDD24B9RDB34557
|
||||
XEYDD14B1RA004189",,,"Picture_3.JPG,Picture_2.JPG,Picture_1.JPG",Vsevolod,31.0,2025-01-13,2025-02-13
|
||||
TR376,Mail,13/01/2025,Remote control ,"Remote control stopped working. A message ""Time for respond from server has expired. Please try again later"" comes up when calling an operation. TSP is OK: last login and frequency data on 13/01. MNO is also OK.",,"13/02: customer's feedback: remote control started working without any porblems. TR closed.
|
||||
14/01 Collect the customer’s operating scenario, operating time after reproducing the problem
|
||||
13/01: tbox log is attached.",Low,close,local O&M,Vsevolod,13/02/2025,EXEED RX(T22),LVTDD24B5RG023693,,Customer phone number: +79251338221,"LOG_LVTDD24B5RG023693_13_01_2025.tar,Picture_1.JPG",Vsevolod,31.0,2025-01-13,2025-02-13
|
||||
TR377,Telegram bot,13/01/2025,Activation SIM,Failed relogin HU via App QR scan,,21.01: Customer feedback - everything works now,Low,close,生态/ecologically,冯时光,21/01/2025,EXEED VX FL(M36T),LVTDD24B4PD577306,,TGR0000183,e5486d85-ef3a-4b9a-8ad9-5f02e93aff03.png,Kostya,8.0,2025-01-13,2025-01-21
|
||||
TR378,Mail,13/01/2025,Remote control ,"Remote control stopped working. There were no more apn1 available since Jan, 1st. ",0319:等待OTA0224:在41辆车计划清单中,等待用户进站升级。,"24/04: no issues => TR closed
|
||||
03/04: OTA upgrade completed successfully on March, 31th - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
05/03: wait for OTA update to be released in march.
|
||||
24/02: customer is in the list of 41 car to be upgraded with new TBOX SW at dealer.
|
||||
11/02: should we wait for the new OTA in march to be applied to fix the remote control issue? @喻起航
|
||||
16/01:The cause of the problem is network congestion. Please send the user OTA upgrade to resolve the problem。
|
||||
14/01: operation date&time - on 14/01 at 13:15, command name - engine start. Tbox log attached
|
||||
|
||||
MNO Investigation conclusion(15/01):There are no prohibitions or restrictions on the use of APN1 from MTS side.The device establishes a session through APN1 on Jan 14, however, the device does not actively use APN1, we recommend that the device side continue to check. You can see the detailed investigation results in the attachment.",Low,close,local O&M,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24B3RG016340,,Customer phone number: +79315051488,"image.png,LOG_LVTDD24B3RG016340_14_01_2025.tar,Picture_2.jpg,Picture_1.jpg",Vsevolod,101.0,2025-01-13,2025-04-24
|
||||
TR379,Telegram bot,13/01/2025,Remote control ,"The customer can't use remote control, no any data about car in app
|
||||
MNO - OK, both APNs are ok
|
||||
TSP Low frequency Data - Not since 11 jan
|
||||
TSP high frequency Data - Not since 11 jan
|
||||
Last Tbox connect - 11 jan",,"12.02 No feedback after asking -> closed
|
||||
14/01 TSP: No login tsp records on 13 &14 Jan, all remote command on 13&14 Jan fialed Because TBOX wake-up failed. real-data reported normally.
|
||||
Perhaps the customer's parking location is poor, or it could be an old issue with T22 that requires an OTA upgrade @Kostya",Low,close,local O&M,Kostya,13/02/2025,EXEED RX(T22),LVTDD24B7RG023517,,TGR0000178,,Kostya,31.0,2025-01-13,2025-02-13
|
||||
TR380,Mail,13/01/2025,Remote control ,The customer can't use remote control since 11.01.2025 + wrong status of car ,0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。,"Feb 26: problem solved
|
||||
14/01 TSP: tsp hasnot recieved remote command records on 11 Jan, all remote command 13 Jan fialed Because TBOX wake-up failed. real-data reported normally.
|
||||
Perhaps the customer's parking location is poor, or it could be an old issue with T22 that requires an OTA upgrade @Evgeniy",Low,close,local O&M,Evgeniy,26/02/2025,EXEED RX(T22),LVTDD24B4RG032014,,bound +79255388020,,Evgeniy,44.0,2025-01-13,2025-02-26
|
||||
TR381,Mail,13/01/2025,Remote control ,Vehicle data is not reflected in remote control app. MNO is OK. Last tbox login is on 13/01 as well as frequency data.,,"17/02: customer's feedback: remote control work well. No issue found => TR closed.
|
||||
13/02: customer asked if the issue is still valid. Wait for a feedback.
|
||||
20/01: what are the next steps to fix the customer's issue? MNO status - the only apn available is apn1 for the reason of the traffic used up.
|
||||
14/01 TSP: tsp has not recieved the reflected request on 13 Jan @Vsevolod
|
||||
13/01: tbox log is attached to the TR",Low,close,local O&M,Vsevolod,17/02/2025,EXEED RX(T22),LVTDD24B1RG013498,,Customer phone number: +79803429000,"LOG_LVTDD24B1RG013498_14_01_2025.tar,Picture_1.JPG",Vsevolod,35.0,2025-01-13,2025-02-17
|
||||
TR387,Mail,14/01/2025,Remote control ,The customer can't use remote control,"0506:TSP未查询1月至5月远控记录,建议用户重新尝试
|
||||
0427:等待属地与客户联系确认是否可用
|
||||
0425:等待属地与客户联系确认是否可用
|
||||
0424:等待属地与客户联系确认是否可用
|
||||
0423:平台查询日志上传为1月14,建议用户重新尝试后,如不可用远控,再提供远控操作及日志
|
||||
0421:TSP核实车控时间后,转TBOX分析日志
|
||||
0417:添加0226的截图。
|
||||
0415:等待用户反馈
|
||||
0410:等待用户反馈
|
||||
0325:继续等待
|
||||
0320:继续等待
|
||||
0318:等待最新日志
|
||||
0312:提供新抓取日志发生时间 @赵杰
|
||||
0311:转国内分析 @刘海丰
|
||||
0304;需要前方运维抓取日志
|
||||
0226:用户反馈始终无法正常运行
|
||||
0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。","13/05: customer can't use any functions. he installed another alarm security system with remote control -temporary close
|
||||
06/05:TSP did not query remote control records from January to May, users are advised to try again.@Evgeniy
|
||||
27/04:Waiting for the territory to contact the customer to confirm availability
|
||||
25/04:Waiting for the territory to contact the client to confirm availability
|
||||
24/04:Waiting for location to contact customer to confirm availability.
|
||||
23/04:The platform query log was uploaded on January 14th. It is recommended that users try again and provide remote control operations and logs if remote control is not available.
|
||||
21/04: TSP verifies car control time and then transfers to TBOX to analyse logs
|
||||
17/04: added screenshot from 26.02.
|
||||
10/04:Still Waiting.
|
||||
01/04:Still Waiting.
|
||||
25/03:Still waiting
|
||||
20/03:Still waiting
|
||||
18/03:Provide the remote control occurrence time in the newly captured logs.@Evgeniy
|
||||
12/03:Provide the remote control occurrence time in the newly captured logs.@Evgeniy
|
||||
11/03 : turn to analysis.
|
||||
04/03 :need tbox log
|
||||
26 /02: still doesn't work
|
||||
14/01 TSP: tsp waited feedback timeout Because of waking up TBox spends too much time
|
||||
14/01: operation data/time - 14.01 8:04",Low,temporary close,local O&M,Evgeniy,13/05/2025,JAECOO J7(T1EJ),LVVDD21B3RC077416,,bound 79606323647,"image.png,LVVDD21B3RC077416.tar,Screenshot_20250110_080433_com.ru.chery.od.myOmoda.jpg",Evgeniy,119.0,2025-01-14,2025-05-13
|
||||
TR388,Telegram bot,14/01/2025,Remote control ,Remote control issue - commands are not exucuted. TSP is OK as well as MNO - apn1 is OK.,"0429:等待用户进站
|
||||
0427:等待用户进站
|
||||
0425:等待用户进站
|
||||
0424:等待用户进站
|
||||
0422:等待属地确认
|
||||
0421:
|
||||
4月16日的两次空调控制都是车辆不在线,唤醒短信下发成功后,tbox无响应,35s超时
|
||||
4月17日的三次空调控制都是车辆不在线,唤醒短信下发成功后,tbox无响应,35s超时
|
||||
4月18日的空调控制成功了,4月18和4月19日和4月21日都发生了,没有下发远控发动机的指令,但是tbox上报了远控发动机执行成功的指令。建议告知用户近几次远控地,信号稳定性不佳,建议提供远控地经纬度信息供排查
|
||||
0417:等待用户反馈相关远控信息
|
||||
0410:等待用户反馈
|
||||
0407:已告知用户执行发动机启动并提供相应数据。等待反馈。
|
||||
0401:建议用户在信号好的地方,尝试重启车辆后,重试远控
|
||||
0327: TSP显示近三天TBOX有登录记录,仅查询到一条空调远控,但提示超时。
|
||||
0326:客户回来说远控仍然不能用。
|
||||
0325:再次询问客户问题是否仍然存在。等待反馈。
|
||||
0224:再次询问客户问题是否仍然存在。
|
||||
0217 计划暂时关闭此问题","29/04:waiting customer go to dealer
|
||||
27/04:waiting customer go to dealer
|
||||
25/04:waiting customer go to dealer
|
||||
24/04:waiting customer go to dealer
|
||||
22/04: Awaiting feedback on progress.
|
||||
21/04:
|
||||
On the 16th o+f April two air conditioning control are vehicle is not online, wake up text message sent successfully, tbox no response, 35s timeout
|
||||
On the 17th of April, the vehicle was not online on all three occasions of air conditioning control, and after the wake-up message was sent successfully, the tbox did not respond and the 35s timeout was exceeded.
|
||||
The air conditioning control on 18 April was successful, it happened on 18 April and 19 April and 21 April, there was no remote engine command issued, but tbox reported a successful remote engine execution. It is recommended that the user be informed of the last few remote control locations where the signal stability is poor, and it is recommended that latitude and longitude information of the remote control locations be provided for troubleshooting.
|
||||
|
||||
17/04: repeat request sent to customer to call a command and provide then the respective data.@Vsevolod Tsoi
|
||||
10/04:Still waiting
|
||||
07/04: customer was asked to execute the engine start and provide the respective data. Wait for feedback.
|
||||
01/04:Users are advised to try to retry the remote control after restarting the vehicle in a place with a good signal.@Vsevolod Tsoi
|
||||
27/03:TSP shows that TBOX has logged in records in the past three days, and only one air-conditioning remote control was queried, but it prompted timeout.
|
||||
26/03: customer is back saying that remote control still doesn't work.
|
||||
25/03: customer is asked again it the problem still takes place. Wait for feedback.
|
||||
24/02: repeat request sent to customer whether the issue is still valid.
|
||||
13/02: customer asked if the issue is still valid. Wait for a feedback.
|
||||
21/01: operation date&time - on 17/01 at 7:23; picture of an error message attached.
|
||||
14/01: customer is asked to provide the needed data for analysis",Low,temporary close,local O&M,Vsevolod,13/05/2025,CHERY TIGGO 9 (T28)),LVTDD24B3RG087859,,"TGR0000191, TGR00001013 ",file_327.jpg,Vsevolod,119.0,2025-01-14,2025-05-13
|
||||
TR392,Telegram bot,15/01/2025,Network,"11/02:still waiting
|
||||
20/01 Normal on platforms waiting for feedback from customer @Константин
|
||||
19/01 :the traffic record of this sim is normal, we recommand to close this ticket
|
||||
17/01 :The Vehicle is in hiberation mode and will not be attached to the network. As per our observed the SIM card was offline.Pls double check with the owner the vehicle status currently.thanks
|
||||
16/01 (SIMBA):pls ask customer to try it again ,network should be normal now.(this car was active at 2025-01-07 13:21:30 , at that time ,MTS network suffered DDOS attack,So the package was delayed for two days)
|
||||
The customer can't use remote control -> No any network data on MNO platform, only 2g network on HU, No Tbox Login after 2024 11 12 (december) ->
|
||||
was activated 16:22:57 09-01-2025 according MNO
|
||||
2025-01-07 13:21:30 according TSP/Simba
|
||||
PKI tbox certificate creating time 2024-11-27 08:53:43
|
||||
""Vehicle is in hiberation mode"" - Can't collerct tbox log",0211:等待客户反馈,"13.02 Solved, waited for feedback - time out - closed",Low,close,local O&M,Kostya,13/02/2025,CHERY TIGGO 9 (T28)),LVTDD24B0RG090671 sim:79863995436,79863995436,"TGR0000142
|
||||
TGR0000121
|
||||
TGR0000162
|
||||
TGR0000161",,Kostya,29.0,2025-01-15,2025-02-13
|
||||
TR395,Mail,16/01/2025,Remote control ,"Remote control issue - commands are not executed. Following the MNO investigation the status is as follow: apn2 had been desactivated on 07/01 for the reason of exeeding traffic limit but on other side we see well that apn2 continued working since this date. Last tbox login - 2025-01-16 10:11:39; last frequency data - 2025-01-16 10:11:35. Operation date&time - 13/01/2025 at 23:22 Moscow time, command - engine start, screenshot of the error message attached as well as tbox log.","0506:TSP见用户近期远控解锁已成功,建议关闭此问题
|
||||
0429:等待用户反馈
|
||||
0427:等待用户反馈
|
||||
0425:等待用户反馈
|
||||
0424:等待用户反馈有效信息
|
||||
0421:等待用户反馈
|
||||
0417:等待用户反馈信息
|
||||
0415:等待用户反馈
|
||||
0410:等待用户反馈
|
||||
0408: TSP分析无异常,TBOX登录记录正常,请提供具体远控操作及时间转TBOX分析
|
||||
0401:等待中
|
||||
0325:等待执行操作的反馈,然后提供相应的数据。
|
||||
0320:等待客户反馈操作时间及具体操作
|
||||
0318:等待取新日志@Vsevolod Tsoi
|
||||
0312:重新获取日志并提供时间点@Vsevolod
|
||||
0306 转国内分析
|
||||
0217 计划暂时关闭此问题","05/05:TSP sees that the user's recent remote unlocking has been successful, it is recommended to close this issue.@Vsevolod Tsoi
|
||||
29/04:Awaiting feedback on progress.
|
||||
27/04:Awaiting feedback on progress.
|
||||
25/04:Awaiting feedback on progress.
|
||||
24/04:Awaiting feedback on progress.
|
||||
21/04: Awaiting feedback on progress.
|
||||
17/04: customer has been asked for some details and executing of an operation with respective data.@Vsevolod Tsoi
|
||||
10/04:waiting for feedback.
|
||||
08/04:TSP analysis shows no abnormalities, TBOX login records are normal. Please provide specific remote control operations and time for TBOX analysis
|
||||
07/04: command - unlock the car at 6:08. TBOX log attached.
|
||||
01/04:Still Waiting.
|
||||
25/03: wait for feedback on the executing of an operation and providing then the respective data.
|
||||
20/03:Waiting for customer feedback on operation time and specific operation.
|
||||
19/03: customer is asked to execute an operation as well as providing the respective data for investigation. Wait for feedback.
|
||||
12/03: Retrieve logs and provide point-in-time. @Vsevolod
|
||||
12/03: Retrieve logs and provide point-in-time. @Vsevolod
|
||||
06/03: customer's feedback: the issue is still valid. Client is asked to reproduce the problem and then provied the needed input data for investigation.
|
||||
05/03: repeat request to customer to provide feedaback.
|
||||
0227 Plan to close the issue temporarily
|
||||
13/02: customer is asked to provide a feedback if the problem is still valid. Wait for a feedback.
|
||||
20/01: customer's feed-back: mobile network is OK. Customer was rebooting the network setting on our request.
|
||||
17/01 Tsp: tsp has not recieved the remote control command at 13/01/2025 at 23:22 Moscow time, pls check the network of his mobile",Low,temporary close,TBOX,Vsevolod,13/05/2025,JAECOO J7(T1EJ),LVVDD21B9RC053203,,voyt056@gmail.com,"LOG_LVVDD21B9RC053203_07_04_2025.tar,LOG_LVVDD21B9RC053203_16_01_2025.tar,Error_message.jpeg,Picture_2.JPG,Simba_status.JPG,Picture_1.JPG",Vsevolod,117.0,2025-01-16,2025-05-13
|
||||
TR396,Mail,16/01/2025,Remote control ,"Remote control issue - commands are not executed. MNO investigation: apn1&2 are available. Last tbox login - 2025-01-16 12:44:44; last frequency data - 2025-01-16 11:23:49. Operation date&time - 03/01/2025 at 14:30 Moscow time, command - engine start, screenshot of the error message and tbox log are attached.","0311:等待顾客回复
|
||||
0225:向用户询问问题是否依旧存在
|
||||
0217 计划暂时关闭此问题","20/30: customer's feedback: the problem is solved.
|
||||
19/03: customer is asked again to provide a feedback.
|
||||
25/02: repeat request sent to customer wether the issue is still valid.
|
||||
13/02: customer asked if the problem is still valid. Wait for feedback.
|
||||
23/01: Input data such as the operation time, date etc. was on 13/01 at 14:30 and not on 13/01 at 23:52 on which you provided the feed-back. Please check it again @林小辉
|
||||
017/01 Tsp: tsp has not recieved the remote control command at 13/01/2025 at 23:22 Moscow time, pls check the network of his mobile",Low,close,local O&M,Vsevolod,20/03/2025,JAECOO J7(T1EJ),LVVDB21B0RC080599,,"lelya71@inbox.ru, phone number 79276589759","LOG_LVVDB21B0RC080599_16_01_2025.tar,Picture_1.jpeg",Vsevolod,63.0,2025-01-16,2025-03-20
|
||||
TR402,Mail,17/01/2025,Remote control ,Remote control issue - commands are not executed. MNO status: apn1&2 are availabel. Last tbox login on 17/01; last frequency data on 17/01. Operation date&time 03/01/2025 at 9:28. Pictures are attached as well as tbox log from 16/01.,0227 计划暂时关闭此问题,"19/03: customer is asked again wether the issue still exists. Wait for feedback.
|
||||
05/03: repeat request to customer whether the issue is valid.
|
||||
0227 Plan to close the issue temporarily
|
||||
13/02: customer is asked for providing the info whether the issue is still valid. Wasit for feedback.
|
||||
17/01 tep waited response timeout, waking up TBox spends too much time at 03/01/2025 at 9:28
|
||||
but excuting remote control command successfully at Jan 3, 2025 17:38",Low,temporary close,local O&M,Vsevolod,27/02/2025,JAECOO J7(T1EJ),LVVDB21B1RC075802,,"zdorov_sergey@mail.ru, phone number 79081505465","LOG_LVVDB21B1RC075802_16_01_2025.tar,Picture_3.JPG,Picture_4.jpeg,Picture_1.JPG,Picture_2.JPG",Vsevolod,41.0,2025-01-17,2025-02-27
|
||||
TR403,Telegram bot,17/01/2025,Problem with auth in member center,"Issue with coming up of QR-code to login in HU when entering to personal account. An error message ""QR code is not valid"". Customer was trying with internet network shared from smartphone => same result. No data exchange is available in PKI neither tbox or DMC login.",@胡纯静 apn1& apn2 流量都无法使用,T平台显示已激活,"20/01: customer's feed-back: after firmware update issue with QR-code has been fixed.
|
||||
19/01 :the traffic record of this sim is normal, we recommand to close this ticket
|
||||
18/01 apn1& apn2 don't work",Low,close,local O&M,胡纯静,20/01/2025,EXEED RX(T22),LVTDD24B4RG013561,,TGR0000154,"file_300.jpg,file_216.jpg,file_302.jpg,file_301.jpg,file_215.jpg",Vsevolod,3.0,2025-01-17,2025-01-20
|
||||
TR405,Mail,20/01/2025,Network,"There is bad connection of network no network/3g.When connecting wifi everything is ok. Network indication. Constantly jumps from 3G to no network. At the same time the network on other devices in the same place works without problems. No errors, T-BOX and multimedia updated to the latest versions. Registration is passed. ",,"23/01: customer's feed-back - internet network started working again - problem is fixed. TR is closed.
|
||||
1/21:Please ask customer to try it again and feedback the latest status .as monitor from backend the sims two APNs are available now.",Low,close,local O&M,Evgeniy,23/01/2025,EXEED VX FL(M36T),LVTDD24B4RD062289,,,"LOG_LVTDD24B4RD06228920250120141741.tar,photo_2025-01-18_18-44-40.jpg,photo_2025-01-18_18-44-35.jpg,photo_2025-01-18_18-44-06.jpg",Evgeniy,3.0,2025-01-20,2025-01-23
|
||||
TR406,Mail,20/01/2025,Remote control ,"2/2 pls upgrade ota @Evgeniy
|
||||
23/01:软件版本为旧版本,建议点对点OTA升级解决问题
|
||||
TR406- T22-tBOX 版本18.14.01_22.39.00
|
||||
The customer can't use remote control",0319:等待OTA0217 建议属地运维进行OTA升级操作,"Apr 17: after OTA works well- solved
|
||||
03/04: OTA upgrade completed successfully on April, 1st - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
Feb 17 Recommended OTA upgrade operation by local O&M @Evgeniy
|
||||
22/01: operation time 19/01 22:26
|
||||
20/01: waiting for screenshot+operation time",Low,close,OTA,Evgeniy,17/04/2025,EXEED RX(T22),LVTDD24BXRG012480 ,,bound +79164620901,"image.png,LOG_LVTDD24BXRG01248020250120144246.tar",Evgeniy,87.0,2025-01-20,2025-04-17
|
||||
TR407,Mail,20/01/2025,Remote control ,Remote control issue - commands are not executed in the remote control app,"0305:客户未接听电话
|
||||
0225:如果问题仍然有效,将在本周内与客户联系。
|
||||
0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。","13/03: TR closed.
|
||||
05/03: customer doesn't respond on the calls.
|
||||
25/02: we will get in contact with customer within this week if the issue is still valid.
|
||||
Feb 17:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
21/01: what are the next steps to do? Another execution of an operation and then collecting the respective data for investigation?
|
||||
21/01: tsp waited feedback timeout Because of waking up TBox spends too much time 【longer than 35 second】
|
||||
20/01: operation date&time - 29/12 at 22:06, tbox log attached as well as the screeenshot of an error message",Low,close,local O&M,Vsevolod,13/03/2025,JAECOO J7(T1EJ),LVVDD21B3RC077562,,,"image.png,LOG_LVVDD21B3RC077562_20_01_2025.tar,Picture_1.jpeg",Vsevolod,52.0,2025-01-20,2025-03-13
|
||||
TR408,Telegram channel,21/01/2025,Problem with auth in member center,"Issue with log in the HU: after scannning of QR-code by customer to log in IHU, he received a notification in mobile remote control app on his smartphone that the autentification in member center has successefully been completed. However, there was any autentification in the IHU (see pictures attached). All the apps such as VK services, Navi and weather work well.",,"23/01: issue has been fixed. Cause was an additional space being present in the end of IHU ID.
|
||||
23/01:Please provide relevant logs. I initially suspect that it is caused by network issues. Can you ask the user to click the refresh button to re-obtain the QR code to see if this can solve the problem?",Medium,close,生态/ecologically,刘康男,23/01/2025,JAECOO J7(T1EJ),LVVDD21B9RC076643,,TG: @gav_n_o,"Picture_1.JPG,Picture_2.JPG",Vsevolod,2.0,2025-01-21,2025-01-23
|
||||
TR409,Mail,21/01/2025,Remote control ,Remote control issue - commands are not executed. Last tbox log in 2025-01-21 13:30:54; frequency data 2025-01-21 13:30:48; Both apn1&2 are active.,,"14/02: customer's feed-back: remote control started working. Problem is fixed.
|
||||
13/02: customer asked whether the issue still tales place. Wait for feedback.
|
||||
21/01: required data is asked for the investigation",Low,close,local O&M,Vsevolod,14/02/2025,EXEED RX(T22),LVTDD24B8RG019153,,,Picture_1.jpg,Vsevolod,24.0,2025-01-21,2025-02-14
|
||||
TR410,Mail,21/01/2025,Remote control ,Remote control issue - vehicle data is not displayed in real time in the remote control app. Last login 2025-01-21 14:31:11; frequency data 2025-01-21 14:32:53. Apn1&2 are available and operational in MNO.,0217 计划暂时关闭此问题,"17/02: following customer's feedback ""there is no longer issue with remote control"". TR closed.
|
||||
13/02: status in TSP: last high frequency data 2025-02-13 06:46:26; last tbox login 2025-02-13 06:38:40. MNO: apn1&2 are available and active. Customer asked if the issue is still valid. Wait for feedback.
|
||||
21/01: required data is asked for the investigation",Low,close,local O&M,Vsevolod,17/02/2025,EXEED RX(T22),LVTDD24B0RG009099,,,"Picture_2.jpg,Picture_1.JPG",Vsevolod,27.0,2025-01-21,2025-02-17
|
||||
TR411,Mail,21/01/2025,Remote control ,Remote control issue: vehicle data is not displayed in remote control app as well as commands are not executed. Last tbox log 2025-01-21 06:34:17; apn1 is available and active in MNO.,"0319:等待OTA
|
||||
0217 计划暂时关闭此问题
|
||||
0225:2月19日16:12启动发动机,附图,APP端显示车门打开,但车辆实际已锁定","24/04: no issue since upgrade => TR closed.
|
||||
03/04: OTA upgrade completed successfully on April, 1st - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
06/03: log upload is still in process.
|
||||
25/02: command name - engine start on 19/02 at 16:12. Picture attached. Wrong vehicle condition in the app: door is shown as opened however the car is locked.
|
||||
13/02: customer asked for the execution of remote engine start command providing then the required data for investigation. Wait for feedback.
|
||||
22/01 tsp waited feedback timeout Because of waking up TBox spends too much time pls check the tbox-log @何文国
|
||||
21/01: operation date&time - on 21/01 at 17:19 - engine start, at 17:22 - AC activation Pictures attached. tbox log is still in process",Low,close,TBOX,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24BXRG033460,,,"Picture_4.JPG,Picture_1.jpg,Picture_3.jpg,Picture_2.jpg",Vsevolod,93.0,2025-01-21,2025-04-24
|
||||
TR412,Mail,21/01/2025,Remote control ,21/01 TSP:客户反馈的时间点tbox没有登录记录 请查下TBOX日志 @何文国,"0319:等待OTA
|
||||
0217 计划暂时关闭此问题","13/05: solved- close
|
||||
24/04: upgrade successfylly completed on April, 22th. If no issues after one week, close TR.
|
||||
17/04: downloaded, but no installed
|
||||
03/04: OTA upgraded downloaded. Wait for upgrading.
|
||||
23/01:21 only 18:11 (Beijing time) there is an air conditioning remote control, the execution was successful, before did not receive remote control instructions
|
||||
21/01 no login records at that time pls check tbox-log @何文国
|
||||
21/01: customer can't use remote control + doesn't see any info about car. Operation time 21.01 12:22",Low,close,local O&M,Evgeniy,13/05/2025,EXEED RX(T22),LVTDD24B6RG019748,,bound +79179060320,"LOG_LVTDD24B6RG01974820250121161220.tar,image.png",Evgeniy,112.0,2025-01-21,2025-05-13
|
||||
TR413,Mail,21/01/2025,VK ,The customer can't enter to VK video,,"Feb 26: solved
|
||||
11/02: waiting customer's feedback
|
||||
10/02L: cus
|
||||
02/07 @Evgeniyhave done cloud processing,Now users can check whether the problem is solved and whether VK video is normal and can be used
|
||||
02/06 pls ask the owner to relogin his VK-account on iHU @Evgeniy
|
||||
collect the hu-log ",Low,close,local O&M,Evgeniy,26/02/2025,EXEED VX FL(M36T),LVTDD24B0RD065593,,bound +79883182020,image.png,Evgeniy,36.0,2025-01-21,2025-02-26
|
||||
TR414,Mail,22/01/2025,Remote control ,Remote control issue: vehicle data is not displayed in remote control app as well as commands are not executed in the remotre control app. Last tbox log 2025-01-22 07:32:46; no apn1 available since 16/01/2025.,"0319:等待OTA
|
||||
0217 计划暂时关闭此问题
|
||||
0205:请提供问题日志
|
||||
辛巴已经找运营商看过了,APN1没做任何限制,请@何文国排查TBOX日志
|
||||
0225:日志上传中","24/04: no claimes since upgrade => TR closed
|
||||
03/04: OTA upgrade completed successfully on March, 31st - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
06/03: log upload is still in process.
|
||||
10/03: recent tbox log attached.
|
||||
05/03: upload of logs is in progress.
|
||||
25/02: logs is in process of uploading.
|
||||
06/02 collect the tbox-log pls @Vsevolod
|
||||
23/01 APN1 doesn't work @胡纯静
|
||||
23/01: time&date - at 18:02 on 22/01, command name - open the trunk, screenshot of the error message attached.
|
||||
22/01: required data is requested for investigation (date&operation time etc.)",Low,close,TBOX,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24BXRG021292,,,"LOG_LVTDD24BXRG021292_10_03_2025.tar,Picture_4.jpg,Picture_3.jpg,Picture_2.jpg,Picture_1.jpg",Vsevolod,92.0,2025-01-22,2025-04-24
|
||||
TR415,Mail,22/01/2025,Remote control ,Wrong vehicle data is displayed in the remote control app. No commands are executed.,"0319:等待OTA
|
||||
0217 计划暂时关闭此问题
|
||||
0205:请提供问题日志
|
||||
辛巴已经找运营商看过了,APN1没做任何限制,请@何文国排查TBOX日志
|
||||
0225:车辆处于休眠状态达3 周。请求经销商以收集日志。","24/04: no claimes since upgrade = TR closed
|
||||
03/04: OTA upgrade completed successfully on March, 31st - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
24/03: customer's feedback: everythng works well besides Navi that is not able to detect right positon of the car - see picture attached
|
||||
13/03: logs attached and customer is also asked wehther the issue is still valid.
|
||||
25/02: vehicle is in the hibernation mode for 3 weeks. Request to send to dealer for collecting the log.
|
||||
06/02 collect the tbox-log pls @Vsevolod
|
||||
23/01 apn1 doesnt work @胡纯静
|
||||
23/01: commands name - open the door/engine start; date&time on 22/01 at 19:50. Screenshots of the error message attached.
|
||||
22/01: required data is requested for investigation (date&operation time etc.)",Low,close,TBOX,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24B9RG013653/89701010050605376664,,13/02: vehicle is still in the hibernation mode.,"Picture_4.jpg,LOG_LVTDD24B9RG013653_13_03_2025.tar,Picture_2.jpg,Picture_3.jpg,Picture_1.jpg",Vsevolod,92.0,2025-01-22,2025-04-24
|
||||
TR416,Mail,23/01/2025,Remote control ,The customer can't see status of car and can't use remote control.,"0401:TSP尝试远程抓取日志失败,提示车辆已进入深度睡眠。建议使用物理钥匙启动发动机后,等待一会再尝试远控.
|
||||
0318:等待日志
|
||||
0311:使用物理仍无法使用,取Tbox日志
|
||||
0304:建议用户使用物理钥匙进行启动发动机,再次进行远程操作尝试
|
||||
0228:TBox 进入深度睡眠模式,唤醒失败
|
||||
0217:无进展
|
||||
0211:会后更新进展
|
||||
1/24 MNO已分析流量没有限制,请TBOX分析日志","Apr 17: no feedback ->temporary close
|
||||
01/04: TSP attempts to remotely capture logs failed, indicating that the vehicle has entered deep sleep. It is recommended to use the physical key to start the engine and wait for a 15 minutes before attempting remote control.
|
||||
25/03: waiting invitation to the dealer for log
|
||||
18/03:Need to catch new logs and detail time.@Evgeniy
|
||||
Mar 11:need the tbox logs
|
||||
March 04: The user is advised to use the physical key to start the engine and try the remote operation again
|
||||
Feb 28: TBox enters deep sleep mode, fail to wake up.
|
||||
Feb 26: still has problem
|
||||
23.01: operation date 23.01 time - see video ",Low,temporary close,TBOX,Evgeniy,17/04/2025,EXEED VX FL(M36T),LVTDD24BXPD619459/89701010064499968536,,,"image.png,image.png,tboxlog.tar,IMG_7650.MP4",Evgeniy,84.0,2025-01-23,2025-04-17
|
||||
TR417,Mail,23/01/2025,Remote control ,Remote control issue - customer can't start the engine remotely via app.,"0325:等待反馈
|
||||
0320:等待反馈
|
||||
0318:等待反馈
|
||||
0311:等待经销商反馈
|
||||
0225:经销商至今没有反馈。
|
||||
0213:要求经销商写入 TboxPIN码。
|
||||
0206: 要求经销商使用诊断工具重写 TBOX 的 PIN 码,确保与 EMS PEPS的 PIN 码一致。
|
||||
密码:26506882
|
||||
0124:TPS:tsp 在 14/01 16:04 未收到遥控器,请客户尝试,收集客户的操作场景、重现问题后的操作时间
|
||||
2025 年 1 月 14 日 @ 16:12:27 启动发动机,但返回 “EMS 验证失败”。
|
||||
0123:最后一次 tbox 登录 2025-01-23 10:38:40;最后一次高频数据 2025-01-23 10:45:02;apn1&2 在 MNO 中可用。操作时间和日期 - 14/01 16:04;截图和 tbox 日志附后","25/03: still no feedback.
|
||||
20/03: still no feedback.
|
||||
18/03: still no feedback.@Vsevolod Tsoi
|
||||
05/03: still no feedback.
|
||||
25/02: no feedback from dealer so far.
|
||||
13/02: request sent to dealer for writing the pin-code for Tbox.
|
||||
11/02: still procesing
|
||||
06/02 ask the dealer to Use the diagnostic tool to rewrite the PIN code for TBOX, ensuring it matches the PIN code with EMS PEPS @Vsevolod
|
||||
pin-code:26506882
|
||||
24/01: TPS: tsp has not recieved the remote control at 16:04 on 14/01, pls ask the customer try, Collect the customer’s operating scenario, operating time after reproducing the problem @Vsevolod
|
||||
and the starting engine has been excuted at Jan 14, 2025 @ 16:12:27, but return “EMS Authentication failed ”
|
||||
23/01: last tbox login 2025-01-23 10:38:40; last high frequency data 2025-01-23 10:45:02; apn1&2 are available in MNO. Operation time&date - at 16:04 on 14/01; screenshot attached as well as tbox log",Low,temporary close,local O&M,Vsevolod,01/04/2025,EXEED VX FL(M36T),LVTDD24B0PD584060,,,"iChery20250206-162300.mp4,LOG_LVTDD24B0PD584060_23_01_2025.tar,Picture_2.jpg,Picture_1.JPG",Vsevolod,68.0,2025-01-23,2025-04-01
|
||||
TR422,Telegram bot,23/01/2025,Remote control ,"Remote control: commands are not executed via remote control app. An error message ""time for a response from the server has expired. Please try again later"" comes up when executing the command.","0422:14天未反馈,暂时关闭该问题
|
||||
0421:等待用户反馈
|
||||
0417:等待用户反馈
|
||||
0415:等待用户反馈
|
||||
0410:等待用户反馈
|
||||
0407:等待用户反馈
|
||||
0403:请提供用户远控区域的位置及经纬度,方便排查地区真实网络环境
|
||||
0403:TBOX分析,未收到唤醒短信,16:20车辆已点火唤醒,TSP见16:20TBOX登录记录,未见16:10登录记录,MNO反馈唤醒短信发送失败,短信延迟发送,后续短信功能正常,建议用户重新启动车辆后重新尝试远控。@Vsevolod Tsoi
|
||||
0401:远控时间为3月18日16:10,TSP已见该时间段有TBOX登录记录,远控发动机失败(16:12),获取车辆位置失败(16:12),已查SIM卡及流量状态正常,等待TBOX分析结果。
|
||||
0320:转Tbox分析。远控时间为3月18日16:10,日志已附,请走合规流程申请日志并分析。@王桂玲
|
||||
0318:等待用户进站抓取日志
|
||||
0311:等待用户反馈
|
||||
0304:转TSP分析,tsp 查询35s超时
|
||||
0211:tbox反馈需sim排查
|
||||
0127:远控等待TBOX响应超时,请王桂玲分析TBOX日志","22/04:No feedback for 14 days, issue temporarily closed
|
||||
17/04: no feedback so far.
|
||||
15/04: still waiting for feedback
|
||||
10/04: still waiting for feedback
|
||||
07/04: still waiting for feedback from customer on operation and their respective data.
|
||||
03/04:Please provide the location and latitude/longitude of the user's remote control area to facilitate the investigation of the real network environment in the area.@Vsevolod Tsoi
|
||||
03/04:TBOX analysis, did not receive wake-up SMS, 16:20 vehicle has ignition wake-up, TSP has seen 16:20 TBOX login record, did not see 16:10 login record, MNO feedback wake-up SMS sending failure, SMS delayed sending, subsequent SMS function is normal, suggest the user to restart the vehicle and then re-try to remote control.@Vsevolod Tsoi
|
||||
01/04: remote control time is 16:10 on 18 March, TSP has seen TBOX login record in this time period, remote control engine failed (16:12), get vehicle position failed (16:12), have checked SIM card and traffic status is normal, waiting for TBOX analysis results.
|
||||
20/03:Turn to Tbox analysis
|
||||
19/03: command name - engine start on March, 18th at 16:10. Tbox log attached.
|
||||
18/03: Waiting customer go to the dealer for checking.
|
||||
06/03: customer asked again to execute several commands in different areas of his region. Wait for feedback.
|
||||
0306:suggest customer try again
|
||||
11/02:need simba to analyse
|
||||
27/01 tsp waited feedback timeout Because of waking up TBox spends too much time.
|
||||
23/01: last tbox login 2025-01-23 16:38:23; high frequency data
|
||||
2025-01-19 16:22:29; apn1&2 are availabel and active in MNO. Operation time&date - on 23/01 at 16:37, command - engine start, screenshots attached as well as tbox log.Please provide the location and latitude/longitude of the user's remote control area to facilitate the investigation of the real network environment in the area.",Medium,temporary close,MNO,林兆国,22/04/2025,EXEED VX FL(M36T),LVTDD24B5PD638355,,,"LOG_LVTDD24B5PD638355_19_03_2025.tar,Picture_2.jpg,Permitions.JPG",Vsevolod,89.0,2025-01-23,2025-04-22
|
||||
TR423,Mail,23/01/2025,Remote control ,The customer can't use remote control + wrong status of car + bad location ,"0217 计划关闭此问题
|
||||
0205:请提供问题日志
|
||||
MNO侧和运营商网络都没有对APN1进行过任何操作。需要TBOX侧进一步排查分析。@何文国","26 Feb: solved
|
||||
06/02 collect the tbox-log pls @Evgeniy
|
||||
27/01 tsp: apn1 didnt work since 19.01 .pls check on MNO side @胡纯静",Low,close,TBOX,Evgeniy,26/02/2025,EXEED RX(T22),LVTDD24B9RG011756,,bound +79536011490,"image.png,image.png",Evgeniy,34.0,2025-01-23,2025-02-26
|
||||
TR424,Mail,24/01/2025,Remote control ,"Remote control: commands are not executed via remote control app. An error message ""Response from server was not successful. Time for response has expired"" comes up when executing the command. TSP status: last tbox log - 2025-01-24 17:02:25; high frequency data - 2025-01-24 17:02:23. Apn1&2 are available and active in MNO.","0306;等待oj反馈关闭问题
|
||||
0227:计划 28 日讨论此问题进行关闭
|
||||
0217 计划关闭此问题","06/03: feedback from O&J team: the issue is solved.
|
||||
06/03: request sent to O&J team to figure out the status.
|
||||
Feb 27:a talk with oj team in Feb 28's meeting
|
||||
17/02: a request sent to O&J app team to check. Wait for a feedaback.
|
||||
27/01 tsp hasnot recieved remote command at that time, pls ask app-team to check on app side
|
||||
24/01: operation date&time - on 23/01 at 21:21; command name - engine start, screenshot attached as well as tbox log.",Low,close,local O&M,Vsevolod,06/03/2025,JAECOO J7(T1EJ),LVVDB21B7RC070328,,,"LOG_LVVDB21B7RC070328_24_01_2025.tar,Picture_1.jpeg",Vsevolod,41.0,2025-01-24,2025-03-06
|
||||
TR425,Mail,24/01/2025,Remote control ,"Remote control: commands are not executed via remote control app. An error message ""Response from server was not successful. Time for response has expired"" comes up when executing the command. TSP status: last tbox log - 2025-01-24 17:02:15; high frequency data -
|
||||
2025-01-24 17:12:57. Apn1&2 are available and active in MNO.",0217 计划关闭此问题,"25/02: O&J app's feedback: there was no any feedback from customer more than 7 days. Issue us codsidered as closed.
|
||||
25/02: status of request in O&J - done. Wait for feedback from O&J team if the issue can be closed.
|
||||
17/02: request sent to O&J app for investigation.
|
||||
27/01 tsp hasnot recieved remote command at that time, pls ask app-team to check on app side
|
||||
24/01: operation date&time - on 23/01 at 22:41; command name - engine start, screenshot attached as well as tbox log.",Low,close,local O&M,Vsevolod,25/02/2025,JAECOO J7(T1EJ),LVVDD21BXRC054389,,,"LOG_LVVDD21BXRC054389_24_01_2025.tar,Picture_3.jpg,Picture_1.jpg,Picture_2.jpg",Vsevolod,32.0,2025-01-24,2025-02-25
|
||||
TR426,Mail,27/01/2025,Remote control ,Remote control doesn't work + wrong status of car ,"0217 计划关闭此问题
|
||||
0205:app下发车控,tsp未收到车控指令,请APP和TSP排查
|
||||
MNO侧和运营商网络都没有对APN1进行过任何操作。需要TBOX侧进一步排查分析。@何文国","Feb 26: solved
|
||||
06/02 collect the tbox-log pls @Evgeniy
|
||||
6/2 MNO:MNO and MTS side have no restriction and issue on APN1 , need device side to further check
|
||||
27/01: tsp: tsp didnt recieved the remote command at that time, and apn1 didnt work since 23.01 .pls check on MNO side @胡纯静
|
||||
27/01: operation date&time - on 27/01 at 00:09",Low,close,TBOX,林小辉,26/02/2025,EXEED RX(T22),LVTDD24B8RG019850,,bound +79131489742,"LOG_LVTDD24B8RG01985020250127114825.tar,image.png,image.png",Evgeniy,30.0,2025-01-27,2025-02-26
|
||||
TR427,Telegram channel,27/01/2025,Remote control ,"Remote control: remote engine start command is not executed via remote control app. An error message ""Operation failed. Please make a request on the issue via feed-back form"" comes up when executing the command. TSP status: last tbox log - 2025-01-27 09:15:26; high frequency data -
|
||||
2025-01-27 09:15:24. Apn1&2 are available and active in MNO.",0217 计划关闭此问题,"25/02: remote control started working. Wait for feedback from customer if the issue can be closed.
|
||||
17/02: customer asked if the issue stiil valid. If so, required data will be provided for investigation.
|
||||
27/01 pls collect the Operation time of the problem",Low,close,local O&M,Vsevolod,27/02/2025,EXEED VX FL(M36T),LVTDD24B7PD606426,,,Video_1.mp4,Vsevolod,31.0,2025-01-27,2025-02-27
|
||||
TR429,Mail,28/01/2025,Remote control ,Remote control issue: remote engine start does not work via app. Last tbox log - 2025-01-28 06:40:09; high frequency data - 2025-01-28 06:40:00; Apn1&2 are OK in MNO.,0217 计划关闭此问题,"07/04: still wait for feedback.
|
||||
25/03: repeat request to customer to execute an operation and provide then the respective data for investigation. Wait for feedback.
|
||||
19/03: client is asked for feedback whether the problem is still valid. If so, required data for investigation will be requested.
|
||||
Feb 27:still waiting customer's feedbcak
|
||||
03/02: customer is asked again to provide the needed input data. Wait for a feed-back (by email).
|
||||
2/2 TSP :Tsp has not received the command from app at that time .PLS Collect the customer’s operating scenario, operating time after reproducing the problem @Vsevolod
|
||||
28/01: command name - engine start, time&date - on 26/01 at 15:34, screenshot attached. Tbox log is in process.",Low,temporary close,local O&M,Vsevolod,27/02/2025,JAECOO J7(T1EJ),LVVDB21B0RC071563,,dasha-0215@mail.ru,"LOG_LVVDB21B0RC071563_28_01_2025.tar,Picture_1.jpeg",Vsevolod,30.0,2025-01-28,2025-02-27
|
||||
TR430,Mail,28/01/2025,Remote control ,"Remote control issue - engine is not started remotly via app => error message ""respond from the server was not successful. Time for response has expired"". TSP stauts: last tbox login 2025-01-29 10:40:05, high frequency data - 2025-01-29 10:42:37. MNO status: apn1&2 are available and active.","0422:属地会后确认
|
||||
0421:等待日会结果反馈,无异议后关闭
|
||||
0417:一致协商确定下次日会没有反馈,关闭该问题。
|
||||
0416:用户反馈在别处测试,远控生效
|
||||
0410:等待用户反馈
|
||||
0407:等待用户反馈
|
||||
0401:会后联系用户确认状态及详细位置信息
|
||||
0327: TBOX日志分析 短信延迟 9:40收到短信唤醒.建议用户在信号好的地方重启车机,并尝试远控.建议提供具体的住址信息。
|
||||
0325:会后转TBOX分析@刘海丰
|
||||
0325:客户又来询问何时能解决问题。因此,问题仍然存在。远控时间:3月25日 9:24 。新的相关日志附后。
|
||||
0226:
|
||||
经TBOX日志查询,TBOX没有收到短信唤醒
|
||||
0225:转tbox进行分析@刘海丰
|
||||
0218 转国内分析","22/04:after meeting check
|
||||
21/04: Awaiting feedback on the outcome of the day session, to be closed without objection.
|
||||
17/04:Unanimous consultation determined that there would be no feedback the following day to close the issue.@Vsevolod Tsoi
|
||||
16/04: customer's feedback: tried to execute the engine start having the car in another place => execution completed successfully.
|
||||
10/04: still wait for feedback.
|
||||
07/04: still wait for feedback.
|
||||
01/04:Contact the user after the meeting to confirm the status and detailed location information
|
||||
27/03:TBOX log analysis: SMS delayed 9:40 received SMS.Users are advised to restart the car in a place with good signal and try remote control.
|
||||
25/03: engine start was executed once again on March, 25th at 12:18. tbox log attached.
|
||||
25/03: customer is back asking when the problem is going to be solved. So, problem is still valid. Command name - engine start on March, 25th at 9:24. New respective log attached.
|
||||
18/02: command name - start of the engine on 18/02 at 10:36 (8:36 Moscow time), screenshot attached as well as tbox log.
|
||||
17/02: customer asked if the issue is still valid. If so, the required data fro investigation will be requested
|
||||
2/2 Tsp has not received the command from app at that time .PLS Collect the customer’s operating scenario, operating time after reproducing the problem @Vsevolod
|
||||
29/01: command - engine start, date&time - 28/01 at 12:20 (Moscow), screent shot of an error message attached as well as tbox log",Low,temporary close,local O&M,Vsevolod,24/04/2025,JAECOO J7(T1EJ),LVVDD21B5RC053182,,,"LOG_LVVDD21B5RC053182_25_03_2025_V2.tar,LOG_LVVDD21B5RC053182_25_03_2025.tar,LOG_LVVDD21B5RC053182_18_02_2025.tar,Picture_2.JPG,LOG_LVVDD21B5RC053182_29_01_2025.tar,Picture_1.jpg",Vsevolod,86.0,2025-01-28,2025-04-24
|
||||
TR431,Mail,29/01/2025,Problem with auth in member center,"Customer can't log in member center with QR code coming up in IHU. Error message ""Found QR code is not conform"". Customer was reseting IHU for factory settings => no result. Desactivating remote control (unbinding the vehicle) in the remote control app => no result. IHU reboot => no result. Automatic synchronization of time and date is ON. Vehicle status in the TSP: last tbox login - 2025-01-29 13:47:16; high frequency data - 2025-01-29 13:47:06. MNO status: apn1&2 are active.","0401:继续等待
|
||||
0325:等待OJ反馈
|
||||
0320:等待OJ团队反馈
|
||||
0318:等待属地运维反馈错误提示
|
||||
0311:周五跟进
|
||||
0225:O&J 反馈无法支持
|
||||
0217:同TR433 ,待属地运维修改数据后恢复正常即可关闭
|
||||
0211:等待属地app运维反馈app报错原因","08/04: remote control started working => solved.
|
||||
07/03: customer's feedback: he had wrong VIN number in his registration document that ofcourse had been used for remote control => this has been corrected. Right VIN is LVVDD21B3RC053732. So, current VIN needs to be deleted in the mobile app and then new one should be bound. Wait for feedback.
|
||||
27/03:Still waiting.
|
||||
25/03: stiill wait for the feedback on error code from O&J team.
|
||||
20/03:Waiting for feedback from O&J on error code.
|
||||
1803:Waiting for feedback from local O&M on error code.
|
||||
0304:Discussion at Friday's meeting
|
||||
25/02: feedabck from O&J team: they cannot support in the issue from their side.
|
||||
11/02:Waiting for feedback from territorial app ops on why the app is reporting errors
|
||||
06/02: current status - sim card acitivated, all apps work well. The only issue is that the customer tries to to enter to the member center with QR code coming up when opening the personal account in IHU. When scanning QR code an error message comes up ""Found QR code is not conform"". But actually log in had already been done.
|
||||
06/02 CABIN: pls ask O&J-app-team to check the err-message on the first picture.
|
||||
2/2 TSP: APN1& APN2 work well, and the car was bound on tsp. pls check the reason ",Low,close,O&J-APP,Vadim,08/04/2025,JAECOO J7(T1EJ),LVVDD21B2RC052622 => right one LVVDD21B3RC053732,,,"IMG_5032.MP4,Picture_3.JPG,Picture_1.jpeg,Picture_2.jpeg",Vsevolod,69.0,2025-01-29,2025-04-08
|
||||
TR432,Mail,30/01/2025,Remote control ,Remote control issue - engine is not started remotly via remote app. TSP status: last tbox login - 2025-01-24 09:27:48; frequency data - 2025-01-30 11:14:53. MNO: apn1 - OK.,0319:等待OTA0217 计划关闭此问题,"24/04: no issues since the upgrade => TR closed
|
||||
03/04: OTA upgrade completed successfully on April, 2nd - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
06/03: no apn1 available since Feb, 4th. Would ti mean that this car needs to be updated with new OTA in march ?
|
||||
06/02 collect the tbox-log pls @Vsevolod
|
||||
5/2 MNO:MNO and MTS side have no restriction and issue on APN1 , need device side to further check
|
||||
2/2 tsp: the APN1 didnt work, pls check on MNO side @胡纯静
|
||||
30/01: command name - engine start, date&time - 30/01 at 7:05 Khabarovsk time (14:05 Moscow). Screenshot attached. tbox log upload is in process.",Low,close,MNO,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24B3RG019934,,,Picture_1.jpg,Vsevolod,84.0,2025-01-30,2025-04-24
|
||||
TR433,Telegram bot,03/02/2025,Remote control ,"Customer can't log in member center with QR code - ""Bad connection"" error - QR is not loading.
|
||||
Tried with Wi-Fi - result the same
|
||||
In PKI platform IHU check ""The user does not exist""","0227:该问题已经修复,等待用户反馈
|
||||
0218:等待属地运维询问问题是否关闭
|
||||
0217:同TR436,计划关闭此问题
|
||||
0211:
|
||||
运维反馈该问题仍然存在","March11. Solved -> closed after customer feedback
|
||||
feb 27:need local om confrim this issue exist or not
|
||||
11/02:this issue still exist
|
||||
PLS add info which data we should collect",Low,close,PKI,Kostya,11/03/2025,JAECOO J7(T1EJ),LVVDB21B1RC071488,,,"image.png,image.png",Kostya,36.0,2025-02-03,2025-03-11
|
||||
TR434,Mail,04/02/2025,Remote control ,"Engine start command is not executed remotly via app. Error message ""Response from server was not successful. Time for response has expired"". Vehicle status in the TSP: last tbox login
|
||||
2025-02-04 13:23:56, high frequenct data
|
||||
2025-02-04 13:26:27. MNO: apn1&2 are available and operational.","0227:等待用户反馈
|
||||
0217:计划关闭此问题
|
||||
0211:联系用户确认该问题是否仍然存在,不存在就关闭问题
|
||||
07/02 经过日志分析,TBOX没有接到短信 请转平台端排查
|
||||
UTC时间,换算成KM时间,最后一条短信接收时间是2月3日早上8点53分37秒,这之后就没收到过信息","March 6: Omoda team asked us to close the problem
|
||||
Feb 27:waiting feedbcak
|
||||
17/02: customer asked if the issue is still valid. If so, reproducing of a problem will be requested with rpoviding then all the respective input data.
|
||||
11/02: Contact the user to confirm that the issue still exists and close the issue if it doesn't
|
||||
06/02 TSP: waking up TBox spends too much time,pls check the tbox-log @王浩博
|
||||
04/02: operation time&date - 03/02/2025 at 9:43, command - engine start, screenshot/tbox log attached.",Low,close,local O&M,Vsevolod,06/03/2025,JAECOO J7(T1EJ),LVVDD21B2RC052717,,,"LOG_LVVDD21B2RC052717_04_02_2025.tar,Picture_1.PNG",Vsevolod,30.0,2025-02-04,2025-03-06
|
||||
TR435,Telegram bot,04/02/2025,Remote control ,"Remote control issue: none of the commands is not executed via remote control app. Car status in TSP: last tbox login 2025-02-04 11:12:37, last high frequency data 2025-02-04 11:23:31. MNO status: both apn1&2 are available and active.",0319:等待OTA0217:询问属地运维情况,"03/04: upgrade complete on March, 24th. No any issues since update.
|
||||
0217: Inquiries about territorial O&M
|
||||
06/02 tsp: Tsp has received the command from app at 17:44 Russia time but failed, tsp waited tbox feedback timeout , waking up TBox spends too much time
|
||||
but no stating engine command at 16:45 (Moscow time)
|
||||
06/02 command - engine start, time&date on 05/02 at 16:45 (Moscow time). Screenshots attached.",Low,close,local O&M,Vsevolod,03/04/2025,EXEED RX(T22),LVTDD24B7RG032153,,TGR0000480,"image.png,Picture_4.jpg,Picture_3.jpg",Vsevolod,58.0,2025-02-04,2025-04-03
|
||||
TR436,Telegram bot,07/02/2025,VK ,"Customer can't use VK video on HU. According to customer everything else works fine.
|
||||
No any abnormal data on platforms","0214: 个别历史遗留数据受影响,IHUID需属地运维手动修改,后续版本不会出现此问题
|
||||
0211:专题群讨论","0218: Solved
|
||||
0214: Individual historical legacy data is affected, IHUID needs to be manually modified by local O&M, this issue will not occur in subsequent versions.
|
||||
0211: Thematic group discussion
|
||||
07/02 VK license issue in VK video App
|
||||
Short HU sn Pki -> Long HU sn",Low,close,PKI,喻起航,18/02/2025,EXEED RX(T22),LVTDD24B8RG019864,,,image.png,Kostya,11.0,2025-02-07,2025-02-18
|
||||
TR437,Mail,12/02/2025,Remote control ,The customer can't use remote control,"0318: 等待顾客反馈
|
||||
0312:最近一次远控当地时间2025-03-09 10:24:26.956,显示远控成功
|
||||
0311:顾客反馈仍然无法使用,重新抓取日志
|
||||
0227:等待用户反馈该问题是否仍然存在,若恢复正常,则关闭该问题
|
||||
0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。
|
||||
0213: 当地时间11:11对应UTC时间8:11,日志上看11:11TBOX处于Sleep状态,TBOX没有收到短信唤醒,日志上也没有收到短信记录,与TSP失去连接,因此收不到车控指令。13分27秒才被上电唤醒; 没收到短信唤醒,转平台处理
|
||||
0213:转tbox进行分析 ","March 12: The most recent remote control was successful on 2025-03-09 10:24:26.956 local time. @Evgeniy
|
||||
Feb 27: ask customer to try again about remote control this function,if ok we can close this issue
|
||||
Feb 17:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
Feb 14:
|
||||
0213: Local time 11:11 corresponds to UTC time 8:11, logs show that TBOX is in Sleep state at 11:11, TBOX did not receive SMS wakeup, logs also did not receive SMS records, and TSP lost connection, so it could not receive car control commands. 13 minutes 27 seconds before being woken up by the power supply; did not receive the SMS wakeup, transferred to the platform to handle
|
||||
0213: transfer tbox for analysis
|
||||
12/02 : Operation time: 11/02/2025 at 11:11",Low,temporary close,local O&M,Evgeniy,18/03/2025,JAECOO J7(T1EJ),LVVDD21B9RC053184,,bound 79272197355,"img_v3_02jf_5b7f937f-49f8-43fc-8b8c-b4d4d1c0b3ag.jpg,LOG_LVVDD21B9RC05318420250212131105.tar,image.png",Evgeniy,34.0,2025-02-12,2025-03-18
|
||||
TR438,Mail,13/02/2025,Remote control ,"After remote start of the engine, after switching on the seat heating, the keyless entry settings are reset, and if I switch on the mirror heating, the projection is switched off.","0318:等待抓取日志
|
||||
0227:取得日志
|
||||
0213:需要日志进行分析,请提供DMC和tbox日志","25/03: temporary close
|
||||
18/03:Need to catch new logs and detail time.@Evgeniy
|
||||
Feb 27:get log
|
||||
0213:Need logs for analysis, please provide DMC and tbox logs ",Low,temporary close,TBOX,Evgeniy,25/03/2025,EXEED VX FL(M36T),LVTDD24B6RD030427,,,,Evgeniy,40.0,2025-02-13,2025-03-25
|
||||
TR439,Telegram bot,14/02/2025,Remote control ,The customer can't use remote control/doesn't see status of car.,"0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。
|
||||
0214:转开发分析TSP平台日志中,车辆当时处于不在线状态,TSP平台发送唤醒短信成功,tbox未成功上线,超时35S,转tbox分析 ","Feb 26: Solved
|
||||
Feb 17:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
0214: turn development analysis TSP platform logs, the vehicle was in the state of not online, the TSP platform to send wake-up SMS successful, tbox unsuccessful online, timeout 35S, turn tbox analysis
|
||||
14/02: Operation date/time 13.02 at 19:37",Low,close,TBOX,Evgeniy,26/02/2025,CHERY TIGGO 9 (T28)),LVTDD24B3RG087103,,bound 79197687091,image.png,Evgeniy,12.0,2025-02-14,2025-02-26
|
||||
TR443,Telegram bot,10/02/2025,Remote control ,"Remote control issue: wrong data is displayed, no commans are executed in app. Vehicle status in TSP: last tbox login 2025-02-10 06:19:34, high frequency data - 2025-02-10 06:51:53. Status in MNO: apn1&2 are available and active.","0515:暂时关闭该问题
|
||||
0515:平台查询到用户近日多条远控记录,可关闭该问题
|
||||
0424:已下载升级,但尚未应用。等待升级。
|
||||
0304:等待用户反馈
|
||||
0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。
|
||||
0214:转开发分析TSP平台日志中,车辆当时处于不在线状态,TSP平台发送唤醒短信成功,tbox未成功上线,超时35S,转tbox分析","15/05:Suggest temporarily shutting it down. If the user finds that there are still issues, restart the problem
|
||||
15/05:The platform has found multiple remote control records of the user in recent days, and this issue can be closed.
|
||||
24/04: upgrade downloaded but not applied yet. Wait for upgrade.
|
||||
March 4:need customer feedback
|
||||
Feb 17:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
Feb 14: turn development analysis TSP platform logs, the vehicle was in the state of not online, the TSP platform to send wake-up SMS successful, tbox unsuccessful online, timeout 35S, turn tbox analysis
|
||||
10/02: customer opened the mobile app on 10/02 at 11:02, vehicle engine is shown as started however vehicle is parked and actually the engine is stopped. Then the customer tried to stop the engine clicking on button ""stop"", some time has passed and then there was an error message ""Time for the response from server has expired. Please try again later"". ",Medium,temporary close,TBOX,Vsevolod,15/05/2025,EXEED RX(T22),XEYDD14B3RA002651,,TGR0000509,"Picture_3.jpg,Picture_2.jpg",Vsevolod,94.0,2025-02-10,2025-05-15
|
||||
TR447,Mail,14/02/2025,Remote control ,"The application concluded. indicates that the driver's door is open and the engine is running, the mileage is old. the commands don't work, the update doesn't help. do something about it. I can't warm up the car, it all started about two weeks ago.",0319:等待OTA0217:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。,"13/05: upgrade success-problem solved
|
||||
24/04: still wait for upgrade.
|
||||
17/04: downloaded but not installed
|
||||
03/04: downloaded
|
||||
17/04:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
14/02: Operation date 14.02 - operation time-see screenshots",Low,close,车控APP(Car control),Evgeniy,13/05/2025,EXEED RX(T22),LVTDD24B3RG031453,,,"image.png,image.png,image.png",Evgeniy,88.0,2025-02-14,2025-05-13
|
||||
TR448,Telegram bot,14/02/2025,Application,"Customer can't use VK video app. An error message comes up ""License activation was not successful"" - see screenshot attached. Others apps work well.","0227:问题解决 ,等待用户反馈。
|
||||
0224:该问题已经修复,等待属地运维咨询用户是否问题解决,进而进行问题关闭。
|
||||
0221:
|
||||
1.问题原因:
|
||||
由于VK Video的新版本更新,用户从旧版本通过应用市场升级到新版本导致了在雄狮的存储中有两个相同的应用ID从而用户无法使用软件
|
||||
2.临时解决方案:
|
||||
由雄狮老师对该用户的车辆应用ID修改,后续我们这边与雄狮老师确定更好的方案,目前VK也针对此问题做出对应的修改,即使在用户无网络状态下只要激活过,下次也可以校验通过减少出现激活失败的情况。
|
||||
3.遇到此类问题的排查方向:
|
||||
VK在激活失败页面新增了报错提示,通过用户的提示可以更加直观分析出问题的发生情况,比如激活校验失败,PKI失败等等
|
||||
4.后续避免方案:
|
||||
后续VK优化激活逻辑版本应用迭代可以减少此类问题发生,其次正在与雄狮的老师商讨出现问题时自动上报日志的可行性,敲定后此类问题即使出现也是小概率时间,出现后解决问题的方向也会更加明确
|
||||
0217:转信源分析@张鹏飞","0227:issue fixed ,waiting customer feedback
|
||||
0221.
|
||||
1:
|
||||
Due to the new version update of VK Video, users upgrading from the old version to the new version through the app market have two identical app IDs in Lion's storage and thus users are unable to use the software.
|
||||
2.Temporary solution:
|
||||
By the Lion teacher to the user's vehicle application ID modification, followed by our side and the Lion teacher to determine a better solution, the current VK also for this problem to make the corresponding changes, even if the user no network state as long as the activation of the next time can be verified through the activation to reduce the failure of the situation.
|
||||
3. The direction of troubleshooting this kind of problem:
|
||||
VK has added an error message on the activation failure page, which can be used to analyze more intuitively the occurrence of the problem, such as activation verification failure, PKI failure and so on.
|
||||
4. Subsequent avoidance program:
|
||||
Subsequent VK optimization activation logic version of the application iteration can reduce the occurrence of such problems, and secondly, we are discussing with Lion's teachers the feasibility of automatically reporting logs when a problem occurs, after finalizing such problems even if they occur is a small probability of time, after the emergence of a solution to the problem will also be more clear in the direction of the problem.
|
||||
0217:Transfer source analysis Pengfei Zhang",Low,close,生态/ecologically,张鹏飞,06/03/2025,CHERY TIGGO 9 (T28)),LVTDD24BXRG087843,,TGR0000593,Picture_1.jpg,Vsevolod,20.0,2025-02-14,2025-03-06
|
||||
TR451,Mail,17/02/2025,Remote control ,"Remote control doesn't work. An error message comes up ""command is not executed. Please try again on the engine started "" when tying to execute the command activation air conditionning. TSP status: last tbox login 2025-02-09 11:26:08; high frequency data on 2025-02-09 11:03:56. MNO: no apn1 working since Jan, 9th.","0218:
|
||||
1.转辛巴分析中,流量使用正常,
|
||||
2.转TSP分析,车辆于2月15日处于深度睡眠模式,建议用户钥匙启动车辆后再次尝试远控","24/02: customer's feedback: remote control started working again. The issue is solved.
|
||||
18/02: after getting in contact with customer at 15:38 vehicle was running and using. No apn1 available leads to no executing of a remote command. Customer still can't use remote control.
|
||||
Feb 18:
|
||||
1. In the trans-Simba analysis, the flow rate is used normally, the
|
||||
2. to TSP analysis, the vehicle was in deep sleep mode on February 15, suggesting that the user key start the vehicle and then try remote control again
|
||||
17/02: command name - air conditionning on 17/02 at 13:14 (12:14 Moscow time). Screenshot attahed.",Low,close,TBOX,Vsevolod,24/02/2025,EXEED RX(T22),LVTDD24B7RG031441,89701010050664705050,,Picture_1.jpg,Vsevolod,7.0,2025-02-17,2025-02-24
|
||||
TR452,Mail,17/02/2025,Remote control ,Remote control doesn't work + wrong status of car.,"0529:等待更新
|
||||
0520:等待刷新
|
||||
0515:等待用户进站换件
|
||||
0513: 所有远控操作仍然失败。需要更换tbox。已要求ASD团队启动tbox更换程序
|
||||
0429:会上建议用户进站,解除深度睡眠,如所有操作都无法解决,建议换件
|
||||
0427:等待属地与客户确认
|
||||
0424:等待属地与客户确认
|
||||
0421:TBOX侧反馈是否换件可以解决问题,明日日会,确认问题状态,是无法解除深度睡眠,或其他情况
|
||||
0417:建议用户进站换件
|
||||
0415:等待用户反馈是否可用
|
||||
0410:等待用户反馈状态
|
||||
0407:继续等待用户反馈
|
||||
0401:TSP尝试远程抓取日志失败,提示车辆已进入深度睡眠。建议使用物理钥匙启动发动机后,等待一会再尝试远控.
|
||||
0325:尝试使用物理密钥启动 -> 结果相同
|
||||
0325:会后检查
|
||||
0320:本周车辆未启动,建议用户使用物理钥匙启动车辆,并重新尝试远控
|
||||
0318:会后检查远控
|
||||
0227:未取得日志转入分析
|
||||
0218:转TSP分析,车辆于2月15日处于深度睡眠模式,建议用户钥匙启动车辆后再次尝试远控","05/06: tbox was changed, wating for result
|
||||
15/05:Waiting change tbox
|
||||
13/05 all operation still failed. need to change tbox. already asked ASD team to launch procedure of changing tbox
|
||||
29/04:At the meeting, the user was advised to come in and release the deep sleep, and if all operations failed to solve the problem, it was recommended to change the TBOX.
|
||||
27/04: Waiting for confirmation between the locality and the customer
|
||||
24/04: Waiting for confirmation between the locality and the customer
|
||||
21/04:TBOX side feedback on whether changing parts can solve the problem, tomorrow day meeting, to confirm the status of the problem, is not able to lift the deep sleep, or other circumstances.
|
||||
17/04: asked ASD team invite the customer for replace TBOX
|
||||
15/04: Waiting for user feedback on availability
|
||||
10/04: Waiting for user feedback status
|
||||
07/04: Continue to wait for user feedback
|
||||
01/04:TSP attempts to remotely capture logs failed, indicating that the vehicle has entered deep sleep. It is recommended to use the physical key to start the engine and wait for a 15 minutes before attempting remote control.
|
||||
25/03:after meeting check
|
||||
25/03: tried to start with physical key -> result is the same
|
||||
20/03:The vehicle did not start this week and the user is advised to start the vehicle with the physical key and retry the remote control.
|
||||
Mar 18/03: Post-conference inspection of remote control.@赵杰
|
||||
27/02:get log and waiting analysis
|
||||
18/02: The customer has already started car and nothing happend-> the car still stay in deep sleep mode
|
||||
18/02: Turning to TSP analysis, the vehicle was in deep sleep mode on February 15, suggesting that the user key start the vehicle and try remote control again. @Evgeniy
|
||||
17/02: Operation date :15.02 at 12:42",Low,close,车控APP(Car control),Evgeniy,05/06/2025,EXEED VX FL(M36T),LVTDD24B2PD384362,,,"image.png,image.png,image.png",Evgeniy,108.0,2025-02-17,2025-06-05
|
||||
TR453,Mail,17/02/2025,Remote control ,Remote control doesn't work ,0319:等待OTA0218:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。,"Apr17: solved after OTA
|
||||
03/04: OTA upgrade completed successfully on April, 1st - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
Feb 18:After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again. @Evgeniy
|
||||
17:02: 14.02 at 22:38",Low,close,车控APP(Car control),Evgeniy,17/04/2025,EXEED RX(T22),LVTDD24B0RG031393,,,image.png,Evgeniy,59.0,2025-02-17,2025-04-17
|
||||
TR454,Mail,17/02/2025,doesn't exist on TSP,TE1J with IOV but doesn't exist on TSP platform ,"0226:无异常数据,该问题关闭
|
||||
0220:需运维筛查数据,如无历史数据则关闭;
|
||||
0218:
|
||||
1.转pdc分析,该车辆存在于TSP 正式环境,但是无TBOX信息,
|
||||
2.详细TBOX信息见Note
|
||||
3.具体车辆缺失信息原因排查中,涉及车辆数量排查中","0218:
|
||||
1. to pdc analysis, the vehicle exists in the TSP official environment, but no TBOX information, the
|
||||
2.Detailed TBOX information see Note
|
||||
3. The reasons for missing information on specific vehicles are being investigated, and the number of vehicles involved is being investigated.",Low,close,TSP,喻起航,26/02/2025,JAECOO J7(T1EJ),LVVDB21B8RC107189,,"SN:FCOT1EEE527M069#
|
||||
ICCID:89701010050664784857","Телематика Фев 15 2025 (002).jpg,Телематика Скриншот Фев 15 2025.jpg",Evgeniy,9.0,2025-02-17,2025-02-26
|
||||
TR455,Telegram bot,18/02/2025,Remote control ,"Remote control issue: vehicle data is not displayed in the app, commands are not executed. Vehicle status in the TSP: last tbox login 2025-02-15 07:28:43, high frequency data 2025-02-10 07:24:38. Status in the MNO: no apn1 available and active since Feb, 10th.","0605:问题暂时关闭,待抓到日志或问题再次出现开启
|
||||
0603: 建议问题关闭,5月15日已经提出日志收集请求,超过20天
|
||||
0529:等待更新
|
||||
0520:等待更新
|
||||
0519:已超过两周未回复,暂时关闭
|
||||
0513:协商一致,暂时关闭
|
||||
0429:等待用户进站
|
||||
0427:等待用户进站
|
||||
0425:等待用户进站
|
||||
0424:等待用户进站
|
||||
0422:等待属地运维确定
|
||||
0421:等待进度反馈
|
||||
0417:建议客户进站取TBOX日志
|
||||
0410:客户使用实体钥匙启动发动机,等待了 20 分钟,但车辆没有从休眠模式中醒来 - 请参见附图。在手机应用程序中无法执行命令。这是否意味着需要更换 TBOX ECU?最后一次 TBOX 登录 2025-02-15 07:28:43,高频数据 2025-02-10 07:24:38。
|
||||
0408:等待用户反馈
|
||||
0402:问题重新启用,TSP检查车辆已进入深度睡眠,建议用户使用物理钥匙进入车辆并启动车辆,重新尝试远控,如远控异常,请记录异常操作、异常时间点,及截图方便后续排查
|
||||
0328:客户依然遇到远控问题,远控指令失效。
|
||||
0319:等待顾客反馈已超过两周,如依然无反馈,建议暂时关闭
|
||||
0318: 等待用户反馈
|
||||
0311:等待顾客反馈
|
||||
0220:经后台查询,因用户车辆当时处于信号不好的地点,车辆未收到远控指令,后续查看功能可正常使用,建议用户再观察使用,若还有问题,建议再次反馈且提供相关日志。
|
||||
Feb 19:转国内TSP工程师分析","0605:Issue temporarily closed, to be caught in the log or the issue reappears open
|
||||
05/06: wait for feedback.
|
||||
03/06: still wait for
|
||||
29/05: still wait for tbox from dealer.
|
||||
19/05: no close possible as request to collect TBOX log has only been sent on May, 15th.
|
||||
0519: More than two weeks without reply, temporarily closed
|
||||
15/05: request for getting TBOX log has been sent. Wait for feed-back from dealer.
|
||||
29/04:waiting customer go to dealer
|
||||
27/04:waiting customer go to dealer
|
||||
25/04:waiting customer go to dealer
|
||||
24/04:waiting customer go to dealer
|
||||
22/04: Awaiting feedback on progress.
|
||||
21/04: Awaiting feedback on progress.
|
||||
17/04:Suggest customer go to dealer for tbox logs.@Vsevolod Tsoi
|
||||
10/04: customer started the engine wiht physical key, waited for 20 minutes but vehicle did not come out of hibernation mode - see picture attached. No commans possible to execute in the mobile app. Does it mean TBOX ECU needs to be replaced? Last tbox login 2025-02-15 07:28:43, high frequency data 2025-02-10 07:24:38.
|
||||
April 07: Waiting for customer feedback
|
||||
April 02:The problem is re-enabled, the TSP checks that the vehicle has entered deep sleep, and suggests that the user use the physical key to enter the vehicle and start the vehicle, and retry the remote control, such as the remote control is abnormal, please record the abnormal operation, the abnormal time point, and the screenshot for the convenience of subsequent troubleshooting.
|
||||
March 28: customer still has the issue with remote control - commandes are not executed.
|
||||
March 19:Waiting for customer feedback for more than two weeks, if there is still no feedback, recommend temporary closure.@Vsevolod Tsoi
|
||||
March 18: Still Waiting. @Vsevolod Tsoi
|
||||
March 11: Waiting for customer feedback
|
||||
Feb 20After the background inquiry, because the user's vehicle was in a bad signal location, the vehicle did not receive the remote control command, the follow-up view function can be used normally, it is recommended that the user again to observe the use, if there are still problems, it is recommended to feedback and provide relevant logs again.
|
||||
18/02: customer asked for reproducing the problem with futher providing the required input data.",Low,temporary close,车控APP(Car control),Vsevolod,05/06/2025,CHERY TIGGO 9 (T28)),LVTDD24B5RG091332,,TGR0000627,"Hibernation_mode.JPG,Picture_1.jpg",Vsevolod,107.0,2025-02-18,2025-06-05
|
||||
TR458,Mail,19/02/2025,Remote control ,Remote control doesn't work +wrong status,"0319:等待OTA0220:已知TBOX网络阻塞问题,待后续OTA或者进站软件升级
|
||||
0219:
|
||||
1.转 TSP 国内分析 TSP 端日志@张石树
|
||||
2.TBOX log 已抓取,转专业分析@何文国","03/04: OTA upgrade completed successfully. No any issues since ugrade.
|
||||
Feb 20: Known TBOX network blocking issue, pending subsequent OTA or inbound software upgrade
|
||||
19/02: operation time 18/02 at 17:57",Low,close,TBOX,Evgeniy,03/04/2025,EXEED RX(T22),LVTDD24B7RG032105,,,"LOG_LVTDD24B7RG03210520250219115848.tar,image.png",Evgeniy,43.0,2025-02-19,2025-04-03
|
||||
TR460,Telegram bot,20/02/2025,Remote control ,"Remote control issue: vehicle is shown in the remote control app as unlocked and started but in fact it is locked and the engine stopped. When trying to stop the engine in the app an error message comes up ""Time for the response has expired. Please try again later"" - see attached.","0319:等待OTA0225:2月20日13:22发出停止发动机指令,用户尝试熄火但实际已熄火了,附图
|
||||
0224:属地运维已经提交USB刷写tbox清单,刷写后若问题解决,则问题关闭。","24/04: no issues since upgrade => TR closed
|
||||
03/04: OTA upgrade completed successfully on March, 31th - under monitoring Wait for 1 week. If there is no any negative feedback, the issue will be closed.
|
||||
25/02: command name - engine stop on 20/02 at 13:22. Customer tried to stop the engine although in fact it was already stopped. Picture attached.
|
||||
24/02: this car was not included into the list of 41 cars for TBOX SW upgrade. Should it be in?
|
||||
Feb 24:If the issue is resolved after USB refresh, the issue is closed.
|
||||
20/02: vehicle status in the TSP: last tbox login 2025-02-19 17:18:30, high frequency data 2025-02-19 18:31:08. Status in the MNO: acces to the network is granted. Apn1&2 are available and active.",Low,close,TBOX,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24B2RG032562,,TGR0000654,"Picture_3.jpg,Picture_2.jpg,Picture_1.jpg",Vsevolod,63.0,2025-02-20,2025-04-24
|
||||
TR461,Mail,20/02/2025,Application,The customer can't bound the car in APP. Error : А07913,0224:该问题已经在“TELEMATICA APP”解决·,可关闭该问题,"0224: The problem has been solved in the ""TELEMATICA APP"" - the problem can be closed!",Low,close,用户EXEED-APP(User),Evgeniy,24/02/2025,EXEED VX FL(M36T),LVTDD24B6PD593801,,,"image.png,image.png",Evgeniy,4.0,2025-02-20,2025-02-24
|
||||
TR462,Telegram bot,20/02/2025,Activation SIM,"Customer can't activate the remote control. Following the checking IHU ID record in the TSP a message comes up ""Request param error:param tBoxSn is inconsistent with the TSP T-BoxS"". Feedback from 2nd line: ""The production data is this FCOT1EEE527M253#, and it cannot be confirmed whether the actual car has been replaced with parts."" Dealer's feedback ""no repair had been done on the vehicle"".
|
||||
客户无法激活远控。 在 TSP 中检查 IHU ID 记录后,出现一条信息 ""Request param error:param tBoxSn is inconsistent with the TSP T-BoxS""(请求参数错误:参数 tBoxSn 与 TSP T-BoxS 不一致)。 第二行反馈: ""生产数据是此 FCOT1EEE527M253#,无法确认实车是否已更换部件""。 经销商反馈 ""未对车辆进行过维修""。","1119:TSP库里的tboxSN多了一个空格,已修改,请客户再次尝试,如果问题解决了就可以关闭此问题。
|
||||
1118:TR系统已重新启用,因所需数据已提交——详见工程菜单中附带的DMC&TBOX数据截图及最新Tbox日志。
|
||||
0807: 暂时关闭。将在获取 TBOX 数据时重新开放。
|
||||
0804:目前暂无进展
|
||||
0728:等待客户进站读取实车信息
|
||||
0725:等待客户进站读取实车信息
|
||||
0320:继续等待
|
||||
0318:继续等待
|
||||
0318:继续等待
|
||||
0311:无进度反馈
|
||||
0221:等待经销商信息返回 (dealer)","24/11: remote control was restored. TR closed.
|
||||
19/11: Tbox started loging in. The user was asked to try binding his car in the app.
|
||||
19/11:An extra space has been added to tboxSN in the TSP library. This has been rectified; please ask the client to try again.If the issue has been resolved, you may close this case.@Vsevolod Tsoi
|
||||
18/11: TR re-opened since the requested data was provided - see attached DMC&TBOX data photo from engineering menu as well as the latest Tbox log
|
||||
07/08: closed temporarily. Will be-reopened when getting TBOX data.
|
||||
04/08: no feedback so far.
|
||||
31/07: still the same status
|
||||
28/07: invitation of customer for collecting TBOX data is still pending.
|
||||
22/07: TR was opened again because customer still was unable to enter into member center. TSP says that TBOX SN doesn't match to one installed in the car. A request for collecting TBOX data has been sent to dealer.
|
||||
10/04:temp lose
|
||||
08/04Waiting for dealer feedback
|
||||
25/03: please keep it open - so, no close possible.
|
||||
20/03:Still waiting.
|
||||
18/03: Still waiting.
|
||||
11/03: Still waiting.
|
||||
21/02: Wait for the dealer's feedback.
|
||||
20/02: it seems that vehicle has a different TBOX SN from the one in the TSP - see picure attached. Request has been sent to invite the customer to the dealer for collecting TBOX data. Wait for feedback.",Low,close,TBOX,Vsevolod Tsoi,24/11/2025,JAECOO J7(T1EJ),LVVDB21B9RC103393,,"TGR0000500
|
||||
elantseva.dv@rnd.borauto.ru","CHR_LVVDB21B9RC103393_20251118205043.tar,DMC_data.jpg,TBOX_data.jpg,file_714.jpg,Picture_1.JPG,file_715.jpg,file_702.jpg,file_705.jpg,file_704.jpg,file_703.jpg,file_701.jpg",Vsevolod,277.0,2025-02-20,2025-11-24
|
||||
TR464,Telegram bot,21/02/2025,Remote control ,"Customer can't start the vehicle vie remote control app. As a result an error message comes up.
|
||||
Vehicle status in the TSP: last tbox login 2025-02-18 20:20:32, high frequency data 2025-02-18 20:19:26. MNO status: last apn1&2 available on 18/02.",0224:建议加入清单进行USB软件更新,"27/02: customer's feedback - apn1 is again available and as a consequence the remote control started working.
|
||||
26/02: tbox log attached.
|
||||
0224: Recommendation to add to the list for USB software update @Vsevolod
|
||||
21/02: command name - engine start on 21/02 at 12:13. Screenshot attached.",Low,close,local O&M,Vsevolod,27/02/2025,EXEED RX(T22),LVTDD24B1RG021245,,TGR0000646,"LOG_LVTDD24B1RG021245_26_02_2025.tar,Picture_1.jpg",Vsevolod,6.0,2025-02-21,2025-02-27
|
||||
TR465,Telegram bot,21/02/2025,Application,"Customer can't bind his vehicle in the remote control app. Background: customer was using the app and everythig worked well. But one day he opened the app and noticed that he has been loged out. Then he loged in again with his account data and added the vehicle in the app (vehicle status is OK in the system). However he could not activate it in the app => status in the TSP - unbound . An error message comes up ""A07913"" - see pictures attached.","0320:等待客户反馈,追踪是否能够正常使用
|
||||
0320:后台已删除错误数据,可让用户重新绑定
|
||||
0318:已转入分析@张明亮
|
||||
0227:A07913报错 注册品牌信息失败@戴国富
|
||||
0225: 客户反馈:手机端仍然无法激活汽车。同样的错误提示“A07913”@喻起航
|
||||
0225:此问题已解决,请属地运维联系用户核实进行问题关闭@Vsevolod 7天没有反馈需要关闭
|
||||
0224:转APP分析","20/03: Customer's feedback: problem is solved.
|
||||
20/03:Erroneous data has been deleted in the background, allowing users to rebind.@Vsevolod Tsoi
|
||||
18/03:Transferred for analysis.@张明亮
|
||||
25/02: customer's feedback: still can't activate the car in the mobile app. The same error message comes up ""A07913""@喻起航
|
||||
Feb25: This issue has been resolved, please local O&M contact the user to verify for problem closure @Vsevolod
|
||||
Feb 24: Turn APP Analysis",Low,close,车控APP(Car control),Vsevolod Tsoi,20/03/2025,CHERY TIGGO 9 (T28)),LVTDD24B9RG088529,,TGR0000590,"Picture_1.jpg,Status_admin.JPG,Vehice_status_undind.JPG,file_971.jpg",Vsevolod,27.0,2025-02-21,2025-03-20
|
||||
TR466,Telegram bot,21/02/2025,Network,Customer can't laucnh online video app -> License issue -> Other app doesn't work too -> No tbox connect since 18/02 -> No any network connection -> IHU still login well. apps works with wifi,0224:等待更多细节反馈,UPD?,"24/02 Closed, confirmed by customer feedback
|
||||
21/02 PLS provide information what data we should collect for analysis
|
||||
UPD solved by itself -> Waiting for feedback",Low,close,local O&M,Kostya,24/02/2025,EXEED RX(T22),LVTDD24B1RG021245,,TGR0000676,image.png,Kostya,3.0,2025-02-21,2025-02-24
|
||||
TR467,Mail,21/02/2025,Remote control ,"Remote engine start does not work via app. Status in TSP: last tbox login 2025-02-21 03:08:41. high frequency data 2025-02-21 15:14:11. MNO: apn2 - OK. No working apn1 since Feb, 18th. ","0826:暂无进展,转等待数据
|
||||
0819:请QS方更新进展
|
||||
0814: 等待TBOX硬件抵达
|
||||
0812: 预计抵达日期 W33 底
|
||||
0807: 预计抵达日期 CW33 底
|
||||
0804:已询问经销商了解预计抵达时间。
|
||||
0728: 仍在等待 TBOX 硬件的交付,预计将于 7 月底/8 月初交付。
|
||||
0722:仍在等待 TBOX 硬件的交付。
|
||||
0714: 到目前为止还没有收到货物。
|
||||
0710: 仍未收到 TBOX 硬件。
|
||||
0701:等待 8 月份交付 TBOX HW。
|
||||
0626:新的 TBOX 预计从 8 月开始交付
|
||||
0623: 等待用户更换T-BOX
|
||||
0610:等待信息刷新
|
||||
0603:等待用户更换tbox
|
||||
0529:等待更新
|
||||
0515:建议用户进站,先取日志之后,再更换TBOX,保留日志供分析
|
||||
0513:属地反馈问题重新出现,ANP2已在5月1日打开,但无网络连接,会后尝试抓取日志,车辆已进入深度睡眠,建议按深度睡眠流程处理
|
||||
0415:建议此问题暂时关闭
|
||||
0410:等待用户进站
|
||||
0407:等待用户进站
|
||||
0401:等待用户进站
|
||||
0325:等到用户进站
|
||||
0320:等待日志抓取
|
||||
0318:等待用户进站取日志
|
||||
0311:等待前方抓取日志
|
||||
0304:取Tbox日志并分析
|
||||
0227:查询到用户远程操控失败原因均因为提示超时,建议用户将车辆移动到信号好的地方再试试,若还不行,建议进站检查。
|
||||
0225:转TSP查询; 短信下发后 TBOX无响应,35s超时(time out)
|
||||
0224:请MNO协助排查下APN。APN正常","09/10: we got really very strange feedback from dealership: we were informed by dealership that spare TBOX arrived and change might be done accordingly so that the remote control was restored. We were told by a dealership that user was contacted by CHERY representative office telling that change of TBOX was not required.
|
||||
09/10: still pending. Then, status checked out in TSP and MNO: apn1&2 are normal.
|
||||
25/09: TBOX arrival is pending.
|
||||
22/09: arrival TBOX HW is still pending.
|
||||
18/09: no feedback so far.
|
||||
16/09: no change since the last comment.
|
||||
11/09: dealer's feedback - TBOX HW arrival is expected soon. Once received, all the required data will be provided to modify TSP with a new TBOX HW.
|
||||
02/09: repeat request on TBOX arrival date is sent to dealer. Wait for feedback.
|
||||
26/08:no update ,turn to waiting for data
|
||||
19/08: pls QS side update the progress
|
||||
14/08: wait for TBOX HW arrival.
|
||||
12/08: same status
|
||||
07/08: expected arrival date end of W33
|
||||
04/08: request sent to dealer to find out an expected arrival.
|
||||
31/07: same status
|
||||
28/07: still wait for delivery of TBOX HW that is expected end of July/begining August.
|
||||
22/07: still wait for delivery of TBOX HW.
|
||||
14/07: there was no delivery so far.
|
||||
10/07: TBOX HW arrival is still pending.
|
||||
01.07: wait for delivery of TBOX HW in August
|
||||
24/06: delivery of new TBOX is expected begining August.
|
||||
16/06: request for changing TBOX HW has been sent. Waif for after-sales confirmation.
|
||||
0605:need customer go to the dealer,first,catch the tbox log,secondly,replace a new tbox,that's all.
|
||||
03/06: please precise when a decision has been made that tbox HW needs to be replaced.
|
||||
29.05: still wait for feedback
|
||||
19/05: customer is asked to start the engine with Start/Stop button to let vehicle go out of hibernation mode. Wait for feedback.
|
||||
13/05:The local feedback issue has reappeared. ANP2 was opened on May 1st, but there was no network connection. After the meeting, attempts were made to capture logs, and the vehicle has entered deep sleep. It is recommended to follow the deep sleep process for handling.
|
||||
13/05: status checked out in MNO: apn2 has been switched again on May, 1st but there is still no network available - see picture attached.
|
||||
15/04: Suggest temporarily closing this issue
|
||||
10/04: still waiting for invititaion of customer to dealer
|
||||
07/04: still waiting for invititaion of customer to dealer
|
||||
April 01 :Still Waiting.
|
||||
March 25:Still Waiting.
|
||||
March 20:waiting the customer to go to the dealer for logs.
|
||||
March 18: waiting the customer to go to the dealer for logs.
|
||||
March 11:need Tbox logs to analysis
|
||||
March 4: need Tbox logs to analysis
|
||||
Feb 27:Query to the user remote control failure reasons are because of the prompt timeout, it is recommended that the user will move the vehicle to a good signal place and try again, if it does not work, it is recommended to enter the station to check.
|
||||
2/24 MNO:APN1 is configured properly without any additional restrictions(APN1配置正常,无任何额外限制)
|
||||
21/02: wrong vehicle status is displayed in the app: engine shown as started as well as the doors are unlocked. However the car is locked. Commande name - lock the doors on 21/02 at 16:34. Pictures attached. Tbox log in process of upload.",Low,close,TBOX,Vsevolod,16/10/2025,CHERY TIGGO 9 (T28)),LVTDD24B7RG116179,,,"MNO_status.JPG,Picture_2.JPG,Picture-1.JPG",Vsevolod,237.0,2025-02-21,2025-10-16
|
||||
TR468,Telegram bot,24/02/2025,Remote control ,"Remote control doesn't work neither apps in the IHU. However, apps work well using the network shared from smartphone. Status TSP: last tbox login on 2024-12-22 22:01:04, high frequency data
|
||||
2024-12-22 22:18:56. MNO status: no apn1 available since Dec, 22th 2024.","0319:等待OTA
|
||||
0224:请抓取下tbox日志以及主机日志","24/04: upgrade successfully completed on April, 5th. No issues since upgrade => TR closed.
|
||||
03/04: there is still no connection. Wait for downloading.
|
||||
Mar 19: Waiting for OTA.
|
||||
Feb 24:Please grab the tbox logs as well as the DMC logs.@Vsevolod",Low,close,local O&M,Vsevolod,24/04/2025,EXEED RX(T22),LVTDD24B5RG020678,,TGR0000602,"file_993.jpg,file_872.jpg,file_870.jpg,file_843.jpg,file_864.jpg,file_844.jpg,file_871.jpg",Vsevolod,59.0,2025-02-24,2025-04-24
|
||||
TR469,Mail,24/02/2025,Remote control ,Remote control doesn't work + wrong status of car.,0319:等待OTA0225:等待具体日志及信息,"Apr 17: solved after OTA
|
||||
10/04: OTA upgrade success on 10/04. Wait for 2 weeks to check remote control. if no issue to be closed.
|
||||
03/04: download complete. Wait for upgrade.
|
||||
Feb 24: Collecting operation time /screenshots",Low,close,local O&M,Evgeniy,17/04/2025,EXEED RX(T22),LVTDD24B5RG031311,,,,Evgeniy,52.0,2025-02-24,2025-04-17
|
||||
TR470,Mail,24/02/2025,Remote control ,Remote control doesn't work + wrong status of car.,0319:等待OTA0225:等待具体日志及信息,"Apr 17: solved after OTA
|
||||
03/04: download complete. Wait for upgrade.
|
||||
Feb 24: Collecting operation time /screenshots",Low,close,local O&M,Evgeniy,17/04/2025,EXEED RX(T22),LVTDD24B6RG021211,,,image.png,Evgeniy,52.0,2025-02-24,2025-04-17
|
||||
TR471,Mail,24/02/2025,Remote control ,"Remote control doesn't work. Status in the TSP: last tbox login 2025-02-07 04:50:03; high frequency data 2025-01-06 16:37:14. MNO: no apn1 availabale since Jan, 10th.","0515:协商一致,今日拟关闭问题
|
||||
0424:用户与今日升级成功,一周后无反馈可关闭问题
|
||||
0403:无消息反馈,等待用户下载后OTA
|
||||
0319:等待OTA0225:该车可以进行USB刷写软件,新增车辆刷写软件可以在此表格中新增,说清楚时间即可","15/05: Consensus reached through negotiation to close the issue today
|
||||
24/04: upgrade completed successfully on April, 24th. The issue is going to be closed if no there are no any claimes after one week usage.
|
||||
03/04: there is still no connection. Wait for downloading.
|
||||
25/02: command name - unlock the doors on 25/02 at 7:52. Picture attached.
|
||||
Feb 25:The car can be USB brush writing software, new vehicle brush writing software can be added in this form, say clear time can be
|
||||
links:https://l5j8axkr3y6.sg.larksuite.com/share/base/view/shrlgvxQ7p0rMcpHSQpnTiQU2Dh
|
||||
24/02: plese check and confirm if TBOX SW of the car might be upgraded with USB at dealer @喻起航",Low,close,local O&M,Vsevolod,15/05/2025,EXEED RX(T22),LVTDD24B1RG019253,,,Picture_1.jpg,Vsevolod,80.0,2025-02-24,2025-05-15
|
||||
|
56
cli.py
56
cli.py
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
交互式 CLI —— 四层架构自适应分析
|
||||
用法: python3 cli.py [数据库路径]
|
||||
用法: python cli.py [数据库路径]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from config import DB_PATH, LLM_CONFIG, MAX_EXPLORATION_ROUNDS
|
||||
from core.config import DB_PATH, LLM_CONFIG, MAX_EXPLORATION_ROUNDS, PLAYBOOK_DIR
|
||||
from agent import DataAnalysisAgent
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ def print_help():
|
||||
可用命令:
|
||||
<问题> 分析一个问题
|
||||
rounds=<N> <问题> 设置探索轮数
|
||||
report [主题] 整合所有分析,生成综合报告
|
||||
schema 查看数据库 Schema
|
||||
playbooks 查看已加载的预设剧本
|
||||
regen 重新生成预设剧本
|
||||
history 查看分析历史
|
||||
audit 查看 SQL 审计日志
|
||||
clear 清空分析历史
|
||||
@@ -30,17 +33,12 @@ def main():
|
||||
|
||||
if not os.path.exists(db_path):
|
||||
print(f"❌ 数据库不存在: {db_path}")
|
||||
print(f" 请先运行 python3 demo.py 创建示例数据库,或指定已有数据库路径")
|
||||
sys.exit(1)
|
||||
|
||||
if not LLM_CONFIG["api_key"]:
|
||||
print("⚠️ 未配置 LLM_API_KEY,请先设置环境变量:")
|
||||
print(" export LLM_API_KEY=sk-xxx")
|
||||
print(" export LLM_BASE_URL=https://api.openai.com/v1 # 或 Ollama 等")
|
||||
print(" export LLM_MODEL=gpt-4o")
|
||||
print("⚠️ 未配置 LLM_API_KEY")
|
||||
sys.exit(1)
|
||||
|
||||
# 初始化 Agent
|
||||
agent = DataAnalysisAgent(db_path)
|
||||
|
||||
print("=" * 60)
|
||||
@@ -49,6 +47,7 @@ def main():
|
||||
print(f"\n🔗 LLM: {LLM_CONFIG['model']} @ {LLM_CONFIG['base_url']}")
|
||||
print(f"🔄 最大探索轮数: {MAX_EXPLORATION_ROUNDS}")
|
||||
print(f"💾 数据库: {db_path}")
|
||||
print(f"📋 预设剧本: {len(agent.playbook_mgr.playbooks)} 个")
|
||||
print(f"\n💬 输入分析问题(help 查看命令)\n")
|
||||
|
||||
while True:
|
||||
@@ -68,22 +67,47 @@ def main():
|
||||
break
|
||||
elif cmd == "help":
|
||||
print_help()
|
||||
continue
|
||||
elif cmd == "schema":
|
||||
print(agent.get_schema())
|
||||
continue
|
||||
elif cmd == "history":
|
||||
print(agent.get_history())
|
||||
continue
|
||||
elif cmd == "audit":
|
||||
print(agent.get_audit())
|
||||
continue
|
||||
elif cmd == "clear":
|
||||
agent.clear_history()
|
||||
print("✅ 历史已清空")
|
||||
continue
|
||||
|
||||
# 解析可选参数:rounds=3
|
||||
elif cmd.startswith("report"):
|
||||
topic = user_input[6:].strip()
|
||||
try:
|
||||
report = agent.full_report(question=topic)
|
||||
print("\n" + report)
|
||||
print("\n" + "~" * 60)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 报告整合出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
elif cmd == "playbooks":
|
||||
if not agent.playbook_mgr.playbooks:
|
||||
print("(无预设剧本,输入 regen 让 AI 自动生成)")
|
||||
else:
|
||||
for i, pb in enumerate(agent.playbook_mgr.playbooks, 1):
|
||||
print(f" {i}. 📋 {pb.name} — {pb.description} ({len(pb.preset_queries)} 条预设)")
|
||||
elif cmd == "regen":
|
||||
if os.path.isdir(PLAYBOOK_DIR):
|
||||
for f in os.listdir(PLAYBOOK_DIR):
|
||||
if f.startswith("auto_") and f.endswith(".json"):
|
||||
os.remove(os.path.join(PLAYBOOK_DIR, f))
|
||||
agent.playbook_mgr.playbooks.clear()
|
||||
print("🤖 AI 正在重新生成预设剧本...")
|
||||
generated = agent.playbook_mgr.auto_generate(agent.schema_text, save_dir=PLAYBOOK_DIR)
|
||||
if generated:
|
||||
print(f"✅ 生成 {len(generated)} 个剧本:")
|
||||
for pb in generated:
|
||||
print(f" 📋 {pb.name} — {pb.description}")
|
||||
else:
|
||||
print("⚠️ 生成失败")
|
||||
else:
|
||||
# 解析 rounds=N
|
||||
max_rounds = MAX_EXPLORATION_ROUNDS
|
||||
question = user_input
|
||||
if "rounds=" in question.lower():
|
||||
@@ -103,9 +127,9 @@ def main():
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# 退出时显示审计
|
||||
print("\n📋 本次会话审计:")
|
||||
print(agent.get_audit())
|
||||
agent.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
36
config.py
36
config.py
@@ -1,36 +0,0 @@
|
||||
"""
|
||||
配置文件
|
||||
"""
|
||||
import os
|
||||
|
||||
# LLM 配置(兼容 OpenAI API 格式,包括 Ollama / vLLM / DeepSeek 等)
|
||||
LLM_CONFIG = {
|
||||
"api_key": os.getenv("LLM_API_KEY", ""),
|
||||
"base_url": os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"),
|
||||
"model": os.getenv("LLM_MODEL", "gpt-4o"),
|
||||
}
|
||||
|
||||
# 沙箱安全规则
|
||||
SANDBOX_RULES = {
|
||||
"max_result_rows": 1000, # 聚合结果最大行数
|
||||
"round_floats": 2, # 浮点数保留位数
|
||||
"suppress_small_n": 5, # 分组样本 < n 时模糊处理
|
||||
"banned_keywords": [ # 禁止的 SQL 关键字
|
||||
"SELECT *",
|
||||
"INSERT",
|
||||
"UPDATE",
|
||||
"DELETE",
|
||||
"DROP",
|
||||
"ALTER",
|
||||
"CREATE",
|
||||
"ATTACH",
|
||||
"PRAGMA",
|
||||
],
|
||||
"require_aggregation": True, # 是否要求使用聚合函数
|
||||
}
|
||||
|
||||
# 数据库路径
|
||||
DB_PATH = os.getenv("DB_PATH", os.path.join(os.path.dirname(__file__), "demo.db"))
|
||||
|
||||
# 分析控制
|
||||
MAX_EXPLORATION_ROUNDS = int(os.getenv("MAX_ROUNDS", "6")) # 最大探索轮数
|
||||
134
context.py
134
context.py
@@ -1,134 +0,0 @@
|
||||
"""
|
||||
Layer 4: 上下文管理器
|
||||
管理多轮对话的分析上下文,让后续问题可以引用之前的发现。
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
from explorer import ExplorationStep
|
||||
from insights import Insight
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisSession:
|
||||
"""一次分析的完整记录"""
|
||||
question: str
|
||||
plan: dict
|
||||
steps: list[ExplorationStep]
|
||||
insights: list[Insight]
|
||||
report: str
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
|
||||
def summary(self) -> str:
|
||||
"""生成本次分析的摘要(供后续对话引用)"""
|
||||
parts = [f"**问题**: {self.question}"]
|
||||
|
||||
if self.plan:
|
||||
parts.append(f"**分析类型**: {self.plan.get('analysis_type', 'unknown')}")
|
||||
parts.append(f"**关注维度**: {', '.join(self.plan.get('dimensions', []))}")
|
||||
|
||||
# 核心发现(从成功步骤中提取)
|
||||
key_findings = []
|
||||
for step in self.steps:
|
||||
if step.success and step.rows:
|
||||
# 提取最突出的值
|
||||
top_row = step.rows[0] if step.rows else {}
|
||||
finding = f"{step.purpose}: "
|
||||
finding += ", ".join(
|
||||
f"{k}={v}" for k, v in top_row.items() if k.lower() not in ("id",)
|
||||
)
|
||||
key_findings.append(finding)
|
||||
|
||||
if key_findings:
|
||||
parts.append("**核心发现**:")
|
||||
for f in key_findings[:5]:
|
||||
parts.append(f" - {f}")
|
||||
|
||||
# 洞察
|
||||
if self.insights:
|
||||
parts.append("**主动洞察**:")
|
||||
for insight in self.insights[:3]:
|
||||
parts.append(f" - {insight}")
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
def to_reference_text(self) -> str:
|
||||
"""生成供 LLM 使用的上下文文本"""
|
||||
return (
|
||||
f"## 之前的分析\n\n"
|
||||
f"### 问题\n{self.question}\n\n"
|
||||
f"### 摘要\n{self.summary()}\n\n"
|
||||
f"### 详细发现\n"
|
||||
+ "\n".join(
|
||||
f"- {step.purpose}: {step.row_count} 行结果"
|
||||
for step in self.steps if step.success
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ContextManager:
|
||||
"""上下文管理器:管理多轮对话的分析历史"""
|
||||
|
||||
def __init__(self, max_history: int = 10):
|
||||
self.sessions: list[AnalysisSession] = []
|
||||
self.max_history = max_history
|
||||
|
||||
def add_session(
|
||||
self,
|
||||
question: str,
|
||||
plan: dict,
|
||||
steps: list[ExplorationStep],
|
||||
insights: list[Insight],
|
||||
report: str,
|
||||
) -> AnalysisSession:
|
||||
"""记录一次分析"""
|
||||
session = AnalysisSession(
|
||||
question=question,
|
||||
plan=plan,
|
||||
steps=steps,
|
||||
insights=insights,
|
||||
report=report,
|
||||
)
|
||||
self.sessions.append(session)
|
||||
|
||||
# 保持历史大小
|
||||
if len(self.sessions) > self.max_history:
|
||||
self.sessions = self.sessions[-self.max_history:]
|
||||
|
||||
return session
|
||||
|
||||
def get_context_for(self, new_question: str) -> Optional[str]:
|
||||
"""
|
||||
根据新问题,从历史中找到相关上下文。
|
||||
简单实现:取最近的分析会话。
|
||||
"""
|
||||
if not self.sessions:
|
||||
return None
|
||||
|
||||
# 取最近 2 轮分析的摘要
|
||||
recent = self.sessions[-2:]
|
||||
parts = []
|
||||
for session in recent:
|
||||
parts.append(session.to_reference_text())
|
||||
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
def get_history_summary(self) -> str:
|
||||
"""获取所有历史的摘要"""
|
||||
if not self.sessions:
|
||||
return "(无历史分析)"
|
||||
|
||||
lines = [f"共 {len(self.sessions)} 次分析:"]
|
||||
for i, session in enumerate(self.sessions, 1):
|
||||
ts = time.strftime("%H:%M", time.localtime(session.timestamp))
|
||||
lines.append(f" {i}. [{ts}] {session.question}")
|
||||
if session.insights:
|
||||
for insight in session.insights[:2]:
|
||||
lines.append(f" {insight}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def clear(self):
|
||||
"""清空历史"""
|
||||
self.sessions.clear()
|
||||
22
core/__init__.py
Normal file
22
core/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
iov_ana 核心包
|
||||
|
||||
项目结构:
|
||||
core/
|
||||
config.py - 配置
|
||||
utils.py - 公共工具(JSON 提取、LLM 客户端)
|
||||
schema.py - Schema 提取
|
||||
sandbox.py - SQL 沙箱执行器
|
||||
layers/
|
||||
planner.py - Layer 1: 意图规划
|
||||
playbook.py - Layer 1.5: 预设剧本
|
||||
explorer.py - Layer 2: 自适应探索
|
||||
insights.py - Layer 3: 异常洞察
|
||||
context.py - Layer 4: 上下文记忆
|
||||
output/
|
||||
reporter.py - 单次报告生成
|
||||
consolidator.py - 多次报告整合
|
||||
chart.py - 图表生成
|
||||
agent.py - Agent 编排层
|
||||
cli.py - 交互式 CLI
|
||||
"""
|
||||
38
core/config.py
Normal file
38
core/config.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
配置文件
|
||||
"""
|
||||
import os
|
||||
|
||||
# LLM 配置(兼容 OpenAI API 格式,包括 Ollama / vLLM / DeepSeek 等)
|
||||
LLM_CONFIG = {
|
||||
"api_key": os.getenv("LLM_API_KEY", "sk-c44i1hy64xgzwox6x08o4zug93frq6rgn84oqugf2pje1tg4"),
|
||||
"base_url": os.getenv("LLM_BASE_URL", "https://api.xiaomimimo.com/v1"),
|
||||
"model": os.getenv("LLM_MODEL", "mimo-v2-flash"),
|
||||
}
|
||||
|
||||
# 沙箱安全规则
|
||||
SANDBOX_RULES = {
|
||||
"max_result_rows": 1000,
|
||||
"round_floats": 2,
|
||||
"suppress_small_n": 5,
|
||||
"banned_keywords": [
|
||||
"SELECT *", "INSERT", "UPDATE", "DELETE",
|
||||
"DROP", "ALTER", "CREATE", "ATTACH", "PRAGMA",
|
||||
],
|
||||
"require_aggregation": True,
|
||||
}
|
||||
|
||||
# 项目根目录
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
# 数据库路径
|
||||
DB_PATH = os.getenv("DB_PATH", os.path.join(PROJECT_ROOT, "demo.db"))
|
||||
|
||||
# Playbook 目录
|
||||
PLAYBOOK_DIR = os.getenv("PLAYBOOK_DIR", os.path.join(PROJECT_ROOT, "playbooks"))
|
||||
|
||||
# 图表输出目录
|
||||
CHARTS_DIR = os.getenv("CHARTS_DIR", os.path.join(PROJECT_ROOT, "charts"))
|
||||
|
||||
# 分析控制
|
||||
MAX_EXPLORATION_ROUNDS = int(os.getenv("MAX_ROUNDS", "6"))
|
||||
@@ -4,135 +4,95 @@
|
||||
import sqlite3
|
||||
import re
|
||||
from typing import Any
|
||||
from config import SANDBOX_RULES, DB_PATH
|
||||
|
||||
|
||||
from core.config import SANDBOX_RULES
|
||||
class SandboxError(Exception):
|
||||
"""沙箱安全违规"""
|
||||
pass
|
||||
|
||||
|
||||
class SandboxExecutor:
|
||||
def __init__(self, db_path: str = DB_PATH):
|
||||
def __init__(self, db_path: str):
|
||||
self.db_path = db_path
|
||||
self.rules = SANDBOX_RULES
|
||||
self.execution_log: list[dict] = []
|
||||
# 持久连接,避免每次查询都开关
|
||||
self._conn = sqlite3.connect(db_path)
|
||||
self._conn.row_factory = sqlite3.Row
|
||||
|
||||
def execute(self, sql: str) -> dict[str, Any]:
|
||||
"""
|
||||
执行 SQL,返回脱敏后的聚合结果。
|
||||
如果违反安全规则,抛出 SandboxError。
|
||||
"""
|
||||
# 验证 SQL 安全性
|
||||
"""执行 SQL,返回脱敏后的聚合结果"""
|
||||
self._validate(sql)
|
||||
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
|
||||
cur = self._conn.cursor()
|
||||
try:
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
|
||||
# 转为字典列表
|
||||
columns = [desc[0] for desc in cur.description] if cur.description else []
|
||||
results = [dict(row) for row in rows]
|
||||
|
||||
# 脱敏处理
|
||||
sanitized = self._sanitize(results, columns)
|
||||
|
||||
# 记录执行日志
|
||||
self.execution_log.append({
|
||||
"sql": sql,
|
||||
"rows_returned": len(results),
|
||||
"columns": columns,
|
||||
"sql": sql, "rows_returned": len(results), "columns": columns,
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"columns": columns,
|
||||
"rows": sanitized,
|
||||
"row_count": len(sanitized),
|
||||
"sql": sql,
|
||||
"success": True, "columns": columns,
|
||||
"rows": sanitized, "row_count": len(sanitized), "sql": sql,
|
||||
}
|
||||
|
||||
except sqlite3.Error as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"sql": sql,
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
return {"success": False, "error": str(e), "sql": sql}
|
||||
|
||||
def close(self):
|
||||
"""关闭连接"""
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
|
||||
def _validate(self, sql: str):
|
||||
"""SQL 安全验证"""
|
||||
sql_upper = sql.upper().strip()
|
||||
|
||||
# 1. 检查禁止的关键字
|
||||
for banned in self.rules["banned_keywords"]:
|
||||
if banned.upper() in sql_upper:
|
||||
raise SandboxError(f"禁止的 SQL 关键字: {banned}")
|
||||
|
||||
# 2. 只允许 SELECT(不能有多语句)
|
||||
statements = [s.strip() for s in sql.split(";") if s.strip()]
|
||||
if len(statements) > 1:
|
||||
raise SandboxError("禁止多语句执行")
|
||||
if not sql_upper.startswith("SELECT"):
|
||||
raise SandboxError("只允许 SELECT 查询")
|
||||
|
||||
# 3. 检查是否使用了聚合函数或 GROUP BY(可选要求)
|
||||
if self.rules["require_aggregation"]:
|
||||
agg_keywords = ["COUNT", "SUM", "AVG", "MIN", "MAX", "GROUP BY",
|
||||
"DISTINCT", "HAVING", "ROUND", "CAST"]
|
||||
has_agg = any(kw in sql_upper for kw in agg_keywords)
|
||||
has_limit = "LIMIT" in sql_upper
|
||||
if not has_agg and "LIMIT" not in sql_upper:
|
||||
raise SandboxError("要求使用聚合函数 (COUNT/SUM/AVG/MIN/MAX/GROUP BY) 或 LIMIT")
|
||||
|
||||
if not has_agg and not has_limit:
|
||||
raise SandboxError(
|
||||
"要求使用聚合函数 (COUNT/SUM/AVG/MIN/MAX/GROUP BY) 或 LIMIT"
|
||||
)
|
||||
|
||||
# 4. LIMIT 检查
|
||||
limit_match = re.search(r'LIMIT\s+(\d+)', sql_upper)
|
||||
if limit_match:
|
||||
limit_val = int(limit_match.group(1))
|
||||
if limit_val > self.rules["max_result_rows"]:
|
||||
raise SandboxError(
|
||||
f"LIMIT {limit_val} 超过最大允许值 {self.rules['max_result_rows']}"
|
||||
)
|
||||
if limit_match and int(limit_match.group(1)) > self.rules["max_result_rows"]:
|
||||
raise SandboxError(f"LIMIT 超过最大允许值 {self.rules['max_result_rows']}")
|
||||
|
||||
def _sanitize(self, rows: list[dict], columns: list[str]) -> list[dict]:
|
||||
"""对结果进行脱敏处理"""
|
||||
if not rows:
|
||||
return rows
|
||||
|
||||
# 1. 限制行数
|
||||
"""脱敏处理"""
|
||||
rows = rows[:self.rules["max_result_rows"]]
|
||||
suppress_n = self.rules["suppress_small_n"]
|
||||
round_digits = self.rules["round_floats"]
|
||||
|
||||
# 2. 浮点数四舍五入
|
||||
for row in rows:
|
||||
for col in columns:
|
||||
val = row.get(col)
|
||||
if isinstance(val, float):
|
||||
row[col] = round(val, self.rules["round_floats"])
|
||||
|
||||
# 3. 小样本抑制(k-anonymity)
|
||||
# 如果某个分组的 count 小于阈值,标记为 "<n"
|
||||
for row in rows:
|
||||
for col in columns:
|
||||
# 小样本抑制(先做,避免被 round 影响)
|
||||
if col.lower() in ("count", "cnt", "n", "total"):
|
||||
val = row.get(col)
|
||||
if isinstance(val, (int, float)) and val < self.rules["suppress_small_n"]:
|
||||
row[col] = f"<{self.rules['suppress_small_n']}"
|
||||
|
||||
if isinstance(val, (int, float)) and val < suppress_n:
|
||||
row[col] = f"<{suppress_n}"
|
||||
continue
|
||||
# 浮点数四舍五入
|
||||
if isinstance(val, float):
|
||||
row[col] = round(val, round_digits)
|
||||
return rows
|
||||
|
||||
def get_execution_summary(self) -> str:
|
||||
"""获取执行摘要"""
|
||||
if not self.execution_log:
|
||||
return "尚未执行任何查询"
|
||||
|
||||
lines = [f"共执行 {len(self.execution_log)} 条查询:"]
|
||||
for i, log in enumerate(self.execution_log, 1):
|
||||
lines.append(f" {i}. {log['sql'][:80]}... → {log['rows_returned']} 行结果")
|
||||
@@ -6,25 +6,17 @@ from typing import Any
|
||||
|
||||
|
||||
def extract_schema(db_path: str) -> dict[str, Any]:
|
||||
"""
|
||||
从数据库提取 Schema,只返回结构信息:
|
||||
- 表名、列名、类型
|
||||
- 主键、外键
|
||||
- 行数
|
||||
- 枚举列的去重值(不含原始数据)
|
||||
"""
|
||||
"""从数据库提取 Schema,只返回结构信息"""
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
|
||||
# 获取所有表
|
||||
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
||||
tables = [row["name"] for row in cur.fetchall()]
|
||||
|
||||
schema = {"tables": []}
|
||||
|
||||
for table in tables:
|
||||
# 列信息
|
||||
cur.execute(f"PRAGMA table_info('{table}')")
|
||||
columns = []
|
||||
for col in cur.fetchall():
|
||||
@@ -35,21 +27,15 @@ def extract_schema(db_path: str) -> dict[str, Any]:
|
||||
"is_primary_key": col["pk"] == 1,
|
||||
})
|
||||
|
||||
# 外键
|
||||
cur.execute(f"PRAGMA foreign_key_list('{table}')")
|
||||
fks = []
|
||||
for fk in cur.fetchall():
|
||||
fks.append({
|
||||
"column": fk["from"],
|
||||
"references_table": fk["table"],
|
||||
"references_column": fk["to"],
|
||||
})
|
||||
fks = [
|
||||
{"column": fk["from"], "references_table": fk["table"], "references_column": fk["to"]}
|
||||
for fk in cur.fetchall()
|
||||
]
|
||||
|
||||
# 行数
|
||||
cur.execute(f"SELECT COUNT(*) AS cnt FROM '{table}'")
|
||||
row_count = cur.fetchone()["cnt"]
|
||||
|
||||
# 对 VARCHAR / TEXT 类型列,提取去重枚举值(最多 20 个)
|
||||
data_profile = {}
|
||||
for col in columns:
|
||||
col_name = col["name"]
|
||||
@@ -59,11 +45,7 @@ def extract_schema(db_path: str) -> dict[str, Any]:
|
||||
cur.execute(f'SELECT DISTINCT "{col_name}" FROM "{table}" WHERE "{col_name}" IS NOT NULL LIMIT 20')
|
||||
vals = [row[0] for row in cur.fetchall()]
|
||||
if len(vals) <= 20:
|
||||
data_profile[col_name] = {
|
||||
"type": "enum",
|
||||
"distinct_count": len(vals),
|
||||
"values": vals,
|
||||
}
|
||||
data_profile[col_name] = {"type": "enum", "distinct_count": len(vals), "values": vals}
|
||||
elif any(t in col_type for t in ("INT", "REAL", "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC")):
|
||||
cur.execute(f'''
|
||||
SELECT MIN("{col_name}") AS min_val, MAX("{col_name}") AS max_val,
|
||||
@@ -74,18 +56,13 @@ def extract_schema(db_path: str) -> dict[str, Any]:
|
||||
if row and row["min_val"] is not None:
|
||||
data_profile[col_name] = {
|
||||
"type": "numeric",
|
||||
"min": round(row["min_val"], 2),
|
||||
"max": round(row["max_val"], 2),
|
||||
"avg": round(row["avg_val"], 2),
|
||||
"distinct_count": row["distinct_count"],
|
||||
"min": round(row["min_val"], 2), "max": round(row["max_val"], 2),
|
||||
"avg": round(row["avg_val"], 2), "distinct_count": row["distinct_count"],
|
||||
}
|
||||
|
||||
schema["tables"].append({
|
||||
"name": table,
|
||||
"columns": columns,
|
||||
"foreign_keys": fks,
|
||||
"row_count": row_count,
|
||||
"data_profile": data_profile,
|
||||
"name": table, "columns": columns, "foreign_keys": fks,
|
||||
"row_count": row_count, "data_profile": data_profile,
|
||||
})
|
||||
|
||||
conn.close()
|
||||
@@ -95,7 +72,6 @@ def extract_schema(db_path: str) -> dict[str, Any]:
|
||||
def schema_to_text(schema: dict) -> str:
|
||||
"""将 Schema 转为可读文本,供 LLM 理解"""
|
||||
lines = ["=== 数据库 Schema ===\n"]
|
||||
|
||||
for table in schema["tables"]:
|
||||
lines.append(f"📋 表: {table['name']} (共 {table['row_count']} 行)")
|
||||
lines.append(" 列:")
|
||||
@@ -103,12 +79,10 @@ def schema_to_text(schema: dict) -> str:
|
||||
pk = " [PK]" if col["is_primary_key"] else ""
|
||||
null = " NULL" if col["nullable"] else " NOT NULL"
|
||||
lines.append(f' - {col["name"]}: {col["type"]}{pk}{null}')
|
||||
|
||||
if table["foreign_keys"]:
|
||||
lines.append(" 外键:")
|
||||
for fk in table["foreign_keys"]:
|
||||
lines.append(f' - {fk["column"]} → {fk["references_table"]}.{fk["references_column"]}')
|
||||
|
||||
if table["data_profile"]:
|
||||
lines.append(" 数据画像:")
|
||||
for col_name, profile in table["data_profile"].items():
|
||||
@@ -120,7 +94,5 @@ def schema_to_text(schema: dict) -> str:
|
||||
f' - {col_name}: 范围[{profile["min"]} ~ {profile["max"]}], '
|
||||
f'均值{profile["avg"]}, {profile["distinct_count"]}个不同值'
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
93
core/utils.py
Normal file
93
core/utils.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
公共工具 —— JSON 提取、LLM 客户端单例
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import openai
|
||||
|
||||
# ── LLM 客户端单例 ──────────────────────────────────
|
||||
|
||||
_llm_client: openai.OpenAI | None = None
|
||||
_llm_model: str = ""
|
||||
|
||||
|
||||
def get_llm_client(config: dict) -> tuple[openai.OpenAI, str]:
|
||||
"""获取 LLM 客户端(单例),避免每个组件各建一个"""
|
||||
global _llm_client, _llm_model
|
||||
if _llm_client is None:
|
||||
_llm_client = openai.OpenAI(
|
||||
api_key=config["api_key"],
|
||||
base_url=config["base_url"],
|
||||
)
|
||||
_llm_model = config["model"]
|
||||
return _llm_client, _llm_model
|
||||
|
||||
|
||||
# ── JSON 提取 ────────────────────────────────────────
|
||||
|
||||
def extract_json_object(text: str) -> dict:
|
||||
"""从 LLM 输出提取 JSON 对象"""
|
||||
text = _clean_json_text(text)
|
||||
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
for pattern in [r'```json\s*\n(.*?)\n```', r'```\s*\n(.*?)\n```']:
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(_clean_json_text(match.group(1)))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(_clean_json_text(match.group()))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def extract_json_array(text: str) -> list[dict]:
|
||||
"""从 LLM 输出提取 JSON 数组(处理尾逗号、注释等)"""
|
||||
text = _clean_json_text(text)
|
||||
|
||||
try:
|
||||
result = json.loads(text)
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
for pattern in [r'```json\s*\n(.*?)\n```', r'```\s*\n(.*?)\n```']:
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
result = json.loads(_clean_json_text(match.group(1)))
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
match = re.search(r'\[.*\]', text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(_clean_json_text(match.group()))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def _clean_json_text(s: str) -> str:
|
||||
"""清理 LLM 常见的非标准 JSON"""
|
||||
s = re.sub(r'//.*?\n', '\n', s)
|
||||
s = re.sub(r'/\*.*?\*/', '', s, flags=re.DOTALL)
|
||||
s = re.sub(r',\s*([}\]])', r'\1', s)
|
||||
return s.strip()
|
||||
5
demo.py
5
demo.py
@@ -9,7 +9,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from config import DB_PATH, LLM_CONFIG
|
||||
from core.config import DB_PATH, LLM_CONFIG
|
||||
from agent import DataAnalysisAgent
|
||||
|
||||
|
||||
@@ -129,7 +129,8 @@ def main():
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(" ⬆️ AI 只看到 Schema:表结构 + 数据画像")
|
||||
print(" ⬇️ 四层架构分析:规划 → 探索 → 洞察 → 报告")
|
||||
print(" ⬇️ 四层架构分析:规划 → 预设匹配 → 探索 → 洞察 → 报告")
|
||||
print(f" 📋 已加载 {len(agent.playbook_mgr.playbooks)} 个预设剧本")
|
||||
print("=" * 60)
|
||||
|
||||
questions = [
|
||||
|
||||
238
explorer.py
238
explorer.py
@@ -1,238 +0,0 @@
|
||||
"""
|
||||
Layer 2: 自适应探索器
|
||||
基于分析计划 + 已有发现,动态决定下一步查什么。
|
||||
多轮迭代,直到 AI 判断"够了"或达到上限。
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import openai
|
||||
from config import LLM_CONFIG
|
||||
from sandbox_executor import SandboxExecutor
|
||||
|
||||
|
||||
EXPLORER_SYSTEM = """你是一个数据分析执行者。你的上级给了你一个分析计划,你需要通过迭代执行 SQL 来完成分析。
|
||||
|
||||
## 你的工作方式
|
||||
每一轮你看到:
|
||||
1. 分析计划(上级给的目标)
|
||||
2. 数据库 Schema(表结构、数据画像)
|
||||
3. 之前的探索历史(查过什么、得到什么结果)
|
||||
|
||||
你决定下一步:
|
||||
- 输出一条 SQL 继续探索
|
||||
- 或者输出 done 表示分析足够
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
```json
|
||||
{
|
||||
"action": "query",
|
||||
"reasoning": "为什么要做这个查询",
|
||||
"sql": "SELECT ...",
|
||||
"purpose": "这个查询的目的"
|
||||
}
|
||||
```
|
||||
|
||||
或:
|
||||
```json
|
||||
{
|
||||
"action": "done",
|
||||
"reasoning": "为什么分析已经足够"
|
||||
}
|
||||
```
|
||||
|
||||
## SQL 规则
|
||||
- 只用 SELECT,必须有聚合函数或 GROUP BY
|
||||
- 禁止 SELECT *
|
||||
- 用 ROUND 控制精度
|
||||
- 合理使用 LIMIT(分组结果 15 行以内,时间序列 60 行以内)
|
||||
|
||||
## 探索策略
|
||||
1. 第一轮:验证核心假设(计划中最关键的查询)
|
||||
2. 后续轮:基于已有结果追问
|
||||
- 发现离群值 → 追问为什么
|
||||
- 发现异常比例 → 追问细分维度
|
||||
- 结果平淡 → 换个角度试试
|
||||
3. 不要重复查已经确认的事
|
||||
4. 每轮要有新发现,否则就该结束"""
|
||||
|
||||
|
||||
EXPLORER_CONTINUE = """查询结果:
|
||||
|
||||
{result_text}
|
||||
|
||||
请基于这个结果决定下一步。如果发现异常或值得深挖的点,继续查询。如果分析足够,输出 done。"""
|
||||
|
||||
|
||||
class ExplorationStep:
|
||||
"""单步探索结果"""
|
||||
def __init__(self, round_num: int, decision: dict, result: dict):
|
||||
self.round_num = round_num
|
||||
self.reasoning = decision.get("reasoning", "")
|
||||
self.purpose = decision.get("purpose", "")
|
||||
self.sql = decision.get("sql", "")
|
||||
self.action = decision.get("action", "query")
|
||||
self.success = result.get("success", False)
|
||||
self.error = result.get("error")
|
||||
self.columns = result.get("columns", [])
|
||||
self.rows = result.get("rows", [])
|
||||
self.row_count = result.get("row_count", 0)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {
|
||||
"round": self.round_num,
|
||||
"action": self.action,
|
||||
"reasoning": self.reasoning,
|
||||
"purpose": self.purpose,
|
||||
"sql": self.sql,
|
||||
"success": self.success,
|
||||
}
|
||||
if self.success:
|
||||
d["result"] = {
|
||||
"columns": self.columns,
|
||||
"rows": self.rows,
|
||||
"row_count": self.row_count,
|
||||
}
|
||||
else:
|
||||
d["result"] = {"error": self.error}
|
||||
return d
|
||||
|
||||
|
||||
class Explorer:
|
||||
"""自适应探索器:多轮迭代执行 SQL"""
|
||||
|
||||
def __init__(self, executor: SandboxExecutor):
|
||||
self.executor = executor
|
||||
self.client = openai.OpenAI(
|
||||
api_key=LLM_CONFIG["api_key"],
|
||||
base_url=LLM_CONFIG["base_url"],
|
||||
)
|
||||
self.model = LLM_CONFIG["model"]
|
||||
|
||||
def explore(
|
||||
self,
|
||||
plan: dict,
|
||||
schema_text: str,
|
||||
max_rounds: int = 6,
|
||||
) -> list[ExplorationStep]:
|
||||
"""
|
||||
执行探索循环
|
||||
|
||||
Args:
|
||||
plan: Planner 生成的分析计划
|
||||
schema_text: Schema 文本描述
|
||||
max_rounds: 最大探索轮数
|
||||
|
||||
Returns:
|
||||
探步列表
|
||||
"""
|
||||
steps: list[ExplorationStep] = []
|
||||
|
||||
# 构建初始消息
|
||||
messages = [
|
||||
{"role": "system", "content": EXPLORER_SYSTEM},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"## 分析计划\n```json\n{json.dumps(plan, ensure_ascii=False, indent=2)}\n```\n\n"
|
||||
f"## 数据库 Schema\n{schema_text}\n\n"
|
||||
f"请开始第一轮探索。根据计划,先执行最关键的查询。"
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
for round_num in range(1, max_rounds + 1):
|
||||
print(f"\n 🔄 探索第 {round_num}/{max_rounds} 轮")
|
||||
|
||||
# LLM 决策
|
||||
decision = self._llm_decide(messages)
|
||||
action = decision.get("action", "query")
|
||||
reasoning = decision.get("reasoning", "")
|
||||
|
||||
print(f" 💭 {reasoning[:80]}{'...' if len(reasoning) > 80 else ''}")
|
||||
|
||||
if action == "done":
|
||||
print(f" ✅ 探索完成")
|
||||
steps.append(ExplorationStep(round_num, decision, {"success": True}))
|
||||
break
|
||||
|
||||
# 执行 SQL
|
||||
sql = decision.get("sql", "")
|
||||
purpose = decision.get("purpose", "")
|
||||
|
||||
if not sql:
|
||||
print(f" ⚠️ 未生成 SQL,跳过")
|
||||
continue
|
||||
|
||||
print(f" 📝 {purpose}")
|
||||
result = self.executor.execute(sql)
|
||||
|
||||
if result["success"]:
|
||||
print(f" ✅ {result['row_count']} 行")
|
||||
else:
|
||||
print(f" ❌ {result['error']}")
|
||||
|
||||
step = ExplorationStep(round_num, decision, result)
|
||||
steps.append(step)
|
||||
|
||||
# 更新对话历史
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": json.dumps(decision, ensure_ascii=False),
|
||||
})
|
||||
|
||||
result_text = self._format_result(result)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": EXPLORER_CONTINUE.format(result_text=result_text),
|
||||
})
|
||||
|
||||
return steps
|
||||
|
||||
def _llm_decide(self, messages: list[dict]) -> dict:
|
||||
"""LLM 决策"""
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
temperature=0.2,
|
||||
max_tokens=1024,
|
||||
)
|
||||
content = response.choices[0].message.content.strip()
|
||||
return self._extract_json(content)
|
||||
|
||||
def _format_result(self, result: dict) -> str:
|
||||
"""格式化查询结果"""
|
||||
if not result.get("success"):
|
||||
return f"❌ 执行失败: {result.get('error', '未知错误')}"
|
||||
|
||||
rows = result["rows"][:20]
|
||||
return (
|
||||
f"✅ 查询成功,返回 {result['row_count']} 行\n"
|
||||
f"列: {result['columns']}\n"
|
||||
f"数据:\n{json.dumps(rows, ensure_ascii=False, indent=2)}"
|
||||
)
|
||||
|
||||
def _extract_json(self, text: str) -> dict:
|
||||
"""从 LLM 输出提取 JSON"""
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
for pattern in [r'```json\s*\n(.*?)\n```', r'```\s*\n(.*?)\n```']:
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group(1))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return {"action": "done", "reasoning": f"无法解析输出: {text[:100]}"}
|
||||
103
import_csv.py
Normal file
103
import_csv.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
将工单 CSV 数据导入 SQLite 数据库
|
||||
"""
|
||||
import csv
|
||||
import sqlite3
|
||||
import os
|
||||
import sys
|
||||
|
||||
def import_csv(csv_path: str, db_path: str):
|
||||
"""将工单 CSV 导入 SQLite"""
|
||||
if os.path.exists(db_path):
|
||||
os.remove(db_path)
|
||||
print(f"🗑️ 已删除旧数据库: {db_path}")
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
# 创建工单表
|
||||
cur.execute("""
|
||||
CREATE TABLE tickets (
|
||||
工单号 TEXT PRIMARY KEY,
|
||||
来源 TEXT,
|
||||
创建日期 TEXT,
|
||||
问题类型 TEXT,
|
||||
问题描述 TEXT,
|
||||
处理过程 TEXT,
|
||||
跟踪记录 TEXT,
|
||||
严重程度 TEXT,
|
||||
工单状态 TEXT,
|
||||
模块 TEXT,
|
||||
责任人 TEXT,
|
||||
关闭日期 TEXT,
|
||||
车型 TEXT,
|
||||
VIN TEXT,
|
||||
SIM TEXT,
|
||||
Notes TEXT,
|
||||
Attachment TEXT,
|
||||
创建人 TEXT,
|
||||
关闭时长_天 REAL,
|
||||
创建日期_解析 TEXT,
|
||||
关闭日期_解析 TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
with open(csv_path, "r", encoding="utf-8-sig") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
|
||||
for row in rows:
|
||||
cur.execute("""
|
||||
INSERT INTO tickets VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
""", (
|
||||
row.get("工单号", ""),
|
||||
row.get("来源", ""),
|
||||
row.get("创建日期", ""),
|
||||
row.get("问题类型", ""),
|
||||
row.get("问题描述", ""),
|
||||
row.get("处理过程", ""),
|
||||
row.get("跟踪记录", ""),
|
||||
row.get("严重程度", ""),
|
||||
row.get("工单状态", ""),
|
||||
row.get("模块", ""),
|
||||
row.get("责任人", ""),
|
||||
row.get("关闭日期", ""),
|
||||
row.get("车型", ""),
|
||||
row.get("VIN", ""),
|
||||
row.get("SIM", ""),
|
||||
row.get("Notes", ""),
|
||||
row.get("Attachment", ""),
|
||||
row.get("创建人", ""),
|
||||
float(row["关闭时长(天)"]) if row.get("关闭时长(天)") else None,
|
||||
row.get("创建日期_解析", ""),
|
||||
row.get("关闭日期_解析", ""),
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
print(f"✅ 导入 {len(rows)} 条工单到 {db_path}")
|
||||
|
||||
# 验证
|
||||
cur.execute("SELECT COUNT(*) FROM tickets")
|
||||
print(f" 数据库中共 {cur.fetchone()[0]} 条记录")
|
||||
|
||||
cur.execute("SELECT DISTINCT 问题类型 FROM tickets")
|
||||
types = [r[0] for r in cur.fetchall()]
|
||||
print(f" 问题类型: {', '.join(types)}")
|
||||
|
||||
cur.execute("SELECT DISTINCT 工单状态 FROM tickets")
|
||||
statuses = [r[0] for r in cur.fetchall()]
|
||||
print(f" 工单状态: {', '.join(statuses)}")
|
||||
|
||||
cur.execute("SELECT DISTINCT 车型 FROM tickets")
|
||||
models = [r[0] for r in cur.fetchall()]
|
||||
print(f" 车型: {', '.join(models)}")
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
csv_path = sys.argv[1] if len(sys.argv) > 1 else "cleaned_data.csv"
|
||||
db_path = os.path.join(os.path.dirname(__file__), "demo.db")
|
||||
import_csv(csv_path, db_path)
|
||||
239
insights.py
239
insights.py
@@ -1,239 +0,0 @@
|
||||
"""
|
||||
Layer 3: 洞察引擎
|
||||
对探索结果进行异常检测 + 主动洞察,输出用户没问但值得知道的事。
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
from typing import Any
|
||||
|
||||
import openai
|
||||
from config import LLM_CONFIG
|
||||
from explorer import ExplorationStep
|
||||
|
||||
|
||||
INSIGHT_SYSTEM = """你是一个数据洞察专家。你会收到探索过程的所有结果,你需要:
|
||||
|
||||
1. 从结果中发现异常和有趣现象
|
||||
2. 对比不同维度,找出差异
|
||||
3. 输出用户可能没问但值得知道的洞察
|
||||
|
||||
## 输出格式(严格 JSON 数组)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "outlier" | "trend" | "distribution" | "correlation" | "recommendation",
|
||||
"severity": "high" | "medium" | "low",
|
||||
"title": "简短标题",
|
||||
"detail": "详细描述,包含具体数字",
|
||||
"evidence": "支撑这个洞察的数据来源"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 洞察类型
|
||||
- outlier: 离群值(某个分组异常高/低)
|
||||
- trend: 趋势发现(增长/下降、季节性)
|
||||
- distribution: 分布异常(不均衡、集中度过高)
|
||||
- correlation: 关联发现(两个维度的意外关联)
|
||||
- recommendation: 行动建议(基于数据的建议)
|
||||
|
||||
## 分析原则
|
||||
- 每个洞察必须有具体数字支撑
|
||||
- 用对比来说话(A 比 B 高 X%)
|
||||
- 关注异常,不描述平淡的事实
|
||||
- 如果没有异常,返回空数组"""
|
||||
|
||||
|
||||
class Insight:
|
||||
"""单条洞察"""
|
||||
def __init__(self, data: dict):
|
||||
self.type = data.get("type", "unknown")
|
||||
self.severity = data.get("severity", "low")
|
||||
self.title = data.get("title", "")
|
||||
self.detail = data.get("detail", "")
|
||||
self.evidence = data.get("evidence", "")
|
||||
|
||||
@property
|
||||
def emoji(self) -> str:
|
||||
return {
|
||||
"outlier": "⚠️",
|
||||
"trend": "📈",
|
||||
"distribution": "📊",
|
||||
"correlation": "🔗",
|
||||
"recommendation": "💡",
|
||||
}.get(self.type, "📌")
|
||||
|
||||
@property
|
||||
def severity_emoji(self) -> str:
|
||||
return {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(self.severity, "")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.emoji} {self.severity_emoji} {self.title}: {self.detail}"
|
||||
|
||||
|
||||
class InsightEngine:
|
||||
"""洞察引擎:自动检测异常 + 主动输出"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = openai.OpenAI(
|
||||
api_key=LLM_CONFIG["api_key"],
|
||||
base_url=LLM_CONFIG["base_url"],
|
||||
)
|
||||
self.model = LLM_CONFIG["model"]
|
||||
|
||||
def analyze(self, steps: list[ExplorationStep], question: str) -> list[Insight]:
|
||||
"""
|
||||
对探索结果进行洞察分析
|
||||
|
||||
Args:
|
||||
steps: 探索步骤列表
|
||||
question: 原始用户问题
|
||||
|
||||
Returns:
|
||||
洞察列表
|
||||
"""
|
||||
if not steps:
|
||||
return []
|
||||
|
||||
# 构建探索历史文本
|
||||
history = self._build_history(steps)
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": INSIGHT_SYSTEM},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"## 用户原始问题\n{question}\n\n"
|
||||
f"## 探索历史\n{history}\n\n"
|
||||
f"请分析以上数据,输出异常和洞察。"
|
||||
),
|
||||
},
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=2048,
|
||||
)
|
||||
|
||||
content = response.choices[0].message.content.strip()
|
||||
insights_data = self._extract_json_array(content)
|
||||
|
||||
return [Insight(d) for d in insights_data]
|
||||
|
||||
def format_insights(self, insights: list[Insight]) -> str:
|
||||
"""格式化洞察为可读文本"""
|
||||
if not insights:
|
||||
return ""
|
||||
|
||||
# 按严重程度排序
|
||||
severity_order = {"high": 0, "medium": 1, "low": 2}
|
||||
sorted_insights = sorted(insights, key=lambda i: severity_order.get(i.severity, 9))
|
||||
|
||||
lines = ["## 💡 主动洞察", ""]
|
||||
lines.append("_以下是你没问但数据告诉我们的事:_\n")
|
||||
|
||||
for insight in sorted_insights:
|
||||
lines.append(f"**{insight.emoji} {insight.title}** {insight.severity_emoji}")
|
||||
lines.append(f" {insight.detail}")
|
||||
lines.append(f" _数据来源: {insight.evidence}_")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_history(self, steps: list[ExplorationStep]) -> str:
|
||||
"""构建探索历史文本"""
|
||||
parts = []
|
||||
for step in steps:
|
||||
if step.action == "done":
|
||||
parts.append(f"### 结束\n{step.reasoning}")
|
||||
continue
|
||||
|
||||
if step.success:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"思考: {step.reasoning}\n"
|
||||
f"SQL: `{step.sql}`\n"
|
||||
f"结果 ({step.row_count} 行):\n"
|
||||
f"列: {step.columns}\n"
|
||||
f"数据: {json.dumps(step.rows, ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"SQL: `{step.sql}`\n"
|
||||
f"结果: 执行失败 - {step.error}"
|
||||
)
|
||||
|
||||
return "\n\n".join(parts)
|
||||
|
||||
def _extract_json_array(self, text: str) -> list[dict]:
|
||||
"""从 LLM 输出提取 JSON 数组"""
|
||||
try:
|
||||
result = json.loads(text)
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
for pattern in [r'```json\s*\n(.*?)\n```', r'```\s*\n(.*?)\n```']:
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
result = json.loads(match.group(1))
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 找最外层 []
|
||||
match = re.search(r'\[.*\]', text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return []
|
||||
|
||||
|
||||
# ── 基于规则的快速异常检测(无需 LLM)────────────────
|
||||
|
||||
def quick_detect(steps: list[ExplorationStep]) -> list[str]:
|
||||
"""
|
||||
基于规则的快速异常检测,不调 LLM。
|
||||
检测离群值、不均衡分布等。
|
||||
"""
|
||||
alerts = []
|
||||
|
||||
for step in steps:
|
||||
if not step.success or not step.rows:
|
||||
continue
|
||||
|
||||
for row in step.rows:
|
||||
for col in step.columns:
|
||||
val = row.get(col)
|
||||
if not isinstance(val, (int, float)):
|
||||
continue
|
||||
|
||||
# 检查 pct 列:某个分组占比异常
|
||||
if col.lower() in ("pct", "percent", "percentage", "占比"):
|
||||
if isinstance(val, (int, float)) and val > 50:
|
||||
alerts.append(
|
||||
f"⚠️ {step.purpose} 中某个分组占比 {val}%,超过 50%,集中度过高"
|
||||
)
|
||||
|
||||
# 检查 count 列:极值差异
|
||||
if col.lower() in ("count", "cnt", "n", "total", "order_count"):
|
||||
count_vals = [
|
||||
r.get(col) for r in step.rows
|
||||
if isinstance(r.get(col), (int, float))
|
||||
]
|
||||
if len(count_vals) >= 3 and max(count_vals) > 0:
|
||||
ratio = max(count_vals) / (sum(count_vals) / len(count_vals))
|
||||
if ratio > 3:
|
||||
alerts.append(
|
||||
f"⚠️ {step.purpose} 中最大值是均值的 {ratio:.1f} 倍,分布极不均衡"
|
||||
)
|
||||
|
||||
return alerts
|
||||
1
layers/__init__.py
Normal file
1
layers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""分析层:Planner → Playbook → Explorer → Insights → Context"""
|
||||
80
layers/context.py
Normal file
80
layers/context.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Layer 4: 上下文管理器
|
||||
"""
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from layers.explorer import ExplorationStep
|
||||
from layers.insights import Insight
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisSession:
|
||||
"""一次分析的完整记录"""
|
||||
question: str
|
||||
plan: dict
|
||||
steps: list[ExplorationStep]
|
||||
insights: list[Insight]
|
||||
report: str
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
|
||||
def summary(self) -> str:
|
||||
parts = [f"**问题**: {self.question}"]
|
||||
if self.plan:
|
||||
parts.append(f"**分析类型**: {self.plan.get('analysis_type', 'unknown')}")
|
||||
parts.append(f"**维度**: {', '.join(self.plan.get('dimensions', []))}")
|
||||
key_findings = []
|
||||
for step in self.steps:
|
||||
if step.success and step.rows:
|
||||
top_row = step.rows[0] if step.rows else {}
|
||||
finding = f"{step.purpose}: " + ", ".join(f"{k}={v}" for k, v in top_row.items() if k.lower() != "id")
|
||||
key_findings.append(finding)
|
||||
if key_findings:
|
||||
parts.append("**核心发现**:")
|
||||
for f in key_findings[:5]:
|
||||
parts.append(f" - {f}")
|
||||
if self.insights:
|
||||
parts.append("**洞察**:")
|
||||
for i in self.insights[:3]:
|
||||
parts.append(f" - {i}")
|
||||
return "\n".join(parts)
|
||||
|
||||
def to_reference_text(self) -> str:
|
||||
return (
|
||||
f"## 之前的分析\n### 问题\n{self.question}\n### 摘要\n{self.summary()}\n### 发现\n"
|
||||
+ "\n".join(f"- {s.purpose}: {s.row_count} 行" for s in self.steps if s.success)
|
||||
)
|
||||
|
||||
|
||||
class ContextManager:
|
||||
"""上下文管理器"""
|
||||
|
||||
def __init__(self, max_history: int = 10):
|
||||
self.sessions: list[AnalysisSession] = []
|
||||
self.max_history = max_history
|
||||
|
||||
def add_session(self, question: str, plan: dict, steps: list[ExplorationStep],
|
||||
insights: list[Insight], report: str) -> AnalysisSession:
|
||||
session = AnalysisSession(question=question, plan=plan, steps=steps, insights=insights, report=report)
|
||||
self.sessions.append(session)
|
||||
if len(self.sessions) > self.max_history:
|
||||
self.sessions = self.sessions[-self.max_history:]
|
||||
return session
|
||||
|
||||
def get_context_for(self, new_question: str) -> Optional[str]:
|
||||
if not self.sessions:
|
||||
return None
|
||||
return "\n\n---\n\n".join(s.to_reference_text() for s in self.sessions[-2:])
|
||||
|
||||
def get_history_summary(self) -> str:
|
||||
if not self.sessions:
|
||||
return "(无历史分析)"
|
||||
lines = [f"共 {len(self.sessions)} 次分析:"]
|
||||
for i, s in enumerate(self.sessions, 1):
|
||||
ts = time.strftime("%H:%M", time.localtime(s.timestamp))
|
||||
lines.append(f" {i}. [{ts}] {s.question}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def clear(self):
|
||||
self.sessions.clear()
|
||||
224
layers/explorer.py
Normal file
224
layers/explorer.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""
|
||||
Layer 2: 自适应探索器
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client, extract_json_object
|
||||
from core.sandbox import SandboxExecutor
|
||||
|
||||
|
||||
EXPLORER_SYSTEM = """你是一个数据分析执行者。你的上级给了你一个分析计划,你需要通过迭代执行 SQL 来完成分析。
|
||||
|
||||
## 你的工作方式
|
||||
每一轮你看到:
|
||||
1. 分析计划(上级给的目标)
|
||||
2. 数据库 Schema(表结构、数据画像)
|
||||
3. 之前的探索历史(查过什么、得到什么结果)
|
||||
|
||||
你决定下一步:
|
||||
- 输出一条 SQL 继续探索
|
||||
- 或者输出 done 表示分析足够
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
```json
|
||||
{
|
||||
"action": "query",
|
||||
"reasoning": "为什么要做这个查询",
|
||||
"sql": "SELECT ...",
|
||||
"purpose": "这个查询的目的"
|
||||
}
|
||||
```
|
||||
|
||||
或:
|
||||
```json
|
||||
{
|
||||
"action": "done",
|
||||
"reasoning": "为什么分析已经足够"
|
||||
}
|
||||
```
|
||||
|
||||
## SQL 规则(严格遵守,否则会被沙箱拒绝)
|
||||
- 只用 SELECT
|
||||
- 每条 SQL 必须包含聚合函数(COUNT/SUM/AVG/MIN/MAX)或 GROUP BY 或 LIMIT
|
||||
- 禁止 SELECT *
|
||||
- 用 ROUND 控制精度
|
||||
- 合理使用 LIMIT(分组结果 15 行以内,时间序列 60 行以内)
|
||||
- 如果需要查看明细数据,必须加 LIMIT
|
||||
|
||||
## 探索策略
|
||||
1. 第一轮:验证核心假设
|
||||
2. 后续轮:基于已有结果追问
|
||||
3. 不要重复查已经确认的事
|
||||
4. 每轮要有新发现,否则就该结束"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExplorationStep:
|
||||
"""单步探索结果"""
|
||||
round_num: int = 0
|
||||
reasoning: str = ""
|
||||
purpose: str = ""
|
||||
sql: str = ""
|
||||
action: str = "query"
|
||||
success: bool = False
|
||||
error: str | None = None
|
||||
columns: list[str] = field(default_factory=list)
|
||||
rows: list[dict] = field(default_factory=list)
|
||||
row_count: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_decision(cls, round_num: int, decision: dict, result: dict) -> "ExplorationStep":
|
||||
return cls(
|
||||
round_num=round_num,
|
||||
reasoning=decision.get("reasoning", ""),
|
||||
purpose=decision.get("purpose", ""),
|
||||
sql=decision.get("sql", ""),
|
||||
action=decision.get("action", "query"),
|
||||
success=result.get("success", False),
|
||||
error=result.get("error"),
|
||||
columns=result.get("columns", []),
|
||||
rows=result.get("rows", []),
|
||||
row_count=result.get("row_count", 0),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {
|
||||
"round": self.round_num, "action": self.action,
|
||||
"reasoning": self.reasoning, "purpose": self.purpose,
|
||||
"sql": self.sql, "success": self.success,
|
||||
}
|
||||
if self.success:
|
||||
d["result"] = {"columns": self.columns, "rows": self.rows, "row_count": self.row_count}
|
||||
else:
|
||||
d["result"] = {"error": self.error}
|
||||
return d
|
||||
|
||||
|
||||
class Explorer:
|
||||
"""自适应探索器"""
|
||||
|
||||
def __init__(self, executor: SandboxExecutor):
|
||||
self.executor = executor
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def explore(
|
||||
self, plan: dict, schema_text: str,
|
||||
max_rounds: int = 6, playbook_result: dict | None = None,
|
||||
) -> list[ExplorationStep]:
|
||||
steps: list[ExplorationStep] = []
|
||||
|
||||
# 阶段 A: 预设查询
|
||||
preset_context = ""
|
||||
if playbook_result and playbook_result.get("preset_queries"):
|
||||
preset_steps = self._run_preset_queries(playbook_result["preset_queries"])
|
||||
steps.extend(preset_steps)
|
||||
preset_context = self._build_preset_context(preset_steps, playbook_result)
|
||||
|
||||
# 阶段 B: 自适应探索
|
||||
preset_used = len([s for s in steps if s.success])
|
||||
remaining = max(1, max_rounds - preset_used)
|
||||
|
||||
initial = (
|
||||
f"## 分析计划\n```json\n{json.dumps(plan, ensure_ascii=False, indent=2)}\n```\n\n"
|
||||
f"## 数据库 Schema\n{schema_text}\n\n"
|
||||
)
|
||||
|
||||
# 注入历史上下文
|
||||
prev_context = plan.pop("_prev_context", None)
|
||||
if prev_context:
|
||||
initial += f"## 历史分析参考\n{prev_context}\n\n"
|
||||
|
||||
if preset_context:
|
||||
initial += (
|
||||
f"## 预设分析结果(已执行)\n{preset_context}\n\n"
|
||||
f"请基于这些已有数据,决定是否需要进一步探索。\n"
|
||||
f"重点关注:预设结果中的异常、值得深挖的点。\n"
|
||||
f"如果预设结果已经足够,直接输出 done。"
|
||||
)
|
||||
if playbook_result.get("exploration_hints"):
|
||||
initial += f"\n\n## 探索提示\n{playbook_result['exploration_hints']}"
|
||||
else:
|
||||
initial += "请开始第一轮探索。根据计划,先执行最关键的查询。"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": EXPLORER_SYSTEM},
|
||||
{"role": "user", "content": initial},
|
||||
]
|
||||
|
||||
offset = len(steps)
|
||||
for round_num in range(offset + 1, offset + remaining + 1):
|
||||
print(f"\n 🔄 探索第 {round_num}/{max_rounds} 轮")
|
||||
|
||||
decision = self._llm_decide(messages)
|
||||
reasoning = decision.get("reasoning", "")
|
||||
print(f" 💭 {reasoning[:80]}{'...' if len(reasoning) > 80 else ''}")
|
||||
|
||||
if decision.get("action") == "done":
|
||||
print(f" ✅ 探索完成")
|
||||
steps.append(ExplorationStep.from_decision(round_num, decision, {"success": True}))
|
||||
break
|
||||
|
||||
sql = decision.get("sql", "")
|
||||
if not sql:
|
||||
continue
|
||||
|
||||
print(f" 📝 {decision.get('purpose', '')}")
|
||||
try:
|
||||
result = self.executor.execute(sql)
|
||||
except Exception as e:
|
||||
result = {"success": False, "error": str(e), "sql": sql}
|
||||
print(f" {'✅' if result['success'] else '❌'} {result.get('row_count', result.get('error', ''))}")
|
||||
|
||||
steps.append(ExplorationStep.from_decision(round_num, decision, result))
|
||||
|
||||
messages.append({"role": "assistant", "content": json.dumps(decision, ensure_ascii=False)})
|
||||
messages.append({"role": "user", "content": self._format_result(result)})
|
||||
|
||||
return steps
|
||||
|
||||
def _run_preset_queries(self, preset_queries: list[dict]) -> list[ExplorationStep]:
|
||||
steps = []
|
||||
for i, pq in enumerate(preset_queries, 1):
|
||||
sql, purpose = pq["sql"], pq.get("purpose", f"预设查询 {i}")
|
||||
print(f"\n 📌 预设查询 {i}/{len(preset_queries)}: {purpose}")
|
||||
try:
|
||||
result = self.executor.execute(sql)
|
||||
except Exception as e:
|
||||
result = {"success": False, "error": str(e), "sql": sql}
|
||||
decision = {"action": "query", "reasoning": f"[预设] {purpose}", "sql": sql, "purpose": purpose}
|
||||
steps.append(ExplorationStep.from_decision(i, decision, result))
|
||||
print(f" {'✅' if result['success'] else '❌'} {result.get('row_count', result.get('error', ''))}")
|
||||
return steps
|
||||
|
||||
def _build_preset_context(self, steps: list[ExplorationStep], playbook_result: dict) -> str:
|
||||
parts = [f"Playbook: {playbook_result.get('playbook_name', '未知')}"]
|
||||
for step in steps:
|
||||
if step.success:
|
||||
parts.append(
|
||||
f"### {step.purpose}\nSQL: `{step.sql}`\n"
|
||||
f"结果 ({step.row_count} 行): {json.dumps(step.rows[:15], ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
parts.append(f"### {step.purpose}\nSQL: `{step.sql}`\n执行失败: {step.error}")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
def _llm_decide(self, messages: list[dict]) -> dict:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model, messages=messages, temperature=0.2, max_tokens=1024,
|
||||
)
|
||||
content = response.choices[0].message.content.strip()
|
||||
result = extract_json_object(content)
|
||||
return result if result else {"action": "done", "reasoning": f"无法解析: {content[:100]}"}
|
||||
|
||||
def _format_result(self, result: dict) -> str:
|
||||
if not result.get("success"):
|
||||
return f"❌ 执行失败: {result.get('error', '未知错误')}"
|
||||
rows = result["rows"][:20]
|
||||
return (
|
||||
f"查询结果:\n\n✅ 返回 {result['row_count']} 行\n"
|
||||
f"列: {result['columns']}\n数据:\n{json.dumps(rows, ensure_ascii=False, indent=2)}\n\n"
|
||||
f"请基于这个结果决定下一步。如果发现异常或值得深挖的点,继续查询。如果分析足够,输出 done。"
|
||||
)
|
||||
148
layers/insights.py
Normal file
148
layers/insights.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Layer 3: 洞察引擎
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client, extract_json_array
|
||||
from layers.explorer import ExplorationStep
|
||||
|
||||
|
||||
INSIGHT_SYSTEM = """你是一个数据洞察专家。你会收到探索过程的所有结果,你需要:
|
||||
|
||||
1. 从结果中发现异常和有趣现象
|
||||
2. 对比不同维度,找出差异
|
||||
3. 输出用户可能没问但值得知道的洞察
|
||||
|
||||
## 输出格式(严格 JSON 数组)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "outlier" | "trend" | "distribution" | "correlation" | "recommendation",
|
||||
"severity": "high" | "medium" | "low",
|
||||
"title": "简短标题",
|
||||
"detail": "详细描述,包含具体数字",
|
||||
"evidence": "支撑这个洞察的数据来源"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 分析原则
|
||||
- 每个洞察必须有具体数字支撑
|
||||
- 用对比来说话(A 比 B 高 X%)
|
||||
- 关注异常,不描述平淡的事实
|
||||
- 如果没有异常,返回空数组"""
|
||||
|
||||
|
||||
class Insight:
|
||||
"""单条洞察"""
|
||||
def __init__(self, data: dict):
|
||||
self.type = data.get("type", "unknown")
|
||||
self.severity = data.get("severity", "low")
|
||||
self.title = data.get("title", "")
|
||||
self.detail = data.get("detail", "")
|
||||
self.evidence = data.get("evidence", "")
|
||||
|
||||
@property
|
||||
def emoji(self) -> str:
|
||||
return {"outlier": "⚠️", "trend": "📈", "distribution": "📊",
|
||||
"correlation": "🔗", "recommendation": "💡"}.get(self.type, "📌")
|
||||
|
||||
@property
|
||||
def severity_emoji(self) -> str:
|
||||
return {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(self.severity, "")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.emoji} {self.severity_emoji} {self.title}: {self.detail}"
|
||||
|
||||
|
||||
class InsightEngine:
|
||||
"""洞察引擎"""
|
||||
|
||||
def __init__(self):
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def analyze(self, steps: list[ExplorationStep], question: str) -> list[Insight]:
|
||||
if not steps:
|
||||
return []
|
||||
|
||||
history = self._build_history(steps)
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": INSIGHT_SYSTEM},
|
||||
{"role": "user", "content": f"## 用户问题\n{question}\n\n## 探索历史\n{history}\n\n请分析以上数据,输出异常和洞察。"},
|
||||
],
|
||||
temperature=0.3, max_tokens=2048,
|
||||
)
|
||||
content = response.choices[0].message.content.strip()
|
||||
return [Insight(d) for d in extract_json_array(content)]
|
||||
|
||||
def format_insights(self, insights: list[Insight]) -> str:
|
||||
if not insights:
|
||||
return ""
|
||||
severity_order = {"high": 0, "medium": 1, "low": 2}
|
||||
sorted_insights = sorted(insights, key=lambda i: severity_order.get(i.severity, 9))
|
||||
lines = ["## 💡 主动洞察", "", "_以下是你没问但数据告诉我们的事:_\n"]
|
||||
for insight in sorted_insights:
|
||||
lines.append(f"**{insight.emoji} {insight.title}** {insight.severity_emoji}")
|
||||
lines.append(f" {insight.detail}")
|
||||
lines.append(f" _数据来源: {insight.evidence}_")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_history(self, steps: list[ExplorationStep]) -> str:
|
||||
parts = []
|
||||
for step in steps:
|
||||
if step.action == "done":
|
||||
parts.append(f"### 结束\n{step.reasoning}")
|
||||
elif step.success:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"SQL: `{step.sql}`\n结果 ({step.row_count} 行):\n"
|
||||
f"数据: {json.dumps(step.rows, ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
parts.append(f"### 第 {step.round_num} 轮:{step.purpose}\nSQL: `{step.sql}`\n失败: {step.error}")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
def quick_detect(steps: list[ExplorationStep]) -> list[str]:
|
||||
"""基于规则的快速异常检测,不调 LLM"""
|
||||
alerts = []
|
||||
seen = set() # 去重
|
||||
|
||||
for step in steps:
|
||||
if not step.success or not step.rows:
|
||||
continue
|
||||
|
||||
for col in step.columns:
|
||||
vals = [r.get(col) for r in step.rows if isinstance(r.get(col), (int, float))]
|
||||
if not vals:
|
||||
continue
|
||||
|
||||
col_lower = col.lower()
|
||||
|
||||
# 占比列:某个分组占比过高
|
||||
if col_lower in ("pct", "percent", "percentage", "占比"):
|
||||
for v in vals:
|
||||
if v > 50:
|
||||
key = f"pct_{step.purpose}"
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
alerts.append(f"⚠️ {step.purpose} 中某个分组占比 {v}%,集中度过高")
|
||||
break
|
||||
|
||||
# 计数列:极值差异
|
||||
if col_lower in ("count", "cnt", "n", "total", "order_count") and len(vals) >= 3:
|
||||
avg = sum(vals) / len(vals)
|
||||
if avg > 0:
|
||||
ratio = max(vals) / avg
|
||||
if ratio > 3:
|
||||
key = f"count_{step.purpose}"
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
alerts.append(f"⚠️ {step.purpose} 中最大值是均值的 {ratio:.1f} 倍")
|
||||
|
||||
return alerts
|
||||
74
layers/planner.py
Normal file
74
layers/planner.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Layer 1: 意图规划器
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client, extract_json_object
|
||||
|
||||
PROMPT = """你是一个数据分析规划专家。
|
||||
|
||||
## 你的任务
|
||||
根据用户的分析问题和数据库 Schema,生成一个结构化的分析计划。
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
```json
|
||||
{
|
||||
"intent": "一句话描述用户想了解什么",
|
||||
"analysis_type": "ranking" | "distribution" | "trend" | "comparison" | "anomaly" | "overview",
|
||||
"primary_table": "主要分析的表名",
|
||||
"dimensions": ["分组维度列名"],
|
||||
"metrics": ["需要聚合的数值列名"],
|
||||
"aggregations": ["SUM", "AVG", "COUNT", ...],
|
||||
"filters": [{"column": "列名", "condition": "过滤条件(可选)"}],
|
||||
"join_needed": false,
|
||||
"join_info": {"tables": [], "on": ""},
|
||||
"expected_rounds": 3,
|
||||
"rationale": "为什么这样规划,需要关注什么"
|
||||
}
|
||||
```
|
||||
|
||||
## 分析类型说明
|
||||
- ranking: 按某维度排名
|
||||
- distribution: 分布/占比
|
||||
- trend: 时间趋势
|
||||
- comparison: 对比分析
|
||||
- anomaly: 异常检测
|
||||
- overview: 全局概览
|
||||
|
||||
## 规划原则
|
||||
1. 只选择与问题相关的表和列
|
||||
2. 如果需要 JOIN,说明关联条件
|
||||
3. 预估需要几轮探索(1-6)
|
||||
4. 标注可能的异常关注点
|
||||
5. metrics 不要包含 id 列"""
|
||||
|
||||
|
||||
class Planner:
|
||||
"""意图规划器"""
|
||||
|
||||
def __init__(self):
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def plan(self, question: str, schema_text: str) -> dict[str, Any]:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": PROMPT},
|
||||
{"role": "user", "content": f"## Schema\n{schema_text}\n\n## 用户问题\n{question}"},
|
||||
],
|
||||
temperature=0.1,
|
||||
max_tokens=1024,
|
||||
)
|
||||
content = response.choices[0].message.content.strip()
|
||||
plan = extract_json_object(content)
|
||||
|
||||
if not plan:
|
||||
plan = {"intent": content[:100], "analysis_type": "overview"}
|
||||
|
||||
plan.setdefault("analysis_type", "overview")
|
||||
plan.setdefault("expected_rounds", 3)
|
||||
plan.setdefault("filters", [])
|
||||
plan.setdefault("join_needed", False)
|
||||
return plan
|
||||
179
layers/playbook.py
Normal file
179
layers/playbook.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Layer 1.5: 预设分析剧本
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client, extract_json_object, extract_json_array
|
||||
|
||||
|
||||
class Playbook:
|
||||
"""一个预设分析剧本"""
|
||||
def __init__(self, data: dict):
|
||||
self.name = data["name"]
|
||||
self.description = data["description"]
|
||||
self.tags = data.get("tags", [])
|
||||
self.preset_queries: list[dict] = data.get("preset_queries", [])
|
||||
self.exploration_hints = data.get("exploration_hints", "")
|
||||
self.placeholders = data.get("placeholders", {})
|
||||
|
||||
def to_summary(self) -> str:
|
||||
return f"[{self.name}] {self.description} (标签: {', '.join(self.tags)})"
|
||||
|
||||
def render_queries(self, schema: dict) -> list[dict]:
|
||||
rendered = []
|
||||
for q in self.preset_queries:
|
||||
sql, purpose = q["sql"], q.get("purpose", "")
|
||||
for key, val in self.placeholders.items():
|
||||
sql = sql.replace(f"{{{{{key}}}}}", val)
|
||||
purpose = purpose.replace(f"{{{{{key}}}}}", val)
|
||||
rendered.append({"sql": sql, "purpose": purpose})
|
||||
return rendered
|
||||
|
||||
|
||||
class PlaybookManager:
|
||||
"""加载和匹配 Playbook"""
|
||||
|
||||
def __init__(self, playbook_dir: str = ""):
|
||||
self.playbooks: list[Playbook] = []
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
if playbook_dir and os.path.isdir(playbook_dir):
|
||||
self._load_from_dir(playbook_dir)
|
||||
|
||||
def _load_from_dir(self, dir_path: str):
|
||||
for fname in sorted(os.listdir(dir_path)):
|
||||
if not fname.endswith(".json"):
|
||||
continue
|
||||
try:
|
||||
with open(os.path.join(dir_path, fname), "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
items = data if isinstance(data, list) else [data]
|
||||
for item in items:
|
||||
self.playbooks.append(Playbook(item))
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
print(f" ⚠️ 加载 playbook 失败 {fname}: {e}")
|
||||
|
||||
def add(self, playbook: Playbook):
|
||||
self.playbooks.append(playbook)
|
||||
|
||||
def auto_generate(self, schema_text: str, save_dir: str = "") -> list[Playbook]:
|
||||
"""让 LLM 根据 Schema 自动生成 Playbook"""
|
||||
prompt = f"""你是一个数据分析专家。根据以下数据库 Schema,生成 3-5 个预设分析剧本。
|
||||
|
||||
## 数据库 Schema
|
||||
{schema_text}
|
||||
|
||||
## 输出格式(严格 JSON 数组)
|
||||
```json
|
||||
[
|
||||
{{
|
||||
"name": "剧本名称",
|
||||
"description": "一句话描述",
|
||||
"tags": ["关键词1", "关键词2"],
|
||||
"preset_queries": [
|
||||
{{"purpose": "查询目的", "sql": "SELECT ... GROUP BY ..."}}
|
||||
],
|
||||
"exploration_hints": "后续探索提示"
|
||||
}}
|
||||
]
|
||||
```
|
||||
|
||||
## SQL 规则
|
||||
- 只用 SELECT,必须有聚合函数或 GROUP BY
|
||||
- 禁止 SELECT *,用 ROUND 控制精度,合理 LIMIT
|
||||
- 直接使用实际表名和列名"""
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是数据分析专家。只输出 JSON,不要其他内容。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.3, max_tokens=4096,
|
||||
)
|
||||
content = response.choices[0].message.content.strip()
|
||||
playbooks_data = extract_json_array(content)
|
||||
if not playbooks_data:
|
||||
return []
|
||||
|
||||
generated = []
|
||||
for i, pb_data in enumerate(playbooks_data):
|
||||
pb_data.setdefault("tags", [])
|
||||
pb_data.setdefault("exploration_hints", "")
|
||||
pb_data.setdefault("placeholders", {})
|
||||
try:
|
||||
pb = Playbook(pb_data)
|
||||
self.playbooks.append(pb)
|
||||
generated.append(pb)
|
||||
if save_dir:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
safe = re.sub(r'[^\w\u4e00-\u9fff]', '_', pb.name)[:30]
|
||||
fpath = os.path.join(save_dir, f"auto_{i+1}_{safe}.json")
|
||||
with open(fpath, "w", encoding="utf-8") as f:
|
||||
json.dump(pb_data, f, ensure_ascii=False, indent=2)
|
||||
except (KeyError, TypeError) as e:
|
||||
print(f" ⚠️ 跳过无效 Playbook: {e}")
|
||||
return generated
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 自动生成 Playbook 出错: {e}")
|
||||
return []
|
||||
|
||||
def match(self, plan: dict, schema_text: str) -> Optional[dict]:
|
||||
"""用 LLM 判断当前分析计划是否匹配某个 Playbook"""
|
||||
if not self.playbooks:
|
||||
return None
|
||||
|
||||
pb_summaries = []
|
||||
for i, pb in enumerate(self.playbooks):
|
||||
queries_desc = "\n".join(f" - {q.get('purpose', '')}: {q['sql'][:100]}" for q in pb.preset_queries)
|
||||
pb_summaries.append(f"{i+1}. {pb.to_summary()}\n 预设查询:\n{queries_desc}")
|
||||
|
||||
prompt = f"""判断当前分析计划是否适合使用某个预设剧本。
|
||||
|
||||
## 分析计划
|
||||
```json
|
||||
{json.dumps(plan, ensure_ascii=False, indent=2)}
|
||||
```
|
||||
|
||||
## Schema
|
||||
{schema_text}
|
||||
|
||||
## 可用剧本
|
||||
{chr(10).join(pb_summaries)}
|
||||
|
||||
## 输出(严格 JSON)
|
||||
匹配: {{"matched": true, "playbook_index": 1, "reasoning": "原因", "placeholders": {{}}}}
|
||||
不匹配: {{"matched": false, "reasoning": "原因"}}"""
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是分析计划匹配器。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.1, max_tokens=512,
|
||||
)
|
||||
result = extract_json_object(response.choices[0].message.content.strip())
|
||||
if not result.get("matched"):
|
||||
return None
|
||||
|
||||
idx = result.get("playbook_index", 1) - 1
|
||||
if idx < 0 or idx >= len(self.playbooks):
|
||||
return None
|
||||
|
||||
pb = self.playbooks[idx]
|
||||
pb.placeholders = {**pb.placeholders, **result.get("placeholders", {})}
|
||||
return {
|
||||
"matched": True, "playbook_name": pb.name,
|
||||
"reasoning": result.get("reasoning", ""),
|
||||
"preset_queries": pb.render_queries({}),
|
||||
"exploration_hints": pb.exploration_hints,
|
||||
}
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Playbook 匹配出错: {e}")
|
||||
return None
|
||||
1
output/__init__.py
Normal file
1
output/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""输出层:报告、图表、整合"""
|
||||
247
output/chart.py
Normal file
247
output/chart.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
图表生成器 —— 根据探索结果自动生成可视化图表
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.font_manager as fm
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client, extract_json_array
|
||||
from layers.explorer import ExplorationStep
|
||||
|
||||
|
||||
def _setup_chinese_font():
|
||||
candidates = [
|
||||
"SimHei", "Microsoft YaHei", "STHeiti", "WenQuanYi Micro Hei",
|
||||
"Noto Sans CJK SC", "PingFang SC", "Source Han Sans CN",
|
||||
]
|
||||
available = {f.name for f in fm.fontManager.ttflist}
|
||||
for font in candidates:
|
||||
if font in available:
|
||||
plt.rcParams["font.sans-serif"] = [font]
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
return font
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
return None
|
||||
|
||||
_setup_chinese_font()
|
||||
|
||||
|
||||
CHART_PLAN_PROMPT = """你是一个数据可视化专家。根据以下分析结果,规划需要生成的图表。
|
||||
|
||||
## 探索结果
|
||||
{exploration_summary}
|
||||
|
||||
## 规划规则
|
||||
1. 每个有意义的查询结果生成 1 张图,最多 5 张
|
||||
2. 图表类型:bar / horizontal_bar / pie / line / stacked_bar
|
||||
3. 跳过数据量太少(<2 行)的结果
|
||||
4. 标题要简洁
|
||||
|
||||
## 输出格式(纯 JSON 数组,不要代码块)
|
||||
[
|
||||
{{
|
||||
"step_index": 0,
|
||||
"chart_type": "bar",
|
||||
"title": "图表标题",
|
||||
"x_column": "分类轴列名",
|
||||
"y_column": "数值轴列名",
|
||||
"y2_column": null,
|
||||
"top_n": 10,
|
||||
"sort_desc": true
|
||||
}}
|
||||
]"""
|
||||
|
||||
|
||||
class ChartGenerator:
|
||||
"""图表生成器"""
|
||||
|
||||
def __init__(self, output_dir: str = "charts"):
|
||||
self.output_dir = output_dir
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def generate(self, steps: list[ExplorationStep], question: str) -> list[dict]:
|
||||
valid_steps = [(i, s) for i, s in enumerate(steps) if s.success and s.rows and s.row_count >= 2 and s.action != "done"]
|
||||
if not valid_steps:
|
||||
return []
|
||||
|
||||
plans = self._plan_charts(valid_steps, question)
|
||||
if not plans:
|
||||
return []
|
||||
|
||||
self._clean_old_charts()
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
|
||||
charts = []
|
||||
for i, plan in enumerate(plans):
|
||||
try:
|
||||
path = self._render_chart(plan, steps, i)
|
||||
if path:
|
||||
charts.append({"path": path, "title": plan.get("title", f"图表 {i+1}")})
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 图表生成失败: {e}")
|
||||
return charts
|
||||
|
||||
def _plan_charts(self, valid_steps: list[tuple[int, ExplorationStep]], question: str) -> list[dict]:
|
||||
summary_parts = []
|
||||
for idx, step in valid_steps:
|
||||
summary_parts.append(
|
||||
f"### 步骤 {idx}: {step.purpose}\n列: {step.columns}\n行数: {step.row_count}\n"
|
||||
f"前 5 行: {json.dumps(step.rows[:5], ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是数据可视化专家。只输出纯 JSON 数组,不要 markdown 代码块。"},
|
||||
{"role": "user", "content": CHART_PLAN_PROMPT.format(exploration_summary="\n\n".join(summary_parts))},
|
||||
],
|
||||
temperature=0.1, max_tokens=1024,
|
||||
)
|
||||
plans = extract_json_array(response.choices[0].message.content.strip())
|
||||
return plans if plans else self._fallback_plan(valid_steps)
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 图表规划失败: {e},使用 fallback")
|
||||
return self._fallback_plan(valid_steps)
|
||||
|
||||
def _fallback_plan(self, valid_steps: list[tuple[int, ExplorationStep]]) -> list[dict]:
|
||||
plans = []
|
||||
for idx, step in valid_steps[:4]:
|
||||
if len(step.columns) < 2 or step.row_count < 2:
|
||||
continue
|
||||
x_col = step.columns[0]
|
||||
y_col = None
|
||||
for col in step.columns[1:]:
|
||||
if isinstance(step.rows[0].get(col), (int, float)):
|
||||
y_col = col
|
||||
break
|
||||
if not y_col:
|
||||
continue
|
||||
|
||||
chart_type = "bar"
|
||||
if any(kw in x_col for kw in ("月", "日期", "时间", "month", "date")):
|
||||
chart_type = "line"
|
||||
elif step.row_count <= 6:
|
||||
chart_type = "pie"
|
||||
elif len(str(step.rows[0].get(x_col, ""))) > 10:
|
||||
chart_type = "horizontal_bar"
|
||||
|
||||
plans.append({
|
||||
"step_index": idx, "chart_type": chart_type,
|
||||
"title": f"各{x_col}的{y_col}",
|
||||
"x_column": x_col, "y_column": y_col,
|
||||
"y2_column": None, "top_n": 10,
|
||||
"sort_desc": chart_type != "line",
|
||||
})
|
||||
return plans
|
||||
|
||||
def _render_chart(self, plan: dict, steps: list[ExplorationStep], chart_idx: int) -> str | None:
|
||||
step_idx = plan.get("step_index", 0)
|
||||
if step_idx >= len(steps):
|
||||
return None
|
||||
step = steps[step_idx]
|
||||
if not step.success or not step.rows:
|
||||
return None
|
||||
|
||||
chart_type = plan.get("chart_type", "bar")
|
||||
title = plan.get("title", f"图表 {chart_idx + 1}")
|
||||
x_col, y_col = plan.get("x_column", ""), plan.get("y_column", "")
|
||||
y2_col = plan.get("y2_column")
|
||||
top_n = plan.get("top_n", 15)
|
||||
sort_desc = plan.get("sort_desc", True)
|
||||
|
||||
rows = step.rows[:top_n] if top_n else step.rows
|
||||
x_vals = [str(r.get(x_col, "")) for r in rows]
|
||||
y_vals = [self._to_number(r.get(y_col, 0)) for r in rows]
|
||||
|
||||
if sort_desc and chart_type not in ("line",):
|
||||
paired = sorted(zip(x_vals, y_vals), key=lambda p: p[1], reverse=True)
|
||||
x_vals, y_vals = zip(*paired) if paired else ([], [])
|
||||
|
||||
if not x_vals or not y_vals:
|
||||
return None
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
|
||||
if chart_type == "bar":
|
||||
bars = ax.bar(range(len(x_vals)), y_vals, color="#4C78A8")
|
||||
ax.set_xticks(range(len(x_vals)))
|
||||
ax.set_xticklabels(x_vals, rotation=45, ha="right", fontsize=9)
|
||||
self._add_bar_labels(ax, bars)
|
||||
elif chart_type == "horizontal_bar":
|
||||
bars = ax.barh(range(len(x_vals)), y_vals, color="#4C78A8")
|
||||
ax.set_yticks(range(len(x_vals)))
|
||||
ax.set_yticklabels(x_vals, fontsize=9)
|
||||
ax.invert_yaxis()
|
||||
elif chart_type == "pie":
|
||||
filtered = [(x, y) for x, y in zip(x_vals, y_vals) if y > 0]
|
||||
if not filtered:
|
||||
plt.close(fig)
|
||||
return None
|
||||
x_vals, y_vals = zip(*filtered)
|
||||
ax.pie(y_vals, labels=x_vals, autopct="%1.1f%%", startangle=90, textprops={"fontsize": 9})
|
||||
elif chart_type == "line":
|
||||
ax.plot(range(len(x_vals)), y_vals, marker="o", color="#4C78A8", linewidth=2)
|
||||
ax.set_xticks(range(len(x_vals)))
|
||||
ax.set_xticklabels(x_vals, rotation=45, ha="right", fontsize=9)
|
||||
ax.fill_between(range(len(x_vals)), y_vals, alpha=0.1, color="#4C78A8")
|
||||
if y2_col:
|
||||
y2_vals = [self._to_number(r.get(y2_col, 0)) for r in rows]
|
||||
ax2 = ax.twinx()
|
||||
ax2.plot(range(len(x_vals)), y2_vals, marker="s", color="#E45756", linewidth=2, linestyle="--")
|
||||
ax2.set_ylabel(y2_col, fontsize=10, color="#E45756")
|
||||
elif chart_type == "stacked_bar":
|
||||
ax.bar(range(len(x_vals)), y_vals, label=y_col, color="#4C78A8")
|
||||
if y2_col:
|
||||
y2_vals = [self._to_number(r.get(y2_col, 0)) for r in rows]
|
||||
ax.bar(range(len(x_vals)), y2_vals, bottom=y_vals, label=y2_col, color="#E45756")
|
||||
ax.set_xticks(range(len(x_vals)))
|
||||
ax.set_xticklabels(x_vals, rotation=45, ha="right", fontsize=9)
|
||||
ax.legend()
|
||||
|
||||
ax.set_title(title, fontsize=13, fontweight="bold", pad=12)
|
||||
if chart_type not in ("pie",):
|
||||
ax.set_xlabel(x_col, fontsize=10)
|
||||
if chart_type != "horizontal_bar":
|
||||
ax.set_ylabel(y_col, fontsize=10)
|
||||
ax.grid(axis="y", alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
fname = f"chart_{chart_idx + 1}.png"
|
||||
fpath = os.path.join(self.output_dir, fname)
|
||||
fig.savefig(fpath, dpi=150, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
return fpath
|
||||
|
||||
def _clean_old_charts(self):
|
||||
if os.path.isdir(self.output_dir):
|
||||
for f in os.listdir(self.output_dir):
|
||||
if f.endswith(".png"):
|
||||
try:
|
||||
os.remove(os.path.join(self.output_dir, f))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _add_bar_labels(self, ax, bars):
|
||||
for bar in bars:
|
||||
h = bar.get_height()
|
||||
if h > 0:
|
||||
label = f"{h:.1f}" if isinstance(h, float) else str(int(h))
|
||||
ax.text(bar.get_x() + bar.get_width() / 2, h, label, ha="center", va="bottom", fontsize=8)
|
||||
|
||||
def _to_number(self, val) -> float:
|
||||
if isinstance(val, (int, float)):
|
||||
return float(val)
|
||||
if isinstance(val, str):
|
||||
try:
|
||||
return float(val.replace("<", "").replace(",", "").strip())
|
||||
except ValueError:
|
||||
return 0.0
|
||||
return 0.0
|
||||
84
output/consolidator.py
Normal file
84
output/consolidator.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
报告整合器 —— 将多次分析结果合并为一份完整报告
|
||||
"""
|
||||
import json
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client
|
||||
from layers.context import AnalysisSession
|
||||
|
||||
|
||||
CONSOLIDATE_PROMPT = """你是一个高级数据分析总监。下面是你的团队针对同一份数据做的多次分析,请整合为一份完整的综合报告。
|
||||
|
||||
## 核心问题
|
||||
{question}
|
||||
|
||||
## 各次分析结果
|
||||
{sections}
|
||||
|
||||
## 可用图表
|
||||
{charts_text}
|
||||
|
||||
## 整合要求
|
||||
1. **执行摘要**:3-5 句话概括全局结论
|
||||
2. **核心发现**:从所有分析中提炼最重要的发现,去重,按重要性排列
|
||||
3. **交叉洞察**:不同维度之间的关联
|
||||
4. **图表引用**:用 `` 嵌入相关段落
|
||||
5. **风险与建议**:按优先级排列
|
||||
6. **数据附录**:关键统计数字
|
||||
|
||||
中文,专业简报风格。先结论后细节。"""
|
||||
|
||||
|
||||
class ReportConsolidator:
|
||||
"""报告整合器"""
|
||||
|
||||
def __init__(self):
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def consolidate(self, sessions: list[AnalysisSession], question: str = "",
|
||||
charts: list[dict] | None = None) -> str:
|
||||
if not sessions:
|
||||
return "(无分析数据可整合)"
|
||||
if not question:
|
||||
question = sessions[0].question
|
||||
|
||||
sections = self._build_sections(sessions)
|
||||
charts_text = "\n".join(f"{i}. {c['title']}: {c['path']}" for i, c in enumerate(charts or [], 1)) or "无图表。"
|
||||
|
||||
try:
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是高级数据分析总监,整合多维度分析结果。"},
|
||||
{"role": "user", "content": CONSOLIDATE_PROMPT.format(question=question, sections=sections, charts_text=charts_text)},
|
||||
],
|
||||
temperature=0.3, max_tokens=4096,
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
print(f" ⚠️ LLM 整合失败: {e},使用拼接模式")
|
||||
return self._fallback_concat(sessions, charts)
|
||||
|
||||
def _build_sections(self, sessions: list[AnalysisSession]) -> str:
|
||||
parts = []
|
||||
for i, session in enumerate(sessions, 1):
|
||||
section = f"### 分析 {i}: {session.question}\n"
|
||||
section += f"类型: {session.plan.get('analysis_type', '未知')}\n\n"
|
||||
for step in session.steps:
|
||||
if not step.success or not step.rows or step.action == "done":
|
||||
continue
|
||||
section += f"- {step.purpose} ({step.row_count} 行)\n"
|
||||
section += f" 数据: {json.dumps(step.rows[:8], ensure_ascii=False)}\n\n"
|
||||
if session.insights:
|
||||
section += "#### 洞察\n" + "\n".join(f"- {i}" for i in session.insights) + "\n"
|
||||
parts.append(section)
|
||||
return "\n---\n".join(parts)
|
||||
|
||||
def _fallback_concat(self, sessions: list[AnalysisSession], charts: list[dict] | None) -> str:
|
||||
parts = ["# 综合分析报告\n"]
|
||||
for i, s in enumerate(sessions, 1):
|
||||
parts.append(f"## 第 {i} 部分: {s.question}\n{s.report}\n")
|
||||
if charts:
|
||||
parts.append("\n## 可视化\n" + "\n".join(f"![{c['title']}]({c['path']})" for c in charts))
|
||||
return "\n".join(parts)
|
||||
84
output/reporter.py
Normal file
84
output/reporter.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
报告生成器 —— 单次分析报告
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from core.config import LLM_CONFIG
|
||||
from core.utils import get_llm_client
|
||||
from layers.explorer import ExplorationStep
|
||||
from layers.insights import Insight
|
||||
|
||||
|
||||
REPORT_PROMPT = """你是一个数据分析报告撰写专家。基于以下信息撰写报告。
|
||||
|
||||
## 用户问题
|
||||
{question}
|
||||
|
||||
## 分析计划
|
||||
{plan}
|
||||
|
||||
## 探索过程
|
||||
{exploration}
|
||||
|
||||
## 主动洞察
|
||||
{insights_text}
|
||||
|
||||
## 可用图表
|
||||
{charts_text}
|
||||
|
||||
## 撰写要求
|
||||
1. **开头**:一句话总结核心结论
|
||||
2. **核心发现**:按重要性排列,带具体数字
|
||||
3. **图表引用**:用 `` 嵌入到相关段落
|
||||
4. **深入洞察**:异常、趋势、关联
|
||||
5. **建议**:基于数据的行动建议
|
||||
6. **审计**:末尾附上所有 SQL
|
||||
|
||||
中文,专业简报风格。图表自然嵌入对应段落。"""
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""报告生成器"""
|
||||
|
||||
def __init__(self):
|
||||
self.client, self.model = get_llm_client(LLM_CONFIG)
|
||||
|
||||
def generate(self, question: str, plan: dict, steps: list[ExplorationStep],
|
||||
insights: list[Insight], charts: list[dict] | None = None) -> str:
|
||||
exploration = self._build_exploration(steps)
|
||||
insights_text = "\n".join(str(i) for i in insights) if insights else "未检测到异常。"
|
||||
charts_text = "\n".join(f"{i}. 标题: {c['title']}, 路径: {c['path']}" for i, c in enumerate(charts or [], 1)) or "无图表。"
|
||||
|
||||
prompt = REPORT_PROMPT.format(
|
||||
question=question,
|
||||
plan=json.dumps(plan, ensure_ascii=False, indent=2),
|
||||
exploration=exploration,
|
||||
insights_text=insights_text,
|
||||
charts_text=charts_text,
|
||||
)
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的数据分析师,撰写清晰、有洞察力的分析报告。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.3, max_tokens=4096,
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
|
||||
def _build_exploration(self, steps: list[ExplorationStep]) -> str:
|
||||
parts = []
|
||||
for step in steps:
|
||||
if step.action == "done":
|
||||
parts.append(f"### 结束\n{step.reasoning}")
|
||||
elif step.success:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"SQL: `{step.sql}`\n结果 ({step.row_count} 行):\n"
|
||||
f"数据: {json.dumps(step.rows, ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
parts.append(f"### 第 {step.round_num} 轮:{step.purpose}\nSQL: `{step.sql}`\n失败: {step.error}")
|
||||
return "\n\n".join(parts) if parts else "无探索步骤"
|
||||
129
planner.py
129
planner.py
@@ -1,129 +0,0 @@
|
||||
"""
|
||||
Layer 1: 意图规划器
|
||||
将用户问题解析为结构化的分析计划,替代硬编码模板。
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
PROMPT = """你是一个数据分析规划专家。
|
||||
|
||||
## 你的任务
|
||||
根据用户的分析问题和数据库 Schema,生成一个结构化的分析计划。
|
||||
|
||||
## 输出格式(严格 JSON)
|
||||
```json
|
||||
{
|
||||
"intent": "一句话描述用户想了解什么",
|
||||
"analysis_type": "ranking" | "distribution" | "trend" | "comparison" | "anomaly" | "overview",
|
||||
"primary_table": "主要分析的表名",
|
||||
"dimensions": ["分组维度列名"],
|
||||
"metrics": ["需要聚合的数值列名"],
|
||||
"aggregations": ["SUM", "AVG", "COUNT", ...],
|
||||
"filters": [{"column": "列名", "condition": "过滤条件(可选)"}],
|
||||
"join_needed": false,
|
||||
"join_info": {"tables": [], "on": ""},
|
||||
"expected_rounds": 3,
|
||||
"rationale": "为什么这样规划,需要关注什么"
|
||||
}
|
||||
```
|
||||
|
||||
## 分析类型说明
|
||||
- ranking: 按某维度排名(哪个地区最高)
|
||||
- distribution: 分布/占比(各地区占比多少)
|
||||
- trend: 时间趋势(月度变化)
|
||||
- comparison: 对比分析(A vs B)
|
||||
- anomaly: 异常检测(有没有异常值)
|
||||
- overview: 全局概览(整体情况如何)
|
||||
|
||||
## 规划原则
|
||||
1. 只选择与问题相关的表和列
|
||||
2. 如果需要 JOIN,说明关联条件
|
||||
3. 预估需要几轮探索(1-6)
|
||||
4. 标注可能的异常关注点
|
||||
5. metrics 不要包含 id 列"""
|
||||
|
||||
import openai
|
||||
from config import LLM_CONFIG
|
||||
|
||||
|
||||
class Planner:
|
||||
"""意图规划器:将自然语言问题转为结构化分析计划"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = openai.OpenAI(
|
||||
api_key=LLM_CONFIG["api_key"],
|
||||
base_url=LLM_CONFIG["base_url"],
|
||||
)
|
||||
self.model = LLM_CONFIG["model"]
|
||||
|
||||
def plan(self, question: str, schema_text: str) -> dict[str, Any]:
|
||||
"""
|
||||
生成分析计划
|
||||
|
||||
Returns:
|
||||
{
|
||||
"intent": str,
|
||||
"analysis_type": str,
|
||||
"primary_table": str,
|
||||
"dimensions": [str],
|
||||
"metrics": [str],
|
||||
"aggregations": [str],
|
||||
"filters": [dict],
|
||||
"join_needed": bool,
|
||||
"join_info": dict,
|
||||
"expected_rounds": int,
|
||||
"rationale": str,
|
||||
}
|
||||
"""
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": PROMPT},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"## Schema\n{schema_text}\n\n"
|
||||
f"## 用户问题\n{question}"
|
||||
),
|
||||
},
|
||||
],
|
||||
temperature=0.1,
|
||||
max_tokens=1024,
|
||||
)
|
||||
|
||||
content = response.choices[0].message.content.strip()
|
||||
plan = self._extract_json(content)
|
||||
|
||||
# 补充默认值
|
||||
plan.setdefault("analysis_type", "overview")
|
||||
plan.setdefault("expected_rounds", 3)
|
||||
plan.setdefault("filters", [])
|
||||
plan.setdefault("join_needed", False)
|
||||
|
||||
return plan
|
||||
|
||||
def _extract_json(self, text: str) -> dict:
|
||||
"""从 LLM 输出提取 JSON"""
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
for pattern in [r'```json\s*\n(.*?)\n```', r'```\s*\n(.*?)\n```']:
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group(1))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 找最外层 {}
|
||||
match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', text, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return {"intent": text[:100], "analysis_type": "overview"}
|
||||
22
playbooks/auto_1_工单处理效率分析.json
Normal file
22
playbooks/auto_1_工单处理效率分析.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "工单处理效率分析",
|
||||
"description": "分析不同问题类型和模块的工单关闭时长,识别处理瓶颈。",
|
||||
"tags": [
|
||||
"关闭时长",
|
||||
"问题类型",
|
||||
"模块",
|
||||
"效率"
|
||||
],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "计算每个问题类型的平均关闭时长,识别处理最慢的问题类型。",
|
||||
"sql": "SELECT 问题类型, ROUND(AVG(关闭时长_天), 2) AS 平均关闭时长 FROM tickets GROUP BY 问题类型 ORDER BY 平均关闭时长 DESC LIMIT 10"
|
||||
},
|
||||
{
|
||||
"purpose": "计算每个模块的平均关闭时长,识别处理最慢的模块。",
|
||||
"sql": "SELECT 模块, ROUND(AVG(关闭时长_天), 2) AS 平均关闭时长 FROM tickets GROUP BY 模块 ORDER BY 平均关闭时长 DESC LIMIT 10"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "查看平均关闭时长高的问题类型和模块,结合跟踪记录和处理过程,分析是否存在流程问题或资源分配不均。关注关闭时长分布的异常值。",
|
||||
"placeholders": {}
|
||||
}
|
||||
22
playbooks/auto_2_工单来源与状态分布.json
Normal file
22
playbooks/auto_2_工单来源与状态分布.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "工单来源与状态分布",
|
||||
"description": "分析不同来源的工单状态分布,评估渠道效果。",
|
||||
"tags": [
|
||||
"来源",
|
||||
"工单状态",
|
||||
"分布",
|
||||
"渠道"
|
||||
],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "统计每个来源的工单数量及状态分布。",
|
||||
"sql": "SELECT 来源, 工单状态, COUNT(*) AS 工单数量 FROM tickets GROUP BY 来源, 工单状态 ORDER BY 来源, 工单状态"
|
||||
},
|
||||
{
|
||||
"purpose": "计算每个来源的工单关闭比例。",
|
||||
"sql": "SELECT 来源, ROUND(SUM(CASE WHEN 工单状态 = 'close' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS 关闭比例 FROM tickets GROUP BY 来源 ORDER BY 关闭比例 DESC"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "比较不同来源的关闭比例,识别效果最佳的渠道。分析状态为'temporary close'的工单特征,如问题类型或责任人,以优化处理流程。",
|
||||
"placeholders": {}
|
||||
}
|
||||
22
playbooks/auto_3_责任人绩效分析.json
Normal file
22
playbooks/auto_3_责任人绩效分析.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "责任人绩效分析",
|
||||
"description": "分析不同责任人的工单处理数量和平均关闭时长。",
|
||||
"tags": [
|
||||
"责任人",
|
||||
"绩效",
|
||||
"处理数量",
|
||||
"关闭时长"
|
||||
],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "统计每个责任人的工单处理数量。",
|
||||
"sql": "SELECT 责任人, COUNT(*) AS 处理工单数量 FROM tickets GROUP BY 责任人 ORDER BY 处理工单数量 DESC LIMIT 10"
|
||||
},
|
||||
{
|
||||
"purpose": "计算每个责任人的平均关闭时长。",
|
||||
"sql": "SELECT 责任人, ROUND(AVG(关闭时长_天), 2) AS 平均关闭时长 FROM tickets GROUP BY 责任人 ORDER BY 平均关闭时长 DESC LIMIT 10"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "结合处理数量和平均关闭时长,评估责任人的效率。关注处理数量高但关闭时长也高的责任人,可能存在工作负荷过重或技能不足的问题。",
|
||||
"placeholders": {}
|
||||
}
|
||||
22
playbooks/auto_4_车型与问题关联分析.json
Normal file
22
playbooks/auto_4_车型与问题关联分析.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "车型与问题关联分析",
|
||||
"description": "分析不同车型的工单问题类型分布,识别车型特有问题。",
|
||||
"tags": [
|
||||
"车型",
|
||||
"问题类型",
|
||||
"关联",
|
||||
"分布"
|
||||
],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "统计每个车型的工单数量及主要问题类型。",
|
||||
"sql": "SELECT 车型, 问题类型, COUNT(*) AS 工单数量 FROM tickets GROUP BY 车型, 问题类型 ORDER BY 车型, 工单数量 DESC"
|
||||
},
|
||||
{
|
||||
"purpose": "计算每个车型的平均关闭时长,识别处理难度高的车型。",
|
||||
"sql": "SELECT 车型, ROUND(AVG(关闭时长_天), 2) AS 平均关闭时长 FROM tickets GROUP BY 车型 ORDER BY 平均关闭时长 DESC LIMIT 10"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "分析特定车型的高频问题类型,结合问题描述和跟踪记录,识别车型设计或软件问题。关注平均关闭时长高的车型,分析是否存在共性问题。",
|
||||
"placeholders": {}
|
||||
}
|
||||
22
playbooks/auto_5_时间趋势分析.json
Normal file
22
playbooks/auto_5_时间趋势分析.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "时间趋势分析",
|
||||
"description": "分析工单创建和关闭的时间趋势,识别高峰期和处理延迟。",
|
||||
"tags": [
|
||||
"时间趋势",
|
||||
"创建日期",
|
||||
"关闭日期",
|
||||
"高峰期"
|
||||
],
|
||||
"preset_queries": [
|
||||
{
|
||||
"purpose": "统计每月创建的工单数量,识别高峰期。",
|
||||
"sql": "SELECT SUBSTR(创建日期_解析, 1, 7) AS 月份, COUNT(*) AS 创建工单数量 FROM tickets GROUP BY 月份 ORDER BY 月份"
|
||||
},
|
||||
{
|
||||
"purpose": "计算每月关闭的工单平均关闭时长,识别处理延迟趋势。",
|
||||
"sql": "SELECT SUBSTR(关闭日期_解析, 1, 7) AS 月份, ROUND(AVG(关闭时长_天), 2) AS 平均关闭时长 FROM tickets WHERE 关闭日期_解析 IS NOT NULL GROUP BY 月份 ORDER BY 月份"
|
||||
}
|
||||
],
|
||||
"exploration_hints": "比较创建高峰期和关闭时长趋势,分析是否存在资源调配问题。关注特定月份的异常值,如关闭时长突然增加,结合跟踪记录查找原因。",
|
||||
"placeholders": {}
|
||||
}
|
||||
110
reporter.py
110
reporter.py
@@ -1,110 +0,0 @@
|
||||
"""
|
||||
报告生成器
|
||||
将分析计划 + 探索结果 + 洞察,综合为一份可读报告。
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import openai
|
||||
from config import LLM_CONFIG
|
||||
from explorer import ExplorationStep
|
||||
from insights import Insight
|
||||
|
||||
|
||||
REPORT_PROMPT = """你是一个数据分析报告撰写专家。基于以下信息撰写报告。
|
||||
|
||||
## 用户问题
|
||||
{question}
|
||||
|
||||
## 分析计划
|
||||
{plan}
|
||||
|
||||
## 探索过程
|
||||
{exploration}
|
||||
|
||||
## 主动洞察
|
||||
{insights_text}
|
||||
|
||||
## 撰写要求
|
||||
1. **开头**:一句话总结核心结论(先给答案)
|
||||
2. **核心发现**:按重要性排列,每个发现带具体数字
|
||||
3. **深入洞察**:异常、趋势、关联(从洞察中提取)
|
||||
4. **建议**:基于数据的行动建议
|
||||
5. **审计**:末尾附上所有执行的 SQL
|
||||
|
||||
使用 Markdown,中文撰写。
|
||||
语气:专业但不枯燥,像一个聪明的分析师在做简报。"""
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""报告生成器"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = openai.OpenAI(
|
||||
api_key=LLM_CONFIG["api_key"],
|
||||
base_url=LLM_CONFIG["base_url"],
|
||||
)
|
||||
self.model = LLM_CONFIG["model"]
|
||||
|
||||
def generate(
|
||||
self,
|
||||
question: str,
|
||||
plan: dict,
|
||||
steps: list[ExplorationStep],
|
||||
insights: list[Insight],
|
||||
) -> str:
|
||||
"""生成分析报告"""
|
||||
|
||||
# 构建探索过程文本
|
||||
exploration = self._build_exploration(steps)
|
||||
|
||||
# 构建洞察文本
|
||||
insights_text = self._build_insights(insights)
|
||||
|
||||
prompt = REPORT_PROMPT.format(
|
||||
question=question,
|
||||
plan=json.dumps(plan, ensure_ascii=False, indent=2),
|
||||
exploration=exploration,
|
||||
insights_text=insights_text,
|
||||
)
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的数据分析师,撰写清晰、有洞察力的分析报告。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=4096,
|
||||
)
|
||||
|
||||
return response.choices[0].message.content
|
||||
|
||||
def _build_exploration(self, steps: list[ExplorationStep]) -> str:
|
||||
parts = []
|
||||
for step in steps:
|
||||
if step.action == "done":
|
||||
parts.append(f"### 探索结束\n{step.reasoning}")
|
||||
continue
|
||||
|
||||
if step.success:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"思考: {step.reasoning}\n"
|
||||
f"SQL: `{step.sql}`\n"
|
||||
f"结果 ({step.row_count} 行):\n"
|
||||
f"列: {step.columns}\n"
|
||||
f"数据: {json.dumps(step.rows, ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
parts.append(
|
||||
f"### 第 {step.round_num} 轮:{step.purpose}\n"
|
||||
f"SQL: `{step.sql}`\n"
|
||||
f"执行失败: {step.error}"
|
||||
)
|
||||
return "\n\n".join(parts) if parts else "无探索步骤"
|
||||
|
||||
def _build_insights(self, insights: list[Insight]) -> str:
|
||||
if not insights:
|
||||
return "未检测到异常。"
|
||||
return "\n".join(str(i) for i in insights)
|
||||
@@ -1 +1,2 @@
|
||||
openai>=1.0.0
|
||||
matplotlib>=3.5.0
|
||||
|
||||
14
test_run.py
Normal file
14
test_run.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""快速测试"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from agent import DataAnalysisAgent
|
||||
|
||||
agent = DataAnalysisAgent("demo.db")
|
||||
|
||||
print(f"\n📋 Playbook: {len(agent.playbook_mgr.playbooks)} 个")
|
||||
print("🚀 开始测试...\n")
|
||||
|
||||
report = agent.analyze("帮我分析一下工单的整体情况", max_rounds=4)
|
||||
print("\n" + report)
|
||||
Reference in New Issue
Block a user