BytePlan Analysis Skill
概述
此 Skill 专注于从 BytePlan 平台查询数据并生成分析报告的数据层工作:
- 登录认证 - 通过 BytePlan 平台认证(凭证存储在
~/.byteplan/.env) - 创建工作目录 - 为每个分析主题创建独立文件夹,防止干扰
- 生成分析任务列表 - 使用 LLM 根据分析主题生成 Analysis Task List
- 查询数据 - 按照任务列表依次查询 BytePlan 数据
- 输出数据 - 生成 Markdown 格式的数据列表
- 生成后续建议 - 使用 LLM 根据数据分析结果生成后续行动建议
⚠️ 核心规则:所有生成的文件(analysisPlan.json、analysis_report.md、Excel、PPT、Word 等)都必须放在独立工作目录 ~/.byteplan/workspaces/{主题}_{时间戳}/ 下,绝不能放在其他位置。
⚠️ 分析完成后必须询问:分析完成后,必须立即主动询问用户是否需要生成 Excel/PPT/Word/网页/视频 报告,不能等用户主动要求。
注意:生成 Excel/PPT/Word/网页/视频 报告需要使用单独的 skill:
- byteplan-excel - Excel 报告
- byteplan-ppt - PPT 演示文稿
- byteplan-word - Word 文档
- byteplan-html - 网页报告
- byteplan-video - 数据可视化视频
目录结构(重要)
~/.byteplan/
├── .env # 凭证和 token(全局共享)
│ ├── BP_ENV=uat
│ ├── BP_USER=手机号
│ ├── BP_PASSWORD=密码
│ ├── ACCESS_TOKEN=...
│ └── TOKEN_EXPIRES_IN=...
│
└── workspaces/ # 分析主题工作目录
├── 计算机资产折旧_20260331_224600/
│ ├── analysisPlan.json # 分析任务列表
│ ├── analysis_report.md # Markdown 报告
│ └── raw_data/ # 原始数据(可选)
│
├── 计算机资产折旧_20260401_103000/ # 重新分析时创建新目录
│ ├── analysisPlan.json
│ └── analysis_report.md
│
└── 库存周转分析_20260402_090000/
│ ├── analysisPlan.json
│ └── analysis_report.md
└ ...
工作目录命名规则:
- 格式:
{分析主题简称}_{时间戳} - 时间戳:
YYYYMMDD_HHMMSS - 示例:
计算机资产折旧_20260331_224600
⚠️ 文件位置规则:
| 文件类型 | 正确位置 | 错误位置 |
|----------|----------|----------|
| analysisPlan.json | workspaces/{主题}_{时间戳}/ ✅ | ~/.byteplan/ ❌ |
| analysis_report.md | workspaces/{主题}_{时间戳}/ ✅ | ~/.openclaw/workspace/ ❌ |
| Excel/PPT/Word | workspaces/{主题}_{时间戳}/ ✅ | /tmp/ ❌ |
| 所有输出文件 | 同一工作目录 ✅ | 分散在不同目录 ❌ |
重新分析处理:
- 当用户说"重新分析"时,自动创建新的工作目录
- 使用新的时间戳,保留历史分析记录
- 例如:第一次
计算机资产折旧_20260331_224600,重新分析计算机资产折旧_20260401_103000
核心流程
用户输入分析主题
↓
【第一步】创建工作目录(~/.byteplan/workspaces/{主题}_{时间戳}/)← 重要!
↓
登录认证(优先使用 ~/.byteplan/.env 缓存 token)
↓
查询可用模型
↓
使用 LLM 生成分析任务列表 (Analysis Task List)
↓
保存 analysisPlan.json 到工作目录 ← 所有文件都在此目录!
↓
依次执行任务查询数据
↓
输出 analysis_report.md 到工作目录 ← 所有文件都在此目录!
↓
使用 LLM 生成后续建议
↓
【必须执行】询问用户:是否生成 网页/PPT/Word?
↓
生成报告文件到【同一工作目录】← 所有文件都在此目录!
工作目录创建时机:
- ✅ 用户输入新的分析主题 → 创建新工作目录
- ✅ 用户说"重新分析" → 创建新工作目录(保留历史)
- ❌ 用户说"继续分析"或"查看上次分析" → 使用现有工作目录
依赖
本 skill 依赖 byteplan-api 提供 API 调用能力。
API 导入方式
import { login, loginWithEnv, getUserInfo, setEnvironment, queryModels, getModelData, getModelColumns } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
前置条件
凭证存储位置
系统使用 ~/.byteplan/.env 存储凭证(用户主目录下的 byteplan 文件夹):
BP_ENV=uat # UAT 环境
BP_USER=你的手机号
BP_PASSWORD="你的密码"
# 以下字段由系统自动管理(登录成功后自动写入)
ACCESS_TOKEN= # 访问令牌
REFRESH_TOKEN= # 刷新令牌
TOKEN_EXPIRES_IN= # 过期时间戳
首次使用时:如果没有凭证文件,系统会询问用户输入账号密码,登录成功后自动创建 ~/.byteplan/ 目录并保存凭证。
工作流程详解
步骤 1:创建工作目录
在开始分析之前,首先创建独立的工作目录,防止不同分析主题干扰。
import { homedir } from 'os';
import path from 'path';
import { existsSync, mkdirSync } from 'fs';
// 基础目录
const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
const WORKSPACES_DIR = path.join(BYTEPLAN_DIR, 'workspaces');
// 确保 workspaces 目录存在
if (!existsSync(WORKSPACES_DIR)) {
mkdirSync(WORKSPACES_DIR, { recursive: true });
}
// 生成工作目录名称
function generateWorkspaceName(theme: string): string {
// 简化主题名称(取前20个字符,去除特殊字符)
const shortTheme = theme.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '').substring(0, 20);
// 生成时间戳
const now = new Date();
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
return `${shortTheme}_${timestamp}`;
}
// 检查是否需要创建新目录
function shouldCreateNewWorkspace(userInput: string, existingWorkspaces: string[]): boolean {
// 用户说"重新分析" → 创建新目录
if (userInput.includes('重新分析') || userInput.includes('重新') || userInput.includes('再分析')) {
return true;
}
// 用户说"继续分析"或"查看上次分析" → 使用现有目录
if (userInput.includes('继续') || userInput.includes('查看') || userInput.includes('上次')) {
return false;
}
// 新的分析主题 → 创建新目录
return true;
}
// 创建工作目录
const workspaceName = generateWorkspaceName(analysisTheme);
const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
if (!existsSync(workspacePath)) {
mkdirSync(workspacePath, { recursive: true });
console.log(`✅ 创建工作目录: ${workspaceName}`);
} else {
console.log(`📁 使用现有工作目录: ${workspaceName}`);
}
// 切换工作目录(后续文件操作都在此目录下)
process.chdir(workspacePath);
console.log(`📌 当前工作目录: ${workspacePath}`);
工作目录创建逻辑:
| 用户意图 | 处理方式 | 示例 | |----------|----------|------| | 新分析主题 | 创建新目录 | "分析计算机资产" → 创建新目录 | | 重新分析 | 创建新目录(保留历史) | "重新分析" → 创建新目录 | | 继续分析 | 使用最近目录 | "继续上次的分析" → 使用现有目录 | | 查看历史 | 不创建新目录 | "查看上次的分析结果" → 使用现有目录 |
步骤 2:验证登录状态或重新登录
优化流程:先检查 ~/.byteplan/.env 中缓存的 token 是否有效,避免每次都重新登录。
- 使用 UAT 环境登录
- 尝试使用
~/.byteplan/.env中缓存的 token(如果存在且未过期) - 如果缓存 token 有效,直接使用;否则重新登录
- 登录成功后必须显示当前租户信息
import { login, loginWithEnv, getUserInfo, setEnvironment, getToken } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
import { existsSync, readFileSync } from 'fs';
import { homedir } from 'os';
import path from 'path';
// 凭证文件路径
const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
const ENV_FILE = path.join(BYTEPLAN_DIR, '.env');
// 1. 设置环境为 UAT
setEnvironment('uat');
// 2. 尝试使用缓存的 token(优先)
let token;
let loginResult;
let isCachedLogin = false;
// 尝试使用 ~/.byteplan/.env 中缓存的 token
if (existsSync(ENV_FILE)) {
loginResult = await loginWithEnv('uat', false).catch(() => null);
if (loginResult?.access_token) {
token = loginResult.access_token;
isCachedLogin = loginResult._cached === true; // 标记是否使用缓存
}
}
// 3. 如果缓存无效,询问用户输入凭证并重新登录
if (!token) {
// 检查是否有保存的账号密码
const envContent = existsSync(ENV_FILE) ? readFileSync(ENV_FILE, 'utf-8') : '';
const hasCredentials = envContent.includes('BP_USER=') || envContent.includes('USER_NAME=');
if (hasCredentials) {
// 有凭证但 token 过期,尝试重新登录
loginResult = await loginWithEnv(selectedEnv, true).catch(() => null);
if (loginResult?.access_token) {
token = loginResult.access_token;
}
}
// 仍然没有 token,需要用户输入
if (!token) {
// 使用 AskUserQuestion 询问用户输入账号密码
// ... 获取 username 和 password
loginResult = await login(username, password, selectedEnv);
token = loginResult.access_token;
}
}
// 4. 显示登录状态和租户信息
const userInfo = await getUserInfo(token);
const currentTenant = userInfo.user?.tenantName || '未知租户';
if (isCachedLogin) {
console.log(`✅ 使用缓存的登录状态,无需重新登录`);
} else {
console.log(`✅ 登录成功!Token 已缓存到 ~/.byteplan/.env`);
}
console.log(`📌 当前租户: ${currentTenant}`);
console.log(`📌 当前环境: ${selectedEnv}`);
登录状态说明
| 状态 | 说明 | 处理方式 |
|------|------|----------|
| 缓存有效 | ~/.byteplan/.env 中有未过期的 token | 直接使用,无需重新登录 |
| 缓存过期 | token 已过期(提前5分钟判定) | 使用保存的凭证重新登录 |
| 无凭证 | ~/.byteplan/.env 不存在或缺少账号密码 | 询问用户输入并登录 |
API 函数说明
| 函数 | 描述 |
|------|------|
| loginWithEnv(env, forceReLogin) | 使用 ~/.byteplan/.env 凭证登录,forceReLogin=false 时优先使用缓存 |
| getToken() | 获取当前缓存的 token |
| isTokenExpired() | 检查 token 是否过期(内部函数) |
| login(username, password, env) | 手动输入凭证登录 |
步骤 3:查询可用模型
import { queryModels } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
const models = await queryModels(token);
// 筛选与分析主题相关的模型(可选)
const relatedModels = models.filter(m => {
const name = m.modelName || '';
const code = m.modelCode || '';
// 根据分析主题关键词筛选
return name.includes('资产') || code.includes('ASSET');
});
// 保存模型列表
const modelList = models.map(m => ({
name: m.modelName,
code: m.modelCode
}));
步骤 4:使用 LLM 生成分析任务列表
调用 LLM 生成分析任务列表 JSON
LLM Prompt 模板
# 角色
你是一个企业级数据分析规划 Agent(Analysis Planner)。
# 任务
根据用户提供的【分析主题】,生成一份【分析任务列表】,用于驱动后续的数据查询和分析。
# 可用模型列表
```json
{available_models_json}
分析主题
{analysis_theme}
你必须遵守的规则
- 不要进行任何数据计算
- 不要猜测数据内容
- 只输出合法 JSON,符合 AnalysisTaskList Schema
- 每个任务只对应一个数据查询
- 任务之间逻辑清晰、层级合理
AnalysisTaskList Schema
{
"analysisPlanId": "PLAN_ID",
"planName": "计划名称",
"planDescription": "计划描述",
"currentDate": "2026-03-27",
"tasks": [
{
"taskId": "TASK_001",
"taskName": "任务名称",
"description": "任务描述",
"modelName": "数据模型名称",
"modelCode": "数据模型代码",
"filters": {
"type": "condition",
"field": "筛选字段",
"operator": "=",
"value": "筛选值"
},
"outputFormat": "表格/卡片/列表",
"outputFields": ["字段1", "字段2", "..."]
}
],
"summary": {
"totalTasks": 5,
"keyMilestones": ["任务1", "任务2", "..."]
}
}
输出要求
只输出 JSON,不要有其他内容。
### 步骤 5:保存分析任务列表
```javascript
const analysisPlan = JSON.parse(llmResponse);
writeFileSync('analysisPlan.json', JSON.stringify(analysisPlan, null, 2));
console.log(`分析任务列表已保存,共 ${analysisPlan.tasks.length} 个任务`);
步骤 6:执行分析任务
import { getModelData, getModelColumns } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
const plan = JSON.parse(readFileSync('analysisPlan.json'));
const results = [];
// 依次执行每个任务
for (const task of plan.tasks) {
console.log(`\n▶ 执行任务: ${task.taskName}`);
try {
const params = {
pageNum: 1,
pageSize: 1000
};
// 添加筛选条件
if (task.filters) {
params.customQuery = task.filters;
}
const data = await getModelData(token, task.modelCode, params);
results.push({
taskId: task.taskId,
taskName: task.taskName,
description: task.description,
modelName: task.modelName,
data: data.data || [],
total: data.total || data.data?.length || 0
});
console.log(` ✅ 获取 ${data.data?.length || 0} 条数据`);
} catch (error) {
console.log(` ❌ 查询失败: ${error.message}`);
results.push({
taskId: task.taskId,
taskName: task.taskName,
description: task.description,
modelName: task.modelName,
data: [],
error: error.message
});
}
}
步骤 7:生成 Markdown 格式数据
import { writeFileSync } from 'fs';
function generateMarkdownReport(plan, results) {
let md = `# ${plan.planName}\n\n`;
md += `> 分析主题: ${plan.planDescription}\n`;
md += `> 生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
md += `---\n\n`;
// 执行摘要
md += `## 📊 执行摘要\n\n`;
md += `| 指标 | 数值 |\n`;
md += `|------|------|\n`;
md += `| 总任务数 | ${plan.tasks.length} |\n`;
md += `| 成功 | ${results.filter(r => !r.error).length} |\n`;
md += `| 失败 | ${results.filter(r => r.error).length} |\n\n`;
// 任务详情
md += `## 📋 任务详情\n\n`;
for (const result of results) {
md += `### ${result.taskId}: ${result.taskName}\n\n`;
md += `**描述**: ${result.description}\n`;
md += `**数据模型**: ${result.modelName}\n\n`;
if (result.error) {
md += `❌ **查询失败**: ${result.error}\n\n`;
continue;
}
md += `✅ 获取数据 **${result.total}** 条\n\n`;
// 如果有数据,生成表格
if (result.data && result.data.length > 0) {
const firstItem = result.data[0];
const fields = Object.keys(firstItem);
md += `| ${fields.join(' | ') } |\n`;
md += `| ${fields.map(() => '---').join(' | ') } |\n`;
const maxRows = Math.min(result.data.length, 20); // 最多显示20行
for (let i = 0; i < maxRows; i++) {
const row = result.data[i];
md += `| ${fields.map(f => {
let val = row[f];
if (typeof val === 'object' && val !== null) {
val = val.name || val.code || JSON.stringify(val);
}
return String(val || '').replace(/\n/g, ' ').substring(0, 50);
}).join(' | ')} |\n`;
}
if (result.data.length > maxRows) {
md += `\n*... 共 ${result.data.length} 条数据*\n`;
}
}
md += `\n---\n\n`;
}
// 数据洞察(基于数据结构)
md += `## 💡 数据洞察\n\n`;
md += `> 基于查询结果的数据特征分析\n\n`;
for (const result of results) {
if (result.data && result.data.length > 0) {
const firstItem = result.data[0];
// 统计数值字段
const numericFields = Object.keys(firstItem).filter(f => {
const val = firstItem[f];
return typeof val === 'number';
});
if (numericFields.length > 0) {
md += `### ${result.taskName} - 数值统计\n\n`;
md += `| 字段 | 类型 | 说明 |\n`;
md += `|------|------|------|\n`;
numericFields.slice(0, 5).forEach(f => {
md += `| ${f} | 数值 | ${result.taskName}相关指标 |\n`;
});
md += `\n`;
}
}
}
// 后续建议(基于数据分析)
// 注意:此处只是占位,实际建议内容由步骤 7.5 的 LLM 生成后追加
md += `## 🎯 后续建议\n\n`;
md += `> 基于以上数据分析,以下是建议的后续行动\n\n`;
md += `> (后续建议将在 LLM 分析后追加,详见步骤 7.5)\n\n`;
// 数据来源
md += `## 📚 数据来源\n\n`;
md += `- 租户: ${currentTenant}\n`;
md += `- 数据模型: ${results.map(r => r.modelName).join(', ')}\n`;
md += `- 查询时间: ${new Date().toISOString()}\n`;
return md;
}
const markdown = generateMarkdownReport(plan, results);
writeFileSync('analysis_report.md', markdown);
console.log('\n✅ Markdown 报告已生成: analysis_report.md');
步骤 7.5:生成后续建议(LLM 分析)
在生成 Markdown 报告后,需要调用 LLM 根据数据分析结果生成后续建议。
LLM Prompt 模板
# 角色
你是一个企业级数据分析顾问(Analysis Consultant)。
# 任务
根据以下数据分析结果,生成后续行动建议。
# 分析主题
{plan_name}: {plan_description}
# 数据分析结果
{analysis_results_json}
# 你必须遵守的规则
1. 建议必须基于实际数据,不得臆造
2. 建议要具体、可执行,避免泛泛而谈
3. 如果发现数据异常,明确指出并给出排查建议
4. 建议要分优先级,区分"立即行动"和"后续关注"
5. 如果数据不足以得出结论,明确说明需要补充的数据
# 建议结构
请按以下结构输出建议(Markdown 格式):
## 🔴 需要立即关注的问题
- 列出数据中发现的问题或异常
- 每个问题给出可能的原因和影响
## 🟡 建议深入分析的方向
- 列出值得进一步探索的数据维度
- 说明每个方向的分析价值
## 🟢 可执行的业务建议
- 给出具体的行动建议
- 说明预期效果和实施要点
## 📋 后续数据收集建议
- 如需补充数据,说明需要收集的数据
- 说明数据用途和收集方式
# 输出要求
只输出 Markdown 格式的建议内容,不要有其他内容。
实现代码
import { writeFileSync, readFileSync } from 'fs';
async function generateFollowUpSuggestions(plan, results) {
// 准备数据摘要(避免数据量过大)
const dataSummary = results.map(r => ({
taskId: r.taskId,
taskName: r.taskName,
description: r.description,
modelName: r.modelName,
totalRecords: r.total,
hasError: !!r.error,
sampleData: r.data?.slice(0, 5) || [], // 只取前5条作为样本
numericStats: extractNumericStats(r.data)
}));
// 调用 LLM 生成建议
const prompt = `
# 角色
你是一个企业级数据分析顾问(Analysis Consultant)。
# 任务
根据以下数据分析结果,生成后续行动建议。
# 分析主题
${plan.planName}: ${plan.planDescription}
# 数据分析结果
\`\`\`json
${JSON.stringify(dataSummary, null, 2)}
\`\`\`
# 你必须遵守的规则
1. 建议必须基于实际数据,不得臆造
2. 建议要具体、可执行,避免泛泛而谈
3. 如果发现数据异常,明确指出并给出排查建议
4. 建议要分优先级,区分"立即行动"和"后续关注"
5. 如果数据不足以得出结论,明确说明需要补充的数据
# 建议结构
请按以下结构输出建议(Markdown 格式):
## 🔴 需要立即关注的问题
- 列出数据中发现的问题或异常
- 每个问题给出可能的原因和影响
## 🟡 建议深入分析的方向
- 列出值得进一步探索的数据维度
- 说明每个方向的分析价值
## 🟢 可执行的业务建议
- 给出具体的行动建议
- 说明预期效果和实施要点
## 📋 后续数据收集建议
- 如需补充数据,说明需要收集的数据
- 说明数据用途和收集方式
# 输出要求
只输出 Markdown 格式的建议内容,不要有其他内容。
`;
// 这里调用 Claude 或其他 LLM API
// const suggestions = await callLLM(prompt);
// return suggestions;
// 临时返回示例结构(实际应调用 LLM)
return generateDefaultSuggestions(results);
}
// 提取数值字段的统计信息
function extractNumericStats(data) {
if (!data || data.length === 0) return {};
const numericFields = {};
const firstItem = data[0];
for (const field of Object.keys(firstItem)) {
if (typeof firstItem[field] === 'number') {
const values = data.map(d => d[field]).filter(v => typeof v === 'number');
if (values.length > 0) {
numericFields[field] = {
min: Math.min(...values),
max: Math.max(...values),
avg: values.reduce((a, b) => a + b, 0) / values.length,
count: values.length
};
}
}
}
return numericFields;
}
// 生成默认建议结构(当 LLM 不可用时)
function generateDefaultSuggestions(results) {
let suggestions = `## 🔴 需要立即关注的问题\n\n`;
suggestions += `> 请根据实际数据分析结果填写\n\n`;
// 检查是否有查询失败的任务
const failedTasks = results.filter(r => r.error);
if (failedTasks.length > 0) {
suggestions += `- **数据查询异常**: ${failedTasks.length} 个任务查询失败,建议检查数据源连接\n`;
}
suggestions += `\n## 🟡 建议深入分析的方向\n\n`;
suggestions += `> 请根据分析主题和数据特征填写\n\n`;
suggestions += `- 建议进一步分析数据的趋势变化\n`;
suggestions += `- 建议对比不同维度的数据分布\n`;
suggestions += `\n## 🟢 可执行的业务建议\n\n`;
suggestions += `> 请根据数据分析结论填写\n\n`;
suggestions += `- 建议定期更新数据,保持分析的时效性\n`;
suggestions += `- 建议建立数据监控机制,及时发现异常\n`;
suggestions += `\n## 📋 后续数据收集建议\n\n`;
suggestions += `> 请根据分析缺口填写\n\n`;
suggestions += `- 如需更全面的分析,建议补充历史数据\n`;
return suggestions;
}
// 更新报告,追加后续建议
function appendSuggestionsToReport(reportPath, suggestions) {
const originalContent = readFileSync(reportPath, 'utf-8');
const lines = originalContent.split('\n');
// 找到"数据来源"章节的位置
const sourceIndex = lines.findIndex(line => line.includes('## 📚 数据来源'));
if (sourceIndex !== -1) {
// 在数据来源之前插入后续建议
lines.splice(sourceIndex, 0, suggestions + '\n');
} else {
// 追加到末尾
lines.push('\n' + suggestions);
}
writeFileSync(reportPath, lines.join('\n'));
}
// 使用示例
const suggestions = await generateFollowUpSuggestions(plan, results);
appendSuggestionsToReport('analysis_report.md', suggestions);
console.log('✅ 后续建议已追加到报告');
建议内容要求
生成的后续建议应包含以下维度:
| 建议类型 | 说明 | 示例 | |----------|------|------| | 🔴 立即关注 | 数据异常、风险点、紧急问题 | 发现资产折旧异常、数据缺失严重 | | 🟡 深入分析 | 值得进一步探索的方向 | 细分品类分析、时间趋势对比 | | 🟢 业务建议 | 可执行的行动建议 | 优化采购策略、调整折旧政策 | | 📋 数据收集 | 需要补充的数据 | 历史数据、外部对标数据 |
⚠️ 步骤 8:询问用户是否生成报告文档(工作流必须执行)
🔴 这是工作流的必要环节,必须执行,不能跳过!
🔴 不能等用户主动要求,分析完成后必须立即主动询问!
分析数据生成完成后,立即主动询问用户下一步操作,输出类似以下内容:
✅ 分析完成!
工作目录: ~/.byteplan/workspaces/边际分析_20260331_230800/
- analysis_report.md 已生成(含后续建议)
- analysisPlan.json 已保存
请问是否需要生成其他格式的报告?
- Excel 报告(结构化数据表格)
- PPT 报告(演示文稿格式)
- Word 报告(文档格式)
- 网页报告(支持演示模式)
- 视频报告(数据可视化视频)
提供的选项
| 选项 | 说明 | 执行动作 | |------|------|----------| | 生成 Excel 报告 | 结构化数据表格,多 Sheet | 调用 byteplan-excel | | 生成 PPT 报告 | PowerPoint 演示格式 | 调用 byteplan-ppt | | 生成 Word 报告 | Word 文档格式 | 调用 byteplan-word | | 生成网页报告 | 支持演示模式 | 调用 byteplan-html | | 生成视频报告 | 数据可视化视频 | 调用 byteplan-video | | 查看 Markdown | 直接查看已生成的 md 文件 | 展示文件内容 | | 不需要 | 结束本次分析 | 结束对话 |
用户选择后自动执行
根据用户选择,直接调用对应的 skill:
- 用户选择 Excel → 执行 byteplan-excel skill
- 用户选择 PPT → 执行 byteplan-ppt skill
- 用户选择 Word → 执行 byteplan-word skill
- 用户选择网页 → 执行 byteplan-html skill
- 用户选择 Video → 执行 byteplan-video skill
- 用户选择 Markdown → 展示 analysis_report.md 内容
注意:
- 不要只是告诉用户"请使用 xxx skill",而是直接执行对应的 skill
- 不能等用户主动要求,分析完成后必须立即主动询问
- 所有生成的报告文件必须输出到同一工作目录
API 参考
| 函数 | 描述 |
|------|------|
| setEnvironment(env) | 设置登录环境(仅支持 'uat') |
| getEnvironment() | 获取当前环境 |
| login(username, password, env?) | 使用凭证登录,token 自动缓存到 ~/.byteplan/.env |
| loginWithEnv(env?, forceReLogin?) | 使用 ~/.byteplan/.env 凭证登录,默认优先使用缓存 token |
| getToken() | 获取当前缓存的 access_token |
| getUserInfo(token) | 获取用户和租户信息 |
| queryModels(token, options?) | 列出可用模型 |
| getModelData(token, modelCode, params) | 查询模型数据 |
| getModelColumns(token, modelCodes) | 获取模型字段定义 |
loginWithEnv 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| env | 'uat' | 'uat' | 登录环境 |
| forceReLogin | boolean | false | 是否强制重新登录(忽略缓存) |
返回值中 _cached: true 表示使用了缓存的 token。
查询语法
条件结构
{
type: 'condition',
field: '字段名',
operator: '=', // 支持: =, !=, >, <, >=, <=, LIKE, IN, BETWEEN
value: '值'
}
条件组结构(多条件)
{
type: 'group',
logic: 'AND', // AND 或 OR
children: [
{ type: 'condition', field: '字段1', operator: '=', value: '值1' },
{ type: 'condition', field: '字段2', operator: '>', value: '值2' }
]
}
完整示例
用户请求:"分析计算机类实物资产的折旧趋势"
1. 创建工作目录
import { homedir } from 'os';
import path from 'path';
import { mkdirSync } from 'fs';
const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
const WORKSPACES_DIR = path.join(BYTEPLAN_DIR, 'workspaces');
const workspaceName = '计算机资产折旧_20260331_224600';
const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
mkdirSync(workspacePath, { recursive: true });
process.chdir(workspacePath);
console.log(`✅ 工作目录: ${workspacePath}`);
2. 登录并获取模型
const models = await queryModels(token);
const modelList = models.map(m => ({ name: m.modelName, code: m.modelCode }));
3. 调用 LLM 生成任务列表
将模型列表和分析主题发送给 LLM:
分析主题:分析计算机类实物资产的折旧趋势
可用模型列表:
[
{"name": "资产基础信息表", "code": "CASMN_ASSET_BASE_INFO"},
{"name": "资产明细表", "code": "CASMN_ASSET_INFO_DETAIL"},
{"name": "资产类别", "code": "CASMN_ASSET_CATEGORY"},
{"name": "已到期未报废资产明细", "code": "CASMN_ASSET_EXPIRED_NOT_SCRAPPED"}
]
4. 保存并执行任务
const plan = JSON.parse(llmResponse);
writeFileSync('analysisPlan.json', JSON.stringify(plan, null, 2));
// 执行每个任务...
const results = [];
for (const task of plan.tasks) {
const data = await getModelData(token, task.modelCode, { pageNum: 1, pageSize: 1000 });
results.push({ ...task, data: data.data });
}
5. 生成 Markdown 报告
const markdown = generateMarkdownReport(plan, results);
writeFileSync('analysis_report.md', markdown);
6. 生成后续建议
const suggestions = await generateFollowUpSuggestions(plan, results);
appendSuggestionsToReport('analysis_report.md', suggestions);
console.log('✅ 后续建议已追加到报告');
7. 询问用户下一步操作(工作流必须执行)
分析完成后,立即询问用户下一步操作:
✅ 分析完成!
工作目录: ~/.byteplan/workspaces/计算机资产折旧_20260331_224600/
- analysis_report.md 已生成(含后续建议)
- analysisPlan.json 已保存
请问是否需要生成其他格式的报告?
- 网页报告(支持演示模式)
- PPT 报告(演示格式)
- Word 报告(文档格式)
- 仅查看 Markdown
根据用户选择直接调用对应 skill:
- 网页 → 执行 byteplan-html
- PPT → 执行 byteplan-ppt
- Word → 执行 byteplan-word
- Markdown → 展示 analysis_report.md 内容
输出文件
| 文件 | 描述 | 存储位置 | 必须 |
|------|------|----------|------|
| analysisPlan.json | 分析任务列表 JSON | 工作目录 | ✅ |
| analysis_report.md | Markdown 格式的数据报告(含后续建议) | 工作目录 | ✅ |
| report_data.json | 报告数据文件(供下游 Excel/PPT/Word/HTML skill 消费) | 工作目录 | ✅ |
report_data.json 规范
⚠️ 此文件是下游报告 skill(html/ppt/word/excel)的标准输入,必须生成!
分析完成后,必须根据查询结果生成 report_data.json,格式如下:
{
"title": "报告标题",
"subtitle": "副标题",
"period": "2026年Q1",
"analysisGoal": "分析目标关键词",
"sections": ["章节1", "章节2"],
"summary": [
{ "rank": "🥇 第一名", "name": "项目名", "type": "类型", "value": "¥1,000万", "rate": "利润率 90%" }
],
"kpis": [
{ "icon": "💰", "label": "指标名", "value": "数值", "unit": "单位" }
],
"charts": [
{
"title": "图表标题",
"type": "bar|line|pie|horizontalBar",
"data": [{ "name": "分类名", "value": 100 }]
}
],
"tableData": {
"title": "明细表标题",
"columns": ["列1", "列2"],
"rows": [["值1", "值2"]]
},
"insights": [
{ "title": "洞察标题", "content": "洞察内容", "warning": false }
],
"recommendations": [
{ "title": "建议标题", "content": "建议内容" }
],
"source": "BytePlan 数据平台"
}
关键规则:
charts数组中每个图表的type可省略,下游脚本会根据标题关键词自动选择summary最多 3 条,kpis最多 4 个,insights和recommendations各最多 4 条- 所有数值型字段使用数字类型(非字符串),方便图表渲染
- 文件必须保存在工作目录根路径下(与其他输出文件同级)
输出文件位置:所有分析结果文件保存在当前工作目录 ~/.byteplan/workspaces/{主题}_{时间戳}/
⚠️ 重要规则:文件必须放在独立工作目录
这是本 skill 最核心的规则,必须严格遵守!
所有输出文件的位置
~/.byteplan/workspaces/{主题}_{时间戳}/
├── analysisPlan.json ← 分析任务列表(必须)
├── analysis_report.md ← Markdown 报告(必须)
├── 边际分析报告.xlsx ← Excel 报告(可选)
├── 边际分析报告.pptx ← PPT 报告(可选)
├── 边际分析报告.docx ← Word 报告(可选)
└── raw_data/ ← 原始数据(可选)
❌ 错误做法
| 错误位置 | 说明 |
|----------|------|
| ~/.openclaw/workspace/ | ❌ 不能放在 OpenClaw workspace 根目录 |
| ~/.byteplan/ | ❌ 不能放在 byteplan 根目录 |
| /tmp/ | ❌ 不能放在临时目录 |
| 当前目录 | ❌ 不能放在任意当前工作目录 |
✅ 正确做法
- 第一步就是创建工作目录:
~/.byteplan/workspaces/{主题}_{时间戳}/ - 切换到该目录:
process.chdir(workspacePath) - 所有后续文件操作都在此目录下
- 生成的 Excel/PPT/Word 也必须放在同一目录
目录切换代码示例
// 创建并切换到工作目录(这是第一步!)
const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
mkdirSync(workspacePath, { recursive: true });
process.chdir(workspacePath); // ← 关键:切换当前工作目录
// 之后所有 writeFileSync 都会写入此目录
writeFileSync('analysisPlan.json', ...); // → workspacePath/analysisPlan.json
writeFileSync('analysis_report.md', ...); // → workspacePath/analysis_report.md
writeFileSync('边际分析报告.xlsx', ...); // → workspacePath/边际分析报告.xlsx
注意事项
- ⚠️ 工作目录隔离(最重要):所有文件必须放在
~/.byteplan/workspaces/{主题}_{时间戳}/,绝不能放在其他位置 - 🔴 分析后必须询问(最重要):分析完成后,必须立即主动询问用户是否需要生成 Excel/PPT/Word/网页/视频,不能等用户主动要求
- 重新分析处理:用户说"重新分析"时创建新目录,保留历史分析记录
- 登录状态检查(重要):优先使用
loginWithEnv(env, false)检查~/.byteplan/.env中缓存的 token,避免每次重新登录 - 首次使用时:如果没有
~/.byteplan/.env文件或缺少凭证,必须询问用户输入账号密码 - 登录成功后:必须调用
getUserInfo获取并显示当前租户信息 - 生成任务列表:必须使用 LLM 生成,不要自行编造
- 执行任务时:严格按照任务列表中的定义执行查询
- 数据分析:只输出数据结构,不做计算和结论
- 生成后续建议:必须使用 LLM 根据实际数据生成,建议要具体可执行
- Token 会自动缓存到
~/.byteplan/.env文件,有效期通常为 24 小时(提前 5 分钟判定过期)
Scan to contact