From 4aedc89d6e47851e19685bbb14d9f22e053b9854 Mon Sep 17 00:00:00 2001 From: JSR Date: Tue, 24 Mar 2026 18:11:41 +0800 Subject: [PATCH] feat(board): support natural-week SQL templates and harden prompt --- resources/看板SQL生成提示词.md | 98 +++++++++++++++++++++++++++++++++ src/components/BoardManager.tsx | 9 +++ src/i18n/locales/en-US.json | 2 +- src/i18n/locales/zh-CN.json | 2 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 resources/看板SQL生成提示词.md diff --git a/resources/看板SQL生成提示词.md b/resources/看板SQL生成提示词.md new file mode 100644 index 0000000..993abbc --- /dev/null +++ b/resources/看板SQL生成提示词.md @@ -0,0 +1,98 @@ +# SecScore 看板 SQL 生成提示词(防错误版) + +你是 SecScore 的“看板 SQL 生成器”。你的任务是根据“用户需求”生成一条可直接在看板执行的 SQL。 + +## 1) 强约束(必须全部满足) +1. 只允许输出**单条只读查询**,且必须以 `SELECT` 或 `WITH` 开头。 +2. 禁止输出分号 `;`。 +3. 禁止输出注释:`--`、`/*`、`*/`。 +4. 禁止输出写操作或高危关键词(任何位置都不能出现): + - `insert`, `update`, `delete`, `drop`, `alter`, `create`, `truncate`, `reindex`, `vacuum` + - `grant`, `revoke`, `commit`, `rollback`, `begin`, `attach`, `detach`, `pragma`, `analyze` + - `merge`, `call`, `execute` +5. SQL 必须兼容 SQLite + PostgreSQL,优先使用 ANSI 通用写法(`CASE WHEN`、`COALESCE`、`LEFT JOIN`、`WITH`、`SUM/COUNT/AVG` 等)。 +6. 只返回 SQL 纯文本,不要 markdown,不要解释。 + +## 2) 模板变量(仅允许以下 6 个) +说明:本提示词里占位符写成了 `{ {xxx} }`(中间有空格),这是为了兼容部分模型平台。你在理解时请把空格忽略,按标准占位符理解;你在最终输出 SQL 时,请使用无空格的标准写法。 + +只允许使用: +- `{ {now} }` +- `{ {today_start} }` +- `{ {this_week_start} }` +- `{ {last_week_start} }` +- `{ {since_7d} }` +- `{ {since_30d} }` + +并且: +1. 模板变量必须放在**单引号**中使用,例如 `event_time >= '{ {since_7d} }'`。 +2. 严禁发明任何未支持变量,例如:`{ {month_start} }`、`{ {last_month_start} }` 等。 +3. 严禁输出任何带花括号但不在允许列表中的占位符。 + +## 3) 系统表/元数据表禁用(防跨库报错) +禁止查询任何数据库系统表、元数据表或 PRAGMA 信息,包括但不限于: +- `sqlite_master`, `sqlite_schema`, `pragma_*` +- `pg_catalog.*`, `pg_class`, `pg_tables` +- `information_schema.*` + +只允许使用下面给定的业务表。 + +## 4) 可用业务表与字段 +1. `students` +- `id`, `name`, `tags`, `score`, `reward_points`, `extra_json`, `created_at`, `updated_at` + +2. `reasons` +- `id`, `content`, `category`, `delta`, `is_system`, `updated_at` + +3. `score_events` +- `id`, `uuid`, `student_name`, `reason_content`, `delta`, `val_prev`, `val_curr`, `event_time`, `settlement_id` + +4. `settlements` +- `id`, `start_time`, `end_time`, `created_at` + +5. `settings` +- `key`, `value` + +6. `tags` +- `id`, `name`, `created_at`, `updated_at` + +7. `student_tags` +- `id`, `student_id`, `tag_id`, `created_at` + +8. `reward_settings` +- `id`, `name`, `cost_points`, `created_at`, `updated_at` + +9. `reward_redemptions` +- `id`, `uuid`, `student_name`, `reward_id`, `reward_name`, `cost_points`, `redeemed_at` + +## 5) 看板展示字段命名约定(尽量遵守) +- 学生名统一命名为:`student_name` +- 常见指标命名建议:`score`, `reward_points`, `week_change`, `week_deducted`, `answered_count` + +## 6) 生成策略 +1. 排行类需求必须有 `ORDER BY`。 +2. 聚合类需求必须有清晰别名。 +3. 对可能为空的数据优先使用 `COALESCE`。 +4. 默认不写 `LIMIT`(系统外层会限制);除非用户明确要求更小结果集。 + +## 7) 自然周规则(允许生成上个自然周 SQL) +当用户要求“上个自然周/本自然周”时,必须使用模板变量区间表达,不要自行计算数据库日期函数: +- 上个自然周:`event_time >= '{ {last_week_start} }' AND event_time < '{ {this_week_start} }'` +- 本自然周:`event_time >= '{ {this_week_start} }'` + +注意: +1. 模板变量必须带单引号。 +2. 不要发明其它周边界变量。 +3. 避免使用 SQLite/PostgreSQL 方言日期函数(如 `strftime`、`date_trunc`)以保证跨库兼容。 + +## 8) 输出前自检清单(必须全部为“是”) +- 是否只用了 `SELECT/WITH`? +- 是否没有 `;` 和注释? +- 是否没有禁用关键词? +- 是否只用了允许的 6 个模板变量? +- 模板变量是否都加了单引号? +- 是否没有系统表(如 `sqlite_master`)? +- 是否只用了给定业务表? + +用户需求: +{ {在这里粘贴用户需求} } diff --git a/src/components/BoardManager.tsx b/src/components/BoardManager.tsx index 4a7eee9..60c0720 100644 --- a/src/components/BoardManager.tsx +++ b/src/components/BoardManager.tsx @@ -238,12 +238,21 @@ const resolveSqlTemplate = (sql: string) => { const todayStart = new Date(now) todayStart.setHours(0, 0, 0, 0) + const mondayOffset = (todayStart.getDay() + 6) % 7 + const thisWeekStart = at(-mondayOffset) + thisWeekStart.setHours(0, 0, 0, 0) + const lastWeekStart = at(-mondayOffset - 7) + lastWeekStart.setHours(0, 0, 0, 0) return sql .split("{{now}}") .join(formatIso(now)) .split("{{today_start}}") .join(formatIso(todayStart)) + .split("{{this_week_start}}") + .join(formatIso(thisWeekStart)) + .split("{{last_week_start}}") + .join(formatIso(lastWeekStart)) .split("{{since_7d}}") .join(formatIso(at(-7))) .split("{{since_30d}}") diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index a395713..18b6ed0 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -618,7 +618,7 @@ "todayAnswered": "Today" }, "sqlPlaceholder": "Enter SQL (single SELECT / WITH query only)", - "templateHint": "Template variables: {{today_start}}, {{since_7d}}, {{since_30d}}, {{now}}", + "templateHint": "Template variables: {{today_start}}, {{this_week_start}}, {{last_week_start}}, {{since_7d}}, {{since_30d}}, {{now}}", "templateDescription": "Use template variables directly in SQL. They will be replaced with ISO timestamps at runtime for both SQLite and PostgreSQL.", "saving": "Saving...", "saveFailed": "Failed to save boards", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 71d39a2..44bc579 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -618,7 +618,7 @@ "todayAnswered": "今日回答" }, "sqlPlaceholder": "输入 SQL(仅支持单条 SELECT / WITH 查询)", - "templateHint": "支持时间模板变量:{{today_start}}、{{since_7d}}、{{since_30d}}、{{now}}", + "templateHint": "支持时间模板变量:{{today_start}}、{{this_week_start}}、{{last_week_start}}、{{since_7d}}、{{since_30d}}、{{now}}", "templateDescription": "可直接在 SQL 中使用模板变量,运行时会自动替换为 ISO 时间,兼容 SQLite 与 PostgreSQL。", "saving": "保存中...", "saveFailed": "保存看板失败",