@qingflow-tech/qingflow-cli 1.1.48 → 1.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +4 -4
  2. package/npm/lib/runtime.mjs +29 -13
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/skills/qingflow-cli/SKILL.md +91 -410
  6. package/skills/qingflow-cli/manifest.yaml +1 -1
  7. package/skills/qingflow-cli/reference/00-INDEX.md +4 -4
  8. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +3 -137
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +6 -4
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +14 -14
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +20 -19
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +18 -14
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +7 -12
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +2 -1
  16. package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +13 -5
  17. package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +5 -3
  18. package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +1 -1
  19. package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +1 -1
  20. package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1665 -1738
  21. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
  22. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +76 -95
  23. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +6 -4
  24. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +13 -13
  25. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +20 -19
  26. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +18 -14
  27. package/skills/qingflow-cli/reference/data/README.md +32 -0
  28. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md +50 -0
  29. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +2 -1
  30. package/skills/qingflow-cli/reference/record/analysis/README.md +21 -113
  31. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +10 -10
  32. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +9 -9
  33. package/skills/qingflow-cli/reference/record/analysis/business-context.md +1 -1
  34. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +10 -10
  35. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +52 -80
  36. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +2 -2
  37. package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +48 -46
  38. package/skills/qingflow-common/SKILL.md +10 -17
  39. package/src/qingflow_mcp/__init__.py +1 -1
  40. package/src/qingflow_mcp/backend_client.py +142 -12
  41. package/src/qingflow_mcp/builder_facade/flow_validation.py +569 -0
  42. package/src/qingflow_mcp/builder_facade/service.py +762 -6
  43. package/src/qingflow_mcp/builder_facade/workflow_spec.py +155 -3
  44. package/src/qingflow_mcp/cli/commands/__init__.py +2 -1
  45. package/src/qingflow_mcp/cli/commands/auth.py +1 -1
  46. package/src/qingflow_mcp/cli/commands/builder.py +57 -41
  47. package/src/qingflow_mcp/cli/commands/data.py +134 -0
  48. package/src/qingflow_mcp/cli/commands/record.py +1 -1
  49. package/src/qingflow_mcp/cli/commands/task.py +10 -12
  50. package/src/qingflow_mcp/cli/commands/workspace.py +20 -7
  51. package/src/qingflow_mcp/cli/context.py +4 -1
  52. package/src/qingflow_mcp/cli/formatters.py +177 -21
  53. package/src/qingflow_mcp/cli/main.py +4 -1
  54. package/src/qingflow_mcp/cli/pagination.py +44 -0
  55. package/src/qingflow_mcp/config.py +60 -0
  56. package/src/qingflow_mcp/public_surface.py +9 -0
  57. package/src/qingflow_mcp/resource_uri.py +241 -0
  58. package/src/qingflow_mcp/response_trim.py +56 -5
  59. package/src/qingflow_mcp/server.py +2 -0
  60. package/src/qingflow_mcp/server_app_builder.py +57 -2
  61. package/src/qingflow_mcp/server_app_user.py +10 -3
  62. package/src/qingflow_mcp/tools/ai_builder_tools.py +374 -41
  63. package/src/qingflow_mcp/tools/app_tools.py +52 -13
  64. package/src/qingflow_mcp/tools/csv_export.py +168 -0
  65. package/src/qingflow_mcp/tools/data_tools.py +284 -0
  66. package/src/qingflow_mcp/tools/login_link_tools.py +34 -6
  67. package/src/qingflow_mcp/tools/record_tools.py +85 -61
  68. package/src/qingflow_mcp/tools/resource_read_tools.py +13 -0
  69. package/src/qingflow_mcp/tools/task_context_tools.py +21 -4
  70. package/src/qingflow_mcp/tools/workspace_tools.py +9 -12
@@ -1,7 +1,8 @@
1
1
  # Qingflow CLI:更新记录工作流(详情后直接写入)
2
2
 
3
+ > **兼容范围**:本文仅限用户明确要求旧 CLI `record` 兼容入口时使用。默认更新必须切换到 [data 操作说明](../data/README.md) 的 `data get` → `data update`;禁止在 data update 失败后自动回退本文链路。
3
4
  > **用途**:使用当前 CLI 会话读取目标记录详情后,用 **`record update`** 直接提交差异字段。主链路是 `record get -> record update`;字段范围不清、候选歧义或失败排查时,才追加 **`record schema update`**。
4
- > **关联**:只读列表/详情见 **[QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md](../core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md)**;**字段 `kind` 与写入值总表**见 **[QINGFLOW_CLI_FIELD_DATA_TYPES.md](../core/QINGFLOW_CLI_FIELD_DATA_TYPES.md)**;最终统计结论统一使用 **[record/analysis](./analysis/README.md) 的 `record access -> Python/pandas`**;**成员 / 部门等** 的细颗粒写入见 **[QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md](./QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md)**;主技能 **[../SKILL.md](../../SKILL.md)**。
5
+ > **关联**:直接数据操作统一使用 [data 操作说明](../data/README.md);最终统计结论统一使用 **[record/analysis](./analysis/README.md) 的 `data query --all --output csv -> Python/pandas`**;主技能 **[../SKILL.md](../../SKILL.md)**。
5
6
 
6
7
  ---
7
8
 
@@ -1,130 +1,38 @@
1
- # Qingflow CLI Record Analysis
1
+ # 轻流记录数据分析
2
2
 
3
- Use this section for final statistical conclusions: counts, distributions, ratios, averages, rankings, trends, comparisons, and analysis reports.
4
-
5
- Default path, every time:
3
+ 最终统计结论、分析报告、趋势、排名、比例和分布统一使用以下路径:
6
4
 
7
5
  ```text
8
- app get -> record schema browse(view_id=...) -> record access -> Python/pandas -> final answer
9
- ```
10
-
11
- Use the CLI entry:
12
-
13
- ```bash
14
- qingflow record --help
15
- qingflow record access --help
16
- qingflow record access --app-key APP_KEY --view-id VIEW_ID --columns-file columns.json --where-file where.json --order-by-file order_by.json --json
17
- ```
18
-
19
- The CLI command is under the `record` command group, so the discovery path is: first inspect `qingflow record --help`, then inspect `qingflow record access --help`.
20
-
21
- ## Hard Rules
22
-
23
- - Never start analysis from `record_list`, export, QingBI, or guessed field ids.
24
- - Never conclude `record_access` is unavailable just because it is not visible as a top-level MCP tool; check the CLI path `qingflow record access`.
25
- - Never call `record access` before `record schema browse`.
26
- - Use only field ids returned by `record schema browse`.
27
- - If `app_get.accessible_views[].analysis_supported=false`, do not use that view for `record_access`.
28
- - Prefer an explicit time range or business filter. If the user gives none and the table may be large, ask for scope or use a clearly provided month/quarter/year.
29
- - Read every CSV shard in `record_access.files[].local_path` with Python. Do not print raw CSV or load raw rows into model context.
30
- - Before final analysis, run a field-quality profile in pandas: row count, null rate, distinct count, and period coverage for candidate grouping fields.
31
- - Do not use a high-missing field as the main conclusion dimension. If a candidate dimension is sparse, downgrade it to an `已填写样本观察` and choose a cleaner semantic fallback when available.
32
- - Full final conclusions require `record_access.complete=true` and `record_access.safe_for_final_conclusion=true`.
33
- - `record_list` is only for sample inspection after the aggregate result is understood.
34
- - `record_get` is only for single-record detail verification, logs, references, images, or readable attachments. Read images from `media_assets.items[].local_path`; read documents/tables from `file_assets.items[].local_path` and `extraction.text_path`.
35
- - `record_export_direct` is only for explicit export/download/Excel requests.
36
- - `chart_get` / QingBI is only for user-provided report URLs or chart ids.
37
-
38
- ## Tool Selection
39
-
40
- | Need | Tool |
41
- |---|---|
42
- | Batch analysis, statistics, comparison, report | `record access -> Python` |
43
- | Browse a few example rows | `record_list` |
44
- | Inspect one record, logs, references, images, attachments | `record_get` |
45
- | User asks to export/download/Excel | `record_export_direct` |
46
- | User gives report URL or chart id | `chart_get` |
47
- | Todo/workflow task actions | `reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md` |
48
-
49
- ## Standard Procedure
50
-
51
- 1. Run `app get` and choose a table-style `view_id` from `accessible_views`.
52
- 2. Run `record schema browse --app-key APP_KEY --view-id VIEW_ID`.
53
- 3. Decide metric intent before fetching data: `count`, `sum`, `avg`, `distinct_count`, ratio, ranking, trend, or comparison.
54
- 4. Choose minimal `record_access.columns`, plus `where` and `order_by`.
55
- 5. Run `qingflow record access`.
56
- 6. Read all returned CSV files in Python; use `fields[]` only when field-id metadata is needed.
57
- 7. Run field-quality checks for all candidate dimensions.
58
- 8. Compute results in pandas.
59
- 9. Report numbers with scope, field choices, field-quality caveats, completeness, and business assumptions.
60
-
61
- Use field-id DSLs only:
62
-
63
- ```json
64
- {
65
- "app_key": "APP_KEY",
66
- "view_id": "system:all",
67
- "columns": [{ "field_id": 2 }, { "field_id": 18 }],
68
- "where": [{ "field_id": 2, "op": "between", "value": ["2026-05-01", "2026-05-31"] }],
69
- "order_by": [{ "field_id": 2, "direction": "asc" }]
70
- }
6
+ qingflow data query --all --output csv -> 读取全部 CSV 分片 -> Python/pandas
71
7
  ```
72
8
 
73
- Never pass `page`, `page_size`, `limit`, `max_rows`, `timeout`, or `profile` to `record_access`.
74
-
75
- CSV columns are already readable and field-id anchored, for example `项目状态__field_343283094`. Do not expect separate `schema.json` or `README.md` files.
76
-
77
- For CLI use, write JSON argument files instead of embedding large JSON in shell text:
9
+ 先用当前 CLI 帮助确认参数:
78
10
 
79
11
  ```bash
80
- qingflow record access \
12
+ qingflow data --help
13
+ qingflow data query --help
14
+ qingflow --json data query \
81
15
  --app-key APP_KEY \
82
- --view-id system:all \
83
16
  --columns-file columns.json \
84
17
  --where-file where.json \
85
- --order-by-file order_by.json \
86
- --json
18
+ --sort-file sort.json \
19
+ --all --output csv
87
20
  ```
88
21
 
89
- ## Status Handling
90
-
91
- Read `record_access.status` before reading files or writing conclusions.
92
-
93
- - `status=success`, `complete=true`, `safe_for_final_conclusion=true`: full-scope answer is allowed.
94
- - `status=needs_scope`: no CSV was written. Ask for a time/business range or retry with a user-provided period using `scope.suggested_time_fields`.
95
- - `status=partial`: read returned files only as a subset. Do not give a full-population conclusion.
96
- - `complete=false`, `truncated=true`, or `safe_for_final_conclusion=false`: answer as `初步观察` or ask for a narrower scope.
97
-
98
- ## Business Context
99
-
100
- If the question mentions department, team, region, owner group, stage, product line, or a named business scope, check whether aliases or child scopes matter before concluding. Use explicit mappings provided by the user or local context; otherwise ask a short clarification.
101
-
102
- Example: if `烈焰组` and `飓风组` are sub-departments of `北斗部门`, apply that mapping in Python and state it in the answer.
103
-
104
- ## Output Shape
105
-
106
- Default to:
107
-
108
- 1. `结论`
109
- 2. `关键数据`
110
- 3. `口径与范围`
111
- 4. `可信度 / 限制`
112
-
113
- Concrete numbers are mandatory. Ratios require both numerator and denominator. Trends require a time field and explicit date range. Rankings must come from a sorted pandas result.
114
- If a requested dimension has poor quality, say so explicitly and provide the nearest reliable fallback dimension, for example platform or product instead of a mostly empty module field.
22
+ `--all` 与 `--output csv` 必须同时使用。columns 使用字段 id 数组;where 与 sort 使用包含 `field_id` 的对象数组。字段 id 来自当前 data schema 或已确认上下文,不得猜测。
115
23
 
116
- ## References
24
+ ## 分析门禁
117
25
 
118
- Load only what is needed:
26
+ - 读取返回摘要的 `status`、`row_count`、`complete`、`truncated`、`safe_for_final_conclusion`、`files` 和 `stopped_reason`。
27
+ - 用 Python/pandas 读取全部 `files[].local_path`,不要把原始 CSV 行打印到模型上下文。
28
+ - 完整总体结论必须同时满足 `complete=true`、`truncated=false`、`safe_for_final_conclusion=true`。
29
+ - `status=needs_scope` 时 `files` 为空;读取 `scope.reported_total` 与 `scope.row_threshold`,请求时间或业务范围后重试。
30
+ - `status=partial` 或任一完整性条件不满足时,只能给出局部观察,或收窄范围后重试。
31
+ - 最终分组前检查行数、空值率、唯一值数量与时间覆盖;高缺失维度只能作为“已填写样本观察”。
32
+ - `record list` 仅用于样本核查,不能代替全量 CSV;导出工具只用于用户明确要求下载文件的场景。
119
33
 
120
- - Data access status machine: [references/data-access-playbook.md](./data-access-playbook.md)
121
- - Python/pandas templates: [references/pandas-recipes.md](./pandas-recipes.md)
122
- - Analysis patterns: [references/analysis-patterns.md](./analysis-patterns.md)
123
- - Business mappings and scope: [references/business-context.md](./business-context.md)
124
- - Confidence and final wording: [references/confidence-reporting.md](./confidence-reporting.md)
125
- - Common mistakes: [references/analysis-gotchas.md](./analysis-gotchas.md)
126
- - Report templates: [references/report-format.md](./report-format.md)
34
+ ## 输出要求
127
35
 
128
- ## Feedback Escalation
36
+ 默认输出结论、关键数据、口径与范围、可信度/限制。比例必须包含分子与分母,趋势必须说明时间字段与日期范围,排名必须来自 pandas 显式排序。
129
37
 
130
- If the desired analysis cannot be completed because of missing capability, unsupported data shape, or an awkward workflow after reasonable attempts, summarize the exact gap and ask whether to submit product feedback. Only after explicit user confirmation, call `feedback_submit`.
38
+ 进一步细节见同目录的取数状态机、pandas 模板、分析模式、业务上下文、可信度和报告模板。
@@ -1,19 +1,19 @@
1
1
  # Analysis Gotchas
2
2
 
3
- ## Do Not Skip Schema
3
+ ## 不要猜字段 schema
4
4
 
5
- Correct path:
5
+ 正确路径:
6
6
 
7
- 1. `app_get`
8
- 2. `record_browse_schema_get`
9
- 3. `record_access`
10
- 4. Python
7
+ 1. 从当前 data schema 或已确认上下文选择字段 id
8
+ 2. `data query --all --output csv`
9
+ 3. 读取全部 `files[].local_path`
10
+ 4. Python/pandas
11
11
 
12
- `record_browse_schema_get` returns readable fields for the selected view. Missing fields are permission or view-scope boundaries, not invitations to guess hidden ids.
12
+ data 工具返回的 schema、字段错误与访问范围是事实源;缺失字段不是猜测隐藏 id 的理由。
13
13
 
14
14
  ## Do Not Use Export For Analysis
15
15
 
16
- Export tools are for user-requested files. Analysis uses `record_access` because it returns structured completeness and compact field metadata.
16
+ 导出工具只用于用户明确要求下载文件。分析使用 `data query --all --output csv`,因为它返回本地 CSV 分片与结构化完整性字段。
17
17
 
18
18
  ## Do Not Treat `record_list` As Full Data
19
19
 
@@ -28,7 +28,7 @@ Export tools are for user-requested files. Analysis uses `record_access` because
28
28
 
29
29
  ## Do Not Control Paging
30
30
 
31
- `record_access` owns paging internally.
31
+ `data query --all --output csv` 内部处理分页。
32
32
 
33
33
  Do not invent:
34
34
 
@@ -84,7 +84,7 @@ For penetration, conversion, or share:
84
84
 
85
85
  ## Normalize Relative Dates
86
86
 
87
- Convert relative phrases into exact ranges before `record_access`.
87
+ 在 data query 前把相对日期转换为精确范围。
88
88
 
89
89
  - `今年5月` -> exact May 1 to May 31 in the current year
90
90
  - `去年同期` -> same date range in previous year
@@ -2,12 +2,12 @@
2
2
 
3
3
  ## Canonical Sequence
4
4
 
5
- 1. `app_get`
6
- 2. `record_browse_schema_get`
7
- 3. decide metric intent
8
- 4. choose `record_access.columns / where / order_by`
9
- 5. `record_access`
10
- 6. Python over every returned CSV shard
5
+ 1. 明确 `app_key`
6
+ 2. 从当前 data schema 或已确认上下文选择字段 id
7
+ 3. 决定指标意图
8
+ 4. 选择 data query 的 columns / where / sort
9
+ 5. `data query --all --output csv`
10
+ 6. Python 读取每个 `files[].local_path`
11
11
  7. optional `record_list` or `record_get` only for sample/detail verification
12
12
 
13
13
  Metric intent must be one of:
@@ -74,7 +74,7 @@ If denominator is missing or scope differs, do not call the result a rate.
74
74
 
75
75
  ## Trend
76
76
 
77
- 1. Choose a date/time field from `suggested_time_fields`.
77
+ 1. 从 data schema 或查询响应的 `fields` 中选择日期/时间字段,并核对其 id、标题与类型。
78
78
  2. Convert relative phrases into exact date ranges.
79
79
  3. Fetch the date field and metrics.
80
80
  4. Bucket in pandas by day/week/month/quarter/year.
@@ -104,8 +104,8 @@ Never use `record_list` alone for final averages, shares, rankings, trends, or d
104
104
 
105
105
  If the exact field is unclear:
106
106
 
107
- 1. inspect `record_browse_schema_get.fields`
108
- 2. use titles and suggested fields
107
+ 1. 检查 data schema 或返回的 `fields`
108
+ 2. 按 `fields` 中的标题、字段 id 与类型筛选候选字段
109
109
  3. if one candidate is clearly dominant, proceed
110
110
  4. otherwise ask the user to confirm
111
111
 
@@ -54,7 +54,7 @@ If any part is ambiguous, ask. Do not rename a count as a rate.
54
54
 
55
55
  ## Time Scope
56
56
 
57
- Normalize relative dates to exact dates before calling `record_access`.
57
+ 调用 data query 前把相对日期转换为精确日期。
58
58
 
59
59
  Examples:
60
60
 
@@ -4,12 +4,12 @@
4
4
 
5
5
  Use `全量可信结论` only when:
6
6
 
7
- - `record_browse_schema_get` was used
8
- - data came from `record_access`
7
+ - 字段 id 来自当前 data schema 或已确认上下文
8
+ - 数据来自 `data query --all --output csv`
9
9
  - all returned CSV shards were read in Python
10
- - `record_access.complete=true`
11
- - `record_access.truncated=false`
12
- - `record_access.safe_for_final_conclusion=true`
10
+ - `complete=true`
11
+ - `truncated=false`
12
+ - `safe_for_final_conclusion=true`
13
13
  - metric definitions are complete
14
14
  - denominator exists for every ratio
15
15
  - time fields and date ranges are explicit
@@ -19,11 +19,11 @@ Use `全量可信结论` only when:
19
19
 
20
20
  Use `初步观察` when:
21
21
 
22
- - `record_access.status=needs_scope`
23
- - `record_access.status=partial`
24
- - `record_access.complete=false`
25
- - `record_access.truncated=true`
26
- - `record_access.safe_for_final_conclusion=false`
22
+ - `status=needs_scope`
23
+ - `status=partial`
24
+ - `complete=false`
25
+ - `truncated=true`
26
+ - `safe_for_final_conclusion=false`
27
27
  - evidence came from `record_list`
28
28
  - scope or saved view filter is unverified
29
29
 
@@ -1,106 +1,78 @@
1
- # Data Access Playbook
1
+ # data CSV 取数状态机
2
2
 
3
- This file is the operational state machine for `record_access`.
3
+ ## 必须路径
4
4
 
5
- ## Required Sequence
5
+ 1. 明确 `app_key` 与指标意图。
6
+ 2. 从当前 data schema 或已确认上下文选择字段 id。
7
+ 3. 构造 columns、where、sort JSON 文件。
8
+ 4. 执行 `data query --all --output csv`。
9
+ 5. 读取全部 `files[].local_path` 并在 Python/pandas 中合并。
6
10
 
7
- 1. `app_get`
8
- 2. choose `view_id` from `accessible_views`
9
- 3. `record_browse_schema_get(app_key, view_id)`
10
- 4. build `record_access.columns / where / order_by` from field ids
11
- 5. run `record_access`
12
- 6. read every CSV shard with Python
13
-
14
- Do not call `record_access` with field titles, guessed ids, page controls, row limits, or profile.
15
- CSV columns are readable and field-id anchored, for example `项目状态__field_343283094`; do not look for extra `schema.json` or README files.
16
-
17
- ## Finding `record_access`
18
-
19
- `record_access` can be available as an MCP tool or as a CLI subcommand. If the MCP surface does not show a top-level `record_access` tool, look under the Qingflow CLI record group before choosing any other path:
20
-
21
- ```bash
22
- qingflow record --help
23
- qingflow record access --help
24
- ```
25
-
26
- The CLI call is:
11
+ CLI 发现与调用:
27
12
 
28
13
  ```bash
29
- qingflow record access \
14
+ qingflow data --help
15
+ qingflow data query --help
16
+ qingflow --json data query \
30
17
  --app-key APP_KEY \
31
- --view-id VIEW_ID \
32
18
  --columns-file columns.json \
33
19
  --where-file where.json \
34
- --order-by-file order_by.json \
35
- --json
20
+ --sort-file sort.json \
21
+ --all --output csv
36
22
  ```
37
23
 
38
- This is the same analysis path: it writes CSV shards and metadata for Python. Do not replace it with list browsing, export, QingBI, or aggregate helpers just because the MCP tool is not visible.
39
-
40
- ## Request Patterns
24
+ columns 是字段 id 数组,例如 `[2, 18]`;where 和 sort 是带 `field_id` 的对象数组。`--all` 与 `--output csv` 必须同时出现。
41
25
 
42
- ### Count or distribution
26
+ ## 真实输出字段
43
27
 
44
- Fetch the grouping field and any time/business filter field.
28
+ 分析前先读取:
45
29
 
46
- ```json
47
- {
48
- "app_key": "APP_KEY",
49
- "view_id": "system:all",
50
- "columns": [{ "field_id": 18 }],
51
- "where": [{ "field_id": 2, "op": "between", "value": ["2026-05-01", "2026-05-31"] }],
52
- "order_by": []
53
- }
54
- ```
30
+ - `status`
31
+ - `files[].local_path`、`files[].row_count`、`files[].part`
32
+ - `row_count`
33
+ - `complete`
34
+ - `truncated`
35
+ - `safe_for_final_conclusion`
36
+ - `fetched_pages`
37
+ - `stopped_reason`
38
+ - `fields`
55
39
 
56
- ### Trend
40
+ 输出还可能包含 `local_dir`、访问上下文和动态提示;这些内容保持原样解释,不在本地补造。
57
41
 
58
- Fetch the date/time field plus metric fields.
42
+ ## 状态决策
59
43
 
60
- ```json
61
- {
62
- "app_key": "APP_KEY",
63
- "view_id": "system:all",
64
- "columns": [{ "field_id": 2 }, { "field_id": 18 }],
65
- "where": [{ "field_id": 2, "op": "between", "value": ["2026-01-01", "2026-12-31"] }],
66
- "order_by": [{ "field_id": 2, "direction": "asc" }]
67
- }
68
- ```
69
-
70
- ### Ratio
71
-
72
- If numerator and denominator use different filters, run separate `record_access` calls. Only compute the ratio after both source datasets are complete and compatible.
73
-
74
- ## Status Decisions
75
-
76
- | Status | Meaning | Agent action |
44
+ | 状态 | 含义 | 动作 |
77
45
  |---|---|---|
78
- | `success` + `safe_for_final_conclusion=true` | Full retrieved scope is reliable | Give final conclusion |
79
- | `needs_scope` | Tool refused large unbounded scan, no CSV | Ask for scope or retry with explicit period/business filter |
80
- | `partial` | Some CSV files written, but not full data | Give only subset observation |
81
- | `complete=false` | Not all requested data is available | Do not present full-population conclusion |
82
- | `truncated=true` | Tool had to stop before full scope | Disclose and narrow scope |
83
-
84
- ## `needs_scope` Recovery
46
+ | `success` 且完整性三项满足 | 请求范围完整可用 | 读取所有分片后形成总体结论 |
47
+ | `needs_scope` | 无范围大查询被拒绝,未写 CSV | 读取 `scope.reported_total` 与 `scope.row_threshold`,请求时间或业务范围 |
48
+ | `partial` | 已写部分 CSV,但未完成 | 只给局部观察,说明 `row_count` 与 `stopped_reason` |
49
+ | `complete=false` | 未取完请求范围 | 不给总体结论 |
50
+ | `truncated=true` | 提前停止 | 收窄范围后重试 |
51
+ | `safe_for_final_conclusion=false` | 不适合最终总体结论 | 降级或重试 |
85
52
 
86
- Use the returned `scope` object:
53
+ `needs_scope` 时 `files=[]`、`row_count=0`;不要寻找不存在的 CSV。partial 时保留的文件可用于局部观察,但不能使用“全部”“整体”“全量”等措辞。
87
54
 
88
- - `reported_total`: explain why scope is needed
89
- - `suggested_time_fields`: choose likely date fields
90
- - `recommended_where_examples`: reuse if they match the user request
55
+ ## 请求示例
91
56
 
92
- If the user already provided a concrete month/quarter/year, retry with that period. If no business boundary is available, ask one short clarification.
57
+ `columns.json`:
93
58
 
94
- ## `partial` Recovery
59
+ ```json
60
+ [2, 18]
61
+ ```
95
62
 
96
- You may read the files, but must label output as partial:
63
+ `where.json`(同一字段的范围使用两个公开操作符并按 AND 组合):
97
64
 
98
- - say which files/rows were analyzed
99
- - do not use `全部`, `所有`, `整体`, or `全量`
100
- - suggest narrowing time or business scope before final conclusion
65
+ ```json
66
+ [
67
+ {"field_id": 2, "op": "gte", "value": "2026-05-01"},
68
+ {"field_id": 2, "op": "lte", "value": "2026-05-31"}
69
+ ]
70
+ ```
101
71
 
102
- ## View Scope
72
+ `sort.json`:
103
73
 
104
- For custom views, the result is scoped to that saved view. If `verification.view_filter_verified=false`, disclose that the saved-filter scope could not be fully verified.
74
+ ```json
75
+ [{"field_id": 2, "direction": "asc"}]
76
+ ```
105
77
 
106
- For board/gantt views, switch to a table-style view or `system:all` plus explicit filters.
78
+ 若分子和分母使用不同筛选,分别执行兼容范围的 data query;两侧都满足完整性门禁后再计算比例。
@@ -9,14 +9,14 @@ import pandas as pd
9
9
 
10
10
  files = [
11
11
  "/absolute/path/records-0001.csv",
12
- # include every record_access.files[].local_path
12
+ # 包含 data query 返回的每个 files[].local_path
13
13
  ]
14
14
 
15
15
  frames = [pd.read_csv(path, dtype=str, keep_default_na=False) for path in files]
16
16
  df = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
17
17
 
18
18
  # Columns are readable and field-id anchored, e.g. 项目状态__field_343283094.
19
- fields = [] # optionally paste record_access.fields here if you need field-id metadata
19
+ fields = [] # 需要字段 id 元数据时使用 data query 返回的 fields
20
20
  field_by_id = {int(item["field_id"]): item for item in fields if "field_id" in item}
21
21
  title_by_col = {item["column_name"]: item["title"] for item in fields if item.get("column_name")}
22
22
  ```
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  验证工作流 spec JSON 是否符合 WorkflowSpecDTO JSON Schema,
4
- 并执行自定义约束检查(DAG、单 applicant、gateway 条件等)。
4
+ 并执行自定义约束检查(边端点/自环、单 applicant、gateway 条件等)。
5
+
6
+ 环判定口径与后端 LegacyEngineFlowValidator(G 规则)镜像:
7
+ - 自环(A→A)拒绝;无根纯环(A→B→A,无 gateway)拒绝;
8
+ - 经 gateway 回指的驳回环(approval→gateway→approval)合法,不做通用 DAG 无环检查;
9
+ - 单节点流程(仅 applicant,空边)合法,提交即结束。
5
10
 
6
11
  用法:
7
12
  python3 validate_flow_spec.py <spec_file.json> --schema <schema_file.json>
8
-
13
+
9
14
  schema 文件应在运行时通过 qingflow builder flow schema --json 动态获取。
10
15
  """
11
16
 
12
17
  import json
13
18
  import sys
14
19
  import os
15
- from collections import deque
16
20
 
17
21
  try:
18
22
  import jsonschema
@@ -48,35 +52,23 @@ def validate_schema(spec, schema):
48
52
 
49
53
 
50
54
  def check_dag(nodes, edges):
51
- """检查边是否构成 DAG(无环)"""
55
+ """检查边端点存在性与自环(与后端 G2/G4 口径镜像)。
56
+
57
+ 注意:不做通用 DAG 无环检查——轻流流程存在合法驳回环
58
+ (approval -> gateway -> approval,经 gateway 回指的回退边),
59
+ 后端同样只拒绝自环与无根纯环;通用 Kahn 环检测曾造成 409 例合法流程误报。
60
+ """
52
61
  node_ids = {n['id'] for n in nodes}
53
- adj = {nid: [] for nid in node_ids}
62
+ errors = []
54
63
  for e in edges:
55
- f, t = e['from'], e['to']
64
+ f, t = e.get('from'), e.get('to')
56
65
  if f not in node_ids:
57
- return [f"edge from={f} to={t}: from 节点不存在于 nodes 中"]
66
+ errors.append(f"edge from={f} to={t}: from 节点不存在于 nodes 中")
58
67
  if t not in node_ids:
59
- return [f"edge from={f} to={t}: to 节点不存在于 nodes 中"]
60
- adj[f].append(t)
61
-
62
- # Kahn's algorithm for topological sort
63
- indeg = {nid: 0 for nid in node_ids}
64
- for e in edges:
65
- indeg[e['to']] += 1
66
-
67
- q = deque([nid for nid, d in indeg.items() if d == 0])
68
- visited = 0
69
- while q:
70
- u = q.popleft()
71
- visited += 1
72
- for v in adj[u]:
73
- indeg[v] -= 1
74
- if indeg[v] == 0:
75
- q.append(v)
76
-
77
- if visited != len(node_ids):
78
- return ["检测到环路:工作流边中存在环,不符合 DAG 要求"]
79
- return []
68
+ errors.append(f"edge from={f} to={t}: to 节点不存在于 nodes 中")
69
+ if f == t:
70
+ errors.append(f"自环边 from={f} to={t}:节点指向自身(自环被后端 G2 拒绝)")
71
+ return errors
80
72
 
81
73
 
82
74
  def check_applicant(nodes):
@@ -301,22 +293,31 @@ def check_orphan_nodes(nodes, edges):
301
293
 
302
294
 
303
295
  def check_routing_entry_and_sync(nodes, edges):
304
- """检查入口出边和需要继续流转节点的 sync 标记。"""
296
+ """检查入口出边和需要继续流转节点的 sync 标记。
297
+
298
+ 返回 (errors, warnings):单节点流程(仅 applicant、无出边)合法,降为警告
299
+ (提交即结束,后端 apply 同样放行)。
300
+ """
305
301
  errors = []
302
+ warn = []
306
303
  outgoing = {}
307
304
  for edge in edges:
308
305
  outgoing.setdefault(edge.get('from'), []).append(edge.get('to'))
309
306
 
307
+ single_node = len(nodes) == 1
310
308
  for node in nodes:
311
309
  node_id = node.get('id')
312
310
  node_type = node.get('type')
313
311
  node_label = f"节点 {node_id} ({node.get('name','')})"
314
312
  has_outgoing = bool(outgoing.get(node_id))
315
313
  if node_type == 'applicant' and not has_outgoing:
316
- errors.append(f"申请人入口节点 {node_id} 没有出边,提交后流程不会进入下一节点")
314
+ if single_node:
315
+ warn.append(f"单节点流程:入口节点 {node_id} 无出边,提交即结束(合法形态)")
316
+ else:
317
+ errors.append(f"申请人入口节点 {node_id} 没有出边,提交后流程不会进入下一节点")
317
318
  if has_outgoing and node.get('sync') is not True:
318
319
  errors.append(f"{node_label} 存在出边但未显式 sync=true,后端可能停止继续路由")
319
- return errors
320
+ return errors, warn
320
321
 
321
322
 
322
323
  def check_minimal_modification(old_spec, new_spec):
@@ -545,25 +546,17 @@ def main():
545
546
  nodes, edges = normalize_spec(spec)
546
547
  if not nodes:
547
548
  all_errors.append("[结构] spec 中未找到 nodes 数组")
548
- if not edges:
549
- all_errors.append("[结构] spec 中未找到 edges 数组")
549
+ # edges 可为空列表:单节点流程(仅 applicant)合法(提交即结束);多节点必须有边
550
+ if not edges and len(nodes) > 1:
551
+ all_errors.append("[结构] 多节点流程要求 edges 非空(单节点流程可为空数组)")
550
552
 
551
- if nodes and edges:
553
+ if nodes:
552
554
  # 3. 必要字段检查
553
555
  all_errors.extend(check_required_fields(nodes, edges))
554
556
 
555
- # 4. DAG 检查(回退边/驳回边可能导致环,降级为警告)
556
- dag_errors = check_dag(nodes, edges)
557
- if dag_errors:
558
- warnings.extend(dag_errors)
559
- warnings.append("DAG 环通常由回退边(如审批驳回、gateway 回退)引起,可能是合法的业务需求")
560
-
561
557
  # 5. 单 applicant 检查
562
558
  all_errors.extend(check_applicant(nodes))
563
559
 
564
- # 6. Gateway 条件检查
565
- all_errors.extend(check_gateway_conditions(nodes, edges))
566
-
567
560
  # 7. 审批节点检查
568
561
  all_errors.extend(check_approval_nodes(nodes))
569
562
 
@@ -576,12 +569,21 @@ def main():
576
569
  # 10. 抄送节点检查
577
570
  all_errors.extend(check_cc_nodes(nodes))
578
571
 
572
+ # 12. 入口出边与 sync 检查(单节点流程降为警告;空边时 outgoing 为空也需检查)
573
+ entry_errors, entry_warnings = check_routing_entry_and_sync(nodes, edges)
574
+ all_errors.extend(entry_errors)
575
+ warnings.extend(entry_warnings)
576
+
577
+ if edges:
578
+ # 4. 边结构检查(端点存在 + 自环;与后端 G2/G4 口径镜像,合法驳回环放行)
579
+ all_errors.extend(check_dag(nodes, edges))
580
+
581
+ # 6. Gateway 条件检查
582
+ all_errors.extend(check_gateway_conditions(nodes, edges))
583
+
579
584
  # 11. 孤立节点检查
580
585
  all_errors.extend(check_orphan_nodes(nodes, edges))
581
586
 
582
- # 12. 入口出边与 sync 检查
583
- all_errors.extend(check_routing_entry_and_sync(nodes, edges))
584
-
585
587
  # 13. 更新模式:最小修改原则检查
586
588
  if previous_file:
587
589
  try: