@qingflow-tech/qingflow-app-builder-mcp 1.0.5 → 1.0.7
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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +68 -6
- package/skills/qingflow-app-builder/references/create-app.md +4 -1
- package/skills/qingflow-app-builder/references/gotchas.md +13 -1
- package/skills/qingflow-app-builder/references/tool-selection.md +3 -1
- package/skills/qingflow-app-builder/references/update-schema.md +6 -2
- package/skills/qingflow-app-builder/references/update-views.md +126 -5
- package/src/qingflow_mcp/builder_facade/models.py +269 -2
- package/src/qingflow_mcp/builder_facade/service.py +3549 -172
- package/src/qingflow_mcp/cli/commands/builder.py +39 -26
- package/src/qingflow_mcp/cli/commands/record.py +19 -1
- package/src/qingflow_mcp/public_surface.py +2 -5
- package/src/qingflow_mcp/response_trim.py +65 -7
- package/src/qingflow_mcp/server.py +4 -3
- package/src/qingflow_mcp/server_app_builder.py +33 -20
- package/src/qingflow_mcp/server_app_user.py +4 -3
- package/src/qingflow_mcp/tools/ai_builder_tools.py +395 -117
- package/src/qingflow_mcp/tools/record_tools.py +622 -56
|
@@ -470,21 +470,22 @@ class RecordTools(ToolBase):
|
|
|
470
470
|
|
|
471
471
|
@mcp.tool(
|
|
472
472
|
description=(
|
|
473
|
-
"Insert
|
|
473
|
+
"Insert Qingflow records using applicant-node field maps. "
|
|
474
474
|
"Use record_insert_schema_get first. "
|
|
475
|
-
"
|
|
475
|
+
"Prefer items=[{'fields': {...}}]; a single insert is one item. "
|
|
476
|
+
"Each item performs internal preflight validation before that item is written."
|
|
476
477
|
)
|
|
477
478
|
)
|
|
478
479
|
def record_insert(
|
|
479
480
|
app_key: str = "",
|
|
480
|
-
|
|
481
|
+
items: list[JSONObject] | None = None,
|
|
481
482
|
verify_write: bool = True,
|
|
482
483
|
output_profile: str = "normal",
|
|
483
484
|
) -> JSONObject:
|
|
484
485
|
return self.record_insert_public(
|
|
485
486
|
profile=DEFAULT_PROFILE,
|
|
486
487
|
app_key=app_key,
|
|
487
|
-
|
|
488
|
+
items=items,
|
|
488
489
|
verify_write=verify_write,
|
|
489
490
|
output_profile=output_profile,
|
|
490
491
|
)
|
|
@@ -1064,11 +1065,16 @@ class RecordTools(ToolBase):
|
|
|
1064
1065
|
required = bool(required_override) if required_override is not None else bool(field.required or any(item.get("required") for item in row_fields))
|
|
1065
1066
|
else:
|
|
1066
1067
|
required = bool(required_override) if required_override is not None else bool(field.required)
|
|
1068
|
+
write_format = _write_format_for_field(field)
|
|
1067
1069
|
payload: JSONObject = {
|
|
1068
1070
|
"title": field.que_title,
|
|
1069
1071
|
"kind": kind,
|
|
1070
1072
|
"required": required,
|
|
1073
|
+
"format_hint": _ready_schema_format_hint(kind, write_format),
|
|
1071
1074
|
}
|
|
1075
|
+
example_value = _ready_schema_example_value(kind, field, write_format, row_fields=row_fields)
|
|
1076
|
+
if example_value is not None:
|
|
1077
|
+
payload["example_value"] = example_value
|
|
1072
1078
|
if include_field_id:
|
|
1073
1079
|
payload["field_id"] = field.que_id
|
|
1074
1080
|
if kind in {"single_select", "multi_select"} and field.options:
|
|
@@ -2995,6 +3001,7 @@ class RecordTools(ToolBase):
|
|
|
2995
3001
|
profile: str = DEFAULT_PROFILE,
|
|
2996
3002
|
app_key: str,
|
|
2997
3003
|
fields: JSONObject | None = None,
|
|
3004
|
+
items: list[JSONObject] | None = None,
|
|
2998
3005
|
verify_write: bool = True,
|
|
2999
3006
|
output_profile: str = "normal",
|
|
3000
3007
|
) -> JSONObject:
|
|
@@ -3002,68 +3009,477 @@ class RecordTools(ToolBase):
|
|
|
3002
3009
|
normalized_output_profile = self._normalize_public_output_profile(output_profile)
|
|
3003
3010
|
if not app_key:
|
|
3004
3011
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
3012
|
+
if items is not None:
|
|
3013
|
+
normalized_items = self._normalize_public_record_insert_batch_items(fields=fields, items=items)
|
|
3014
|
+
return self._record_insert_public_batch(
|
|
3015
|
+
profile=profile,
|
|
3016
|
+
app_key=app_key,
|
|
3017
|
+
items=normalized_items,
|
|
3018
|
+
verify_write=verify_write,
|
|
3019
|
+
output_profile=normalized_output_profile,
|
|
3020
|
+
)
|
|
3005
3021
|
if fields is not None and not isinstance(fields, dict):
|
|
3006
3022
|
raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
|
|
3007
|
-
|
|
3008
|
-
raw_preflight = self._preflight_record_write(
|
|
3023
|
+
return self._record_insert_public_single(
|
|
3009
3024
|
profile=profile,
|
|
3010
|
-
operation="create",
|
|
3011
3025
|
app_key=app_key,
|
|
3012
|
-
apply_id=None,
|
|
3013
|
-
answers=[],
|
|
3014
3026
|
fields=cast(JSONObject, fields or {}),
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
view_key=None,
|
|
3019
|
-
view_name=None,
|
|
3020
|
-
)
|
|
3021
|
-
preflight_used_force_refresh = self._record_preflight_used_force_refresh(raw_preflight)
|
|
3022
|
-
preflight_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
3023
|
-
normalized_payload: JSONObject = self._record_write_normalized_payload(
|
|
3024
|
-
operation="insert",
|
|
3025
|
-
record_id=None,
|
|
3026
|
-
record_ids=[],
|
|
3027
|
-
normalized_answers=cast(list[JSONObject], preflight_data.get("normalized_answers", [])),
|
|
3028
|
-
submit_type=submit_type_value,
|
|
3029
|
-
selection=cast(JSONObject | None, preflight_data.get("selection") if isinstance(preflight_data.get("selection"), dict) else None),
|
|
3027
|
+
verify_write=verify_write,
|
|
3028
|
+
output_profile=normalized_output_profile,
|
|
3029
|
+
capture_exceptions=False,
|
|
3030
3030
|
)
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3031
|
+
|
|
3032
|
+
def _record_insert_public_single(
|
|
3033
|
+
self,
|
|
3034
|
+
*,
|
|
3035
|
+
profile: str,
|
|
3036
|
+
app_key: str,
|
|
3037
|
+
fields: JSONObject,
|
|
3038
|
+
verify_write: bool,
|
|
3039
|
+
output_profile: str,
|
|
3040
|
+
capture_exceptions: bool,
|
|
3041
|
+
) -> JSONObject:
|
|
3042
|
+
"""执行内部辅助逻辑。"""
|
|
3043
|
+
submit_type_value = self._normalize_record_write_submit_type("submit")
|
|
3044
|
+
write_attempted = False
|
|
3040
3045
|
try:
|
|
3041
|
-
|
|
3046
|
+
raw_preflight = self._preflight_record_write(
|
|
3042
3047
|
profile=profile,
|
|
3048
|
+
operation="create",
|
|
3043
3049
|
app_key=app_key,
|
|
3044
|
-
|
|
3045
|
-
|
|
3050
|
+
apply_id=None,
|
|
3051
|
+
answers=[],
|
|
3052
|
+
fields=fields,
|
|
3053
|
+
force_refresh_form=False,
|
|
3054
|
+
view_id=None,
|
|
3055
|
+
list_type=None,
|
|
3056
|
+
view_key=None,
|
|
3057
|
+
view_name=None,
|
|
3058
|
+
)
|
|
3059
|
+
preflight_used_force_refresh = self._record_preflight_used_force_refresh(raw_preflight)
|
|
3060
|
+
preflight_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
3061
|
+
normalized_payload: JSONObject = self._record_write_normalized_payload(
|
|
3062
|
+
operation="insert",
|
|
3063
|
+
record_id=None,
|
|
3064
|
+
record_ids=[],
|
|
3065
|
+
normalized_answers=cast(list[JSONObject], preflight_data.get("normalized_answers", [])),
|
|
3046
3066
|
submit_type=submit_type_value,
|
|
3047
|
-
|
|
3048
|
-
force_refresh_form=preflight_used_force_refresh,
|
|
3067
|
+
selection=cast(JSONObject | None, preflight_data.get("selection") if isinstance(preflight_data.get("selection"), dict) else None),
|
|
3049
3068
|
)
|
|
3050
|
-
|
|
3051
|
-
|
|
3069
|
+
if preflight_data.get("blockers"):
|
|
3070
|
+
return self._record_write_blocked_response(
|
|
3071
|
+
raw_preflight,
|
|
3072
|
+
operation="insert",
|
|
3073
|
+
normalized_payload=normalized_payload,
|
|
3074
|
+
output_profile=output_profile,
|
|
3075
|
+
human_review=False,
|
|
3076
|
+
target_resource={"type": "record", "app_key": app_key, "record_id": None, "record_ids": []},
|
|
3077
|
+
)
|
|
3078
|
+
try:
|
|
3079
|
+
write_attempted = True
|
|
3080
|
+
raw_apply = self.record_create(
|
|
3081
|
+
profile=profile,
|
|
3082
|
+
app_key=app_key,
|
|
3083
|
+
answers=cast(list[JSONObject], preflight_data.get("normalized_answers", [])),
|
|
3084
|
+
fields={},
|
|
3085
|
+
submit_type=submit_type_value,
|
|
3086
|
+
verify_write=verify_write,
|
|
3087
|
+
force_refresh_form=preflight_used_force_refresh,
|
|
3088
|
+
)
|
|
3089
|
+
except QingflowApiError as exc:
|
|
3090
|
+
self._raise_record_write_permission_error(
|
|
3091
|
+
exc,
|
|
3092
|
+
operation="insert",
|
|
3093
|
+
app_key=app_key,
|
|
3094
|
+
record_id=None,
|
|
3095
|
+
selection=cast(JSONObject | None, preflight_data.get("selection") if isinstance(preflight_data.get("selection"), dict) else None),
|
|
3096
|
+
)
|
|
3097
|
+
raise
|
|
3098
|
+
return self._record_write_apply_response(
|
|
3099
|
+
raw_apply,
|
|
3100
|
+
operation="insert",
|
|
3101
|
+
normalized_payload=normalized_payload,
|
|
3102
|
+
output_profile=output_profile,
|
|
3103
|
+
human_review=False,
|
|
3104
|
+
preflight=raw_preflight,
|
|
3105
|
+
)
|
|
3106
|
+
except (QingflowApiError, RuntimeError) as exc:
|
|
3107
|
+
if not capture_exceptions:
|
|
3108
|
+
raise
|
|
3109
|
+
return self._record_write_exception_response(
|
|
3052
3110
|
exc,
|
|
3053
3111
|
operation="insert",
|
|
3112
|
+
profile=profile,
|
|
3054
3113
|
app_key=app_key,
|
|
3055
3114
|
record_id=None,
|
|
3056
|
-
|
|
3115
|
+
output_profile=output_profile,
|
|
3116
|
+
human_review=False,
|
|
3117
|
+
write_executed=write_attempted,
|
|
3057
3118
|
)
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3119
|
+
|
|
3120
|
+
def _normalize_public_record_insert_batch_items(
|
|
3121
|
+
self,
|
|
3122
|
+
*,
|
|
3123
|
+
fields: JSONObject | None,
|
|
3124
|
+
items: list[JSONObject] | None,
|
|
3125
|
+
) -> list[JSONObject]:
|
|
3126
|
+
"""执行内部辅助逻辑。"""
|
|
3127
|
+
if fields is not None:
|
|
3128
|
+
raise_tool_error(QingflowApiError.config_error("record_insert batch mode does not accept fields"))
|
|
3129
|
+
if not isinstance(items, list) or not items:
|
|
3130
|
+
raise_tool_error(QingflowApiError.config_error("items must be a non-empty list"))
|
|
3131
|
+
normalized_items: list[JSONObject] = []
|
|
3132
|
+
for index, item in enumerate(items):
|
|
3133
|
+
if not isinstance(item, dict):
|
|
3134
|
+
raise_tool_error(QingflowApiError.config_error(f"items[{index}] must be an object"))
|
|
3135
|
+
item_fields = item.get("fields")
|
|
3136
|
+
if not isinstance(item_fields, dict):
|
|
3137
|
+
raise_tool_error(QingflowApiError.config_error(f"items[{index}].fields must be an object map keyed by field title"))
|
|
3138
|
+
normalized_items.append({"fields": cast(JSONObject, item_fields)})
|
|
3139
|
+
return normalized_items
|
|
3140
|
+
|
|
3141
|
+
def _record_insert_public_batch(
|
|
3142
|
+
self,
|
|
3143
|
+
*,
|
|
3144
|
+
profile: str,
|
|
3145
|
+
app_key: str,
|
|
3146
|
+
items: list[JSONObject],
|
|
3147
|
+
verify_write: bool,
|
|
3148
|
+
output_profile: str,
|
|
3149
|
+
) -> JSONObject:
|
|
3150
|
+
"""执行内部辅助逻辑。"""
|
|
3151
|
+
responses: list[JSONObject] = []
|
|
3152
|
+
for item in items:
|
|
3153
|
+
responses.append(
|
|
3154
|
+
self._record_insert_public_single(
|
|
3155
|
+
profile=profile,
|
|
3156
|
+
app_key=app_key,
|
|
3157
|
+
fields=cast(JSONObject, item["fields"]),
|
|
3158
|
+
verify_write=verify_write,
|
|
3159
|
+
output_profile=output_profile,
|
|
3160
|
+
capture_exceptions=True,
|
|
3161
|
+
)
|
|
3162
|
+
)
|
|
3163
|
+
return self._record_insert_batch_response(
|
|
3164
|
+
profile=profile,
|
|
3165
|
+
app_key=app_key,
|
|
3166
|
+
responses=responses,
|
|
3167
|
+
output_profile=output_profile,
|
|
3168
|
+
)
|
|
3169
|
+
|
|
3170
|
+
def _record_insert_batch_response(
|
|
3171
|
+
self,
|
|
3172
|
+
*,
|
|
3173
|
+
profile: str,
|
|
3174
|
+
app_key: str,
|
|
3175
|
+
responses: list[JSONObject],
|
|
3176
|
+
output_profile: str,
|
|
3177
|
+
) -> JSONObject:
|
|
3178
|
+
"""执行内部辅助逻辑。"""
|
|
3179
|
+
items = [
|
|
3180
|
+
self._record_insert_batch_item_from_response(index=index, response=response, output_profile=output_profile)
|
|
3181
|
+
for index, response in enumerate(responses)
|
|
3182
|
+
]
|
|
3183
|
+
summary = self._record_insert_batch_summary(items)
|
|
3184
|
+
status, ok, message = self._record_insert_batch_envelope_status(summary=summary)
|
|
3185
|
+
first_response = responses[0] if responses else {}
|
|
3186
|
+
created_record_ids = [
|
|
3187
|
+
cast(str, item["record_id"])
|
|
3188
|
+
for item in items
|
|
3189
|
+
if isinstance(item.get("record_id"), str) and item.get("record_id")
|
|
3190
|
+
]
|
|
3191
|
+
write_executed = any(bool(item.get("write_executed")) for item in items)
|
|
3192
|
+
verification_status = self._record_insert_batch_verification_status(items)
|
|
3193
|
+
return {
|
|
3194
|
+
"profile": first_response.get("profile", profile),
|
|
3195
|
+
"ws_id": first_response.get("ws_id"),
|
|
3196
|
+
"ok": ok,
|
|
3197
|
+
"status": status,
|
|
3198
|
+
"mode": "batch",
|
|
3199
|
+
"total": summary["total"],
|
|
3200
|
+
"succeeded": summary["succeeded"],
|
|
3201
|
+
"failed": summary["failed"],
|
|
3202
|
+
"created_record_ids": created_record_ids,
|
|
3203
|
+
"write_executed": write_executed,
|
|
3204
|
+
"verification_status": verification_status,
|
|
3205
|
+
"safe_to_retry": not write_executed,
|
|
3206
|
+
"request_route": first_response.get("request_route"),
|
|
3207
|
+
"warnings": [],
|
|
3208
|
+
"output_profile": output_profile,
|
|
3209
|
+
"items": items,
|
|
3210
|
+
"data": {
|
|
3211
|
+
"app_key": app_key,
|
|
3212
|
+
"mode": "batch",
|
|
3213
|
+
"summary": summary,
|
|
3214
|
+
"created_record_ids": created_record_ids,
|
|
3215
|
+
"items": items,
|
|
3216
|
+
},
|
|
3217
|
+
"message": message,
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
def _record_insert_batch_summary(self, items: list[JSONObject]) -> JSONObject:
|
|
3221
|
+
"""执行内部辅助逻辑。"""
|
|
3222
|
+
created = [item for item in items if isinstance(item.get("record_id"), str) and item.get("record_id")]
|
|
3223
|
+
failed = [item for item in items if item.get("status") not in {"success", "verification_failed"}]
|
|
3224
|
+
return {
|
|
3225
|
+
"total": len(items),
|
|
3226
|
+
"succeeded": len(created),
|
|
3227
|
+
"failed": len(failed),
|
|
3228
|
+
"created_count": len(created),
|
|
3229
|
+
"blocked_count": sum(1 for item in items if item.get("status") == "blocked"),
|
|
3230
|
+
"confirmation_count": sum(1 for item in items if item.get("status") == "needs_confirmation"),
|
|
3231
|
+
"verification_failed_count": sum(1 for item in items if item.get("status") == "verification_failed"),
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
def _record_insert_batch_envelope_status(self, *, summary: JSONObject) -> tuple[str, bool, str]:
|
|
3235
|
+
"""执行内部辅助逻辑。"""
|
|
3236
|
+
succeeded = int(summary["succeeded"])
|
|
3237
|
+
failed = int(summary["failed"])
|
|
3238
|
+
if succeeded and failed:
|
|
3239
|
+
return "partial_success", False, "batch insert completed with partial failures"
|
|
3240
|
+
if succeeded and int(summary["verification_failed_count"]):
|
|
3241
|
+
return "verification_failed", True, "batch insert completed but verification failed for some created records"
|
|
3242
|
+
if succeeded:
|
|
3243
|
+
return "success", True, "batch insert completed"
|
|
3244
|
+
if int(summary["confirmation_count"]):
|
|
3245
|
+
return "needs_confirmation", False, "batch insert requires confirmation before retrying failed rows"
|
|
3246
|
+
if int(summary["blocked_count"]):
|
|
3247
|
+
return "blocked", False, "batch insert preflight blocked all rows"
|
|
3248
|
+
return "failed", False, "batch insert failed"
|
|
3249
|
+
|
|
3250
|
+
def _record_insert_batch_verification_status(self, items: list[JSONObject]) -> str:
|
|
3251
|
+
"""执行内部辅助逻辑。"""
|
|
3252
|
+
statuses = {str(item.get("verification_status") or "not_requested") for item in items}
|
|
3253
|
+
if "failed" in statuses:
|
|
3254
|
+
return "failed"
|
|
3255
|
+
if "verified" in statuses:
|
|
3256
|
+
return "verified"
|
|
3257
|
+
return "not_requested"
|
|
3258
|
+
|
|
3259
|
+
def _record_insert_batch_item_from_response(
|
|
3260
|
+
self,
|
|
3261
|
+
*,
|
|
3262
|
+
index: int,
|
|
3263
|
+
response: JSONObject,
|
|
3264
|
+
output_profile: str,
|
|
3265
|
+
) -> JSONObject:
|
|
3266
|
+
"""执行内部辅助逻辑。"""
|
|
3267
|
+
data = cast(JSONObject, response.get("data", {})) if isinstance(response.get("data"), dict) else {}
|
|
3268
|
+
resource = _public_record_resource(data.get("resource"))
|
|
3269
|
+
record_id = _public_record_id_text(response.get("record_id"))
|
|
3270
|
+
apply_id = _public_record_id_text(response.get("apply_id"))
|
|
3271
|
+
if record_id is None and isinstance(resource, dict):
|
|
3272
|
+
record_id = _public_record_id_text(resource.get("record_id"))
|
|
3273
|
+
if apply_id is None and isinstance(resource, dict):
|
|
3274
|
+
apply_id = _public_record_id_text(resource.get("apply_id"))
|
|
3275
|
+
item: JSONObject = {
|
|
3276
|
+
"index": index,
|
|
3277
|
+
"row_number": index + 1,
|
|
3278
|
+
"status": response.get("status"),
|
|
3279
|
+
"write_executed": bool(response.get("write_executed")),
|
|
3280
|
+
"verification_status": response.get("verification_status", "not_requested"),
|
|
3281
|
+
"safe_to_retry": bool(response.get("safe_to_retry", True)),
|
|
3282
|
+
}
|
|
3283
|
+
if record_id is not None:
|
|
3284
|
+
item["record_id"] = record_id
|
|
3285
|
+
if apply_id is not None:
|
|
3286
|
+
item["apply_id"] = apply_id
|
|
3287
|
+
if resource:
|
|
3288
|
+
item["resource"] = resource
|
|
3289
|
+
verification = data.get("verification")
|
|
3290
|
+
if isinstance(verification, dict):
|
|
3291
|
+
compact_verification = {
|
|
3292
|
+
key: verification[key]
|
|
3293
|
+
for key in ("verified", "verification_mode", "field_level_verified")
|
|
3294
|
+
if key in verification
|
|
3295
|
+
}
|
|
3296
|
+
if compact_verification:
|
|
3297
|
+
item["verification"] = compact_verification
|
|
3298
|
+
field_errors = cast(list[JSONObject], data.get("field_errors", [])) if isinstance(data.get("field_errors"), list) else []
|
|
3299
|
+
confirmation_requests = cast(list[JSONObject], data.get("confirmation_requests", [])) if isinstance(data.get("confirmation_requests"), list) else []
|
|
3300
|
+
failed_fields = self._record_write_failed_fields(field_errors=field_errors, confirmation_requests=confirmation_requests)
|
|
3301
|
+
if failed_fields:
|
|
3302
|
+
item["failed_fields"] = failed_fields
|
|
3303
|
+
if confirmation_requests:
|
|
3304
|
+
item["confirmation_requests"] = [
|
|
3305
|
+
self._record_write_semantic_confirmation_request(request)
|
|
3306
|
+
for request in confirmation_requests
|
|
3307
|
+
if isinstance(request, dict)
|
|
3308
|
+
]
|
|
3309
|
+
blockers = data.get("blockers")
|
|
3310
|
+
if isinstance(blockers, list) and blockers:
|
|
3311
|
+
item["blockers"] = blockers
|
|
3312
|
+
warnings = response.get("warnings")
|
|
3313
|
+
if isinstance(warnings, list) and warnings:
|
|
3314
|
+
item["warnings"] = warnings
|
|
3315
|
+
error = data.get("error")
|
|
3316
|
+
if isinstance(error, dict):
|
|
3317
|
+
item["error"] = error
|
|
3318
|
+
if output_profile == "verbose" and isinstance(data.get("debug"), dict):
|
|
3319
|
+
item["debug"] = data.get("debug")
|
|
3320
|
+
return item
|
|
3321
|
+
|
|
3322
|
+
def _record_write_failed_fields(
|
|
3323
|
+
self,
|
|
3324
|
+
*,
|
|
3325
|
+
field_errors: list[JSONObject],
|
|
3326
|
+
confirmation_requests: list[JSONObject],
|
|
3327
|
+
) -> list[JSONObject]:
|
|
3328
|
+
"""执行内部辅助逻辑。"""
|
|
3329
|
+
failed_fields = [
|
|
3330
|
+
self._record_write_semantic_field_error(error)
|
|
3331
|
+
for error in field_errors
|
|
3332
|
+
if isinstance(error, dict)
|
|
3333
|
+
]
|
|
3334
|
+
failed_fields.extend(
|
|
3335
|
+
self._record_write_failed_field_from_confirmation(request)
|
|
3336
|
+
for request in confirmation_requests
|
|
3337
|
+
if isinstance(request, dict)
|
|
3066
3338
|
)
|
|
3339
|
+
return failed_fields
|
|
3340
|
+
|
|
3341
|
+
def _record_write_semantic_field_error(self, error: JSONObject) -> JSONObject:
|
|
3342
|
+
"""执行内部辅助逻辑。"""
|
|
3343
|
+
field = error.get("field")
|
|
3344
|
+
field_payload = cast(JSONObject, field) if isinstance(field, dict) else {}
|
|
3345
|
+
error_code = _normalize_optional_text(error.get("error_code")) or "INVALID_FIELD_VALUE"
|
|
3346
|
+
title = (
|
|
3347
|
+
_normalize_optional_text(field_payload.get("que_title"))
|
|
3348
|
+
or _normalize_optional_text(field_payload.get("title"))
|
|
3349
|
+
or _normalize_optional_text(error.get("location"))
|
|
3350
|
+
or "unknown field"
|
|
3351
|
+
)
|
|
3352
|
+
field_id = (
|
|
3353
|
+
field_payload.get("que_id")
|
|
3354
|
+
if field_payload.get("que_id") is not None
|
|
3355
|
+
else field_payload.get("field_id")
|
|
3356
|
+
)
|
|
3357
|
+
expected_format = error.get("expected_format") if isinstance(error.get("expected_format"), dict) else None
|
|
3358
|
+
if expected_format is None:
|
|
3359
|
+
expected_format = self._record_write_expected_format_from_field_payload(field_payload)
|
|
3360
|
+
payload: JSONObject = {
|
|
3361
|
+
"title": title,
|
|
3362
|
+
"field_id": field_id,
|
|
3363
|
+
"error_code": error_code,
|
|
3364
|
+
"message": self._record_write_semantic_error_message(error_code, error.get("message")),
|
|
3365
|
+
"next_action": self._record_write_next_action_for_error(error_code),
|
|
3366
|
+
}
|
|
3367
|
+
if expected_format is not None:
|
|
3368
|
+
payload["expected_format"] = expected_format
|
|
3369
|
+
payload["example_value"] = self._record_write_example_value_for_format(expected_format, field_payload)
|
|
3370
|
+
if error.get("received_value") is not None:
|
|
3371
|
+
payload["received_value"] = error.get("received_value")
|
|
3372
|
+
if error.get("fix_hint") is not None:
|
|
3373
|
+
payload["fix_hint"] = error.get("fix_hint")
|
|
3374
|
+
if error.get("details") is not None:
|
|
3375
|
+
payload["details"] = error.get("details")
|
|
3376
|
+
return payload
|
|
3377
|
+
|
|
3378
|
+
def _record_write_semantic_confirmation_request(self, request: JSONObject) -> JSONObject:
|
|
3379
|
+
"""执行内部辅助逻辑。"""
|
|
3380
|
+
field_ref = request.get("field_ref")
|
|
3381
|
+
field_payload = cast(JSONObject, field_ref) if isinstance(field_ref, dict) else {}
|
|
3382
|
+
payload: JSONObject = {
|
|
3383
|
+
"field": request.get("field"),
|
|
3384
|
+
"title": _normalize_optional_text(request.get("field")) or _normalize_optional_text(field_payload.get("que_title")),
|
|
3385
|
+
"field_id": field_payload.get("que_id"),
|
|
3386
|
+
"kind": request.get("kind"),
|
|
3387
|
+
"input": request.get("input"),
|
|
3388
|
+
"candidates": request.get("candidates", []),
|
|
3389
|
+
"next_action": "让用户确认候选,或用显式 id/object 只重试本行。",
|
|
3390
|
+
}
|
|
3391
|
+
if request.get("parent_field") is not None:
|
|
3392
|
+
payload["parent_field"] = request.get("parent_field")
|
|
3393
|
+
if request.get("row_ordinal") is not None:
|
|
3394
|
+
payload["row_ordinal"] = request.get("row_ordinal")
|
|
3395
|
+
return payload
|
|
3396
|
+
|
|
3397
|
+
def _record_write_failed_field_from_confirmation(self, request: JSONObject) -> JSONObject:
|
|
3398
|
+
"""执行内部辅助逻辑。"""
|
|
3399
|
+
semantic = self._record_write_semantic_confirmation_request(request)
|
|
3400
|
+
return {
|
|
3401
|
+
"title": semantic.get("title") or semantic.get("field"),
|
|
3402
|
+
"field_id": semantic.get("field_id"),
|
|
3403
|
+
"error_code": "LOOKUP_NEEDS_CONFIRMATION",
|
|
3404
|
+
"message": "候选不唯一,需要用户确认。",
|
|
3405
|
+
"kind": semantic.get("kind"),
|
|
3406
|
+
"input": semantic.get("input"),
|
|
3407
|
+
"candidates": semantic.get("candidates", []),
|
|
3408
|
+
"next_action": semantic.get("next_action"),
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
def _record_write_expected_format_from_field_payload(self, field_payload: JSONObject) -> JSONObject | None:
|
|
3412
|
+
"""执行内部辅助逻辑。"""
|
|
3413
|
+
que_type = _coerce_count(field_payload.get("que_type"))
|
|
3414
|
+
if que_type is None:
|
|
3415
|
+
return None
|
|
3416
|
+
synthetic_field = FormField(
|
|
3417
|
+
que_id=_coerce_count(field_payload.get("que_id")) or 0,
|
|
3418
|
+
que_title=_normalize_optional_text(field_payload.get("que_title")) or _normalize_optional_text(field_payload.get("title")) or "",
|
|
3419
|
+
que_type=que_type,
|
|
3420
|
+
required=False,
|
|
3421
|
+
readonly=False,
|
|
3422
|
+
system=False,
|
|
3423
|
+
options=[],
|
|
3424
|
+
aliases=[],
|
|
3425
|
+
target_app_key=None,
|
|
3426
|
+
target_app_name_hint=None,
|
|
3427
|
+
member_select_scope_type=None,
|
|
3428
|
+
member_select_scope=None,
|
|
3429
|
+
dept_select_scope_type=None,
|
|
3430
|
+
dept_select_scope=None,
|
|
3431
|
+
raw={},
|
|
3432
|
+
)
|
|
3433
|
+
return _write_format_for_field(synthetic_field)
|
|
3434
|
+
|
|
3435
|
+
def _record_write_example_value_for_format(self, expected_format: JSONObject, field_payload: JSONObject) -> JSONValue:
|
|
3436
|
+
"""执行内部辅助逻辑。"""
|
|
3437
|
+
examples = expected_format.get("examples")
|
|
3438
|
+
if isinstance(examples, list) and examples:
|
|
3439
|
+
return cast(JSONValue, examples[0])
|
|
3440
|
+
kind = _normalize_optional_text(expected_format.get("kind"))
|
|
3441
|
+
if kind == "member_list":
|
|
3442
|
+
return "张三"
|
|
3443
|
+
if kind == "department_list":
|
|
3444
|
+
return "直销部"
|
|
3445
|
+
if kind == "relation_record":
|
|
3446
|
+
return {"apply_id": "5001"}
|
|
3447
|
+
if kind == "attachment_list":
|
|
3448
|
+
return {"value": "<file_upload_local 返回的 value/url>", "name": "example.pdf"}
|
|
3449
|
+
if kind == "subtable_rows":
|
|
3450
|
+
return {"rows": [{"子字段": "值"}]}
|
|
3451
|
+
if kind == "date_string":
|
|
3452
|
+
return "2026-03-13 10:00:00"
|
|
3453
|
+
if kind == "boolean_label":
|
|
3454
|
+
return "是"
|
|
3455
|
+
if kind in {"single_select", "multi_select"}:
|
|
3456
|
+
options = expected_format.get("options")
|
|
3457
|
+
if isinstance(options, list) and options:
|
|
3458
|
+
return cast(JSONValue, options[0])
|
|
3459
|
+
que_type = _coerce_count(field_payload.get("que_type"))
|
|
3460
|
+
if que_type in NUMBER_QUE_TYPES:
|
|
3461
|
+
return 100
|
|
3462
|
+
return "文本"
|
|
3463
|
+
|
|
3464
|
+
def _record_write_semantic_error_message(self, error_code: str, fallback: JSONValue) -> str:
|
|
3465
|
+
"""执行内部辅助逻辑。"""
|
|
3466
|
+
if error_code == "MISSING_REQUIRED_FIELD":
|
|
3467
|
+
return "缺少必填字段。"
|
|
3468
|
+
if error_code == "FIELD_NOT_FOUND":
|
|
3469
|
+
return "字段不存在或字段标题不匹配。"
|
|
3470
|
+
if error_code == "AMBIGUOUS_FIELD":
|
|
3471
|
+
return "字段标题存在歧义。"
|
|
3472
|
+
if error_code in {"INVALID_FIELD_VALUE", "INVALID_MEMBER_VALUE", "INVALID_DEPARTMENT_VALUE", "INVALID_RELATION_VALUE"}:
|
|
3473
|
+
return _normalize_optional_text(fallback) or "字段值格式不正确。"
|
|
3474
|
+
return _normalize_optional_text(fallback) or "字段写入失败。"
|
|
3475
|
+
|
|
3476
|
+
def _record_write_next_action_for_error(self, error_code: str) -> str:
|
|
3477
|
+
"""执行内部辅助逻辑。"""
|
|
3478
|
+
if error_code == "MISSING_REQUIRED_FIELD":
|
|
3479
|
+
return "补充该字段后只重试本行。"
|
|
3480
|
+
if error_code in {"FIELD_NOT_FOUND", "AMBIGUOUS_FIELD"}:
|
|
3481
|
+
return "重新调用 schema 工具确认字段标题或 field_id 后,只重试本行。"
|
|
3482
|
+
return "修正该字段值后只重试本行。"
|
|
3067
3483
|
|
|
3068
3484
|
@tool_cn_name("更新记录")
|
|
3069
3485
|
def record_update_public(
|
|
@@ -7357,7 +7773,7 @@ class RecordTools(ToolBase):
|
|
|
7357
7773
|
"result": result,
|
|
7358
7774
|
"normalized_answers": normalized_answers,
|
|
7359
7775
|
"status": "completed" if verified else "verification_failed",
|
|
7360
|
-
"ok":
|
|
7776
|
+
"ok": True,
|
|
7361
7777
|
"apply_id": apply_id,
|
|
7362
7778
|
"record_id": apply_id,
|
|
7363
7779
|
"verify_write": verify_write,
|
|
@@ -7561,7 +7977,7 @@ class RecordTools(ToolBase):
|
|
|
7561
7977
|
"result": result,
|
|
7562
7978
|
"normalized_answers": normalized_answers,
|
|
7563
7979
|
"status": "completed" if verified else "verification_failed",
|
|
7564
|
-
"ok":
|
|
7980
|
+
"ok": True,
|
|
7565
7981
|
"verify_write": verify_write,
|
|
7566
7982
|
"write_verified": verified if verify_write else None,
|
|
7567
7983
|
"verification": verification,
|
|
@@ -10100,6 +10516,9 @@ class RecordTools(ToolBase):
|
|
|
10100
10516
|
"ws_id": raw_preflight.get("ws_id"),
|
|
10101
10517
|
"ok": False,
|
|
10102
10518
|
"status": status,
|
|
10519
|
+
"write_executed": False,
|
|
10520
|
+
"verification_status": "not_requested",
|
|
10521
|
+
"safe_to_retry": True,
|
|
10103
10522
|
"request_route": raw_preflight.get("request_route"),
|
|
10104
10523
|
"warnings": warnings,
|
|
10105
10524
|
"output_profile": output_profile,
|
|
@@ -10143,6 +10562,9 @@ class RecordTools(ToolBase):
|
|
|
10143
10562
|
"ws_id": raw_preflight.get("ws_id"),
|
|
10144
10563
|
"ok": True,
|
|
10145
10564
|
"status": "ready",
|
|
10565
|
+
"write_executed": False,
|
|
10566
|
+
"verification_status": "not_requested",
|
|
10567
|
+
"safe_to_retry": True,
|
|
10146
10568
|
"request_route": raw_preflight.get("request_route"),
|
|
10147
10569
|
"warnings": warnings,
|
|
10148
10570
|
"output_profile": output_profile,
|
|
@@ -10185,17 +10607,45 @@ class RecordTools(ToolBase):
|
|
|
10185
10607
|
resolved_fields = cast(list[JSONObject], preflight_data.get("lookup_resolved_fields", []))
|
|
10186
10608
|
if isinstance(verification_warnings, list):
|
|
10187
10609
|
warnings.extend(cast(list[JSONObject], [item for item in verification_warnings if isinstance(item, dict)]))
|
|
10610
|
+
resource = _public_record_resource(raw_apply.get("resource"))
|
|
10611
|
+
record_id = _public_record_id_text(resource.get("record_id")) if isinstance(resource, dict) else None
|
|
10612
|
+
apply_id = _public_record_id_text(resource.get("apply_id")) if isinstance(resource, dict) else None
|
|
10613
|
+
if record_id is None:
|
|
10614
|
+
record_id = _public_record_id_text(raw_apply.get("record_id"))
|
|
10615
|
+
if apply_id is None:
|
|
10616
|
+
apply_id = _public_record_id_text(raw_apply.get("apply_id"))
|
|
10617
|
+
if apply_id is None:
|
|
10618
|
+
apply_id = record_id
|
|
10619
|
+
if record_id is None:
|
|
10620
|
+
record_id = apply_id
|
|
10621
|
+
write_executed = True
|
|
10622
|
+
verification_requested = (
|
|
10623
|
+
raw_apply.get("verify_write") is True
|
|
10624
|
+
or raw_apply.get("write_verified") is not None
|
|
10625
|
+
or isinstance(raw_apply.get("verification"), dict)
|
|
10626
|
+
)
|
|
10627
|
+
if verification_requested:
|
|
10628
|
+
verification_status = "verified" if bool(verification.get("verified")) else "failed"
|
|
10629
|
+
else:
|
|
10630
|
+
verification_status = "not_requested"
|
|
10631
|
+
raw_status = _normalize_optional_text(raw_apply.get("status"))
|
|
10632
|
+
response_status = "verification_failed" if verification_status == "failed" else "success"
|
|
10633
|
+
if not bool(raw_apply.get("ok", True)) and verification_status != "failed":
|
|
10634
|
+
response_status = raw_status or "failed"
|
|
10188
10635
|
response: JSONObject = {
|
|
10189
10636
|
"profile": raw_apply.get("profile"),
|
|
10190
10637
|
"ws_id": raw_apply.get("ws_id"),
|
|
10191
|
-
"ok": bool(raw_apply.get("ok", True)),
|
|
10192
|
-
"status":
|
|
10638
|
+
"ok": True if verification_status == "failed" and write_executed else bool(raw_apply.get("ok", True)),
|
|
10639
|
+
"status": response_status,
|
|
10640
|
+
"write_executed": write_executed,
|
|
10641
|
+
"verification_status": verification_status,
|
|
10642
|
+
"safe_to_retry": False,
|
|
10193
10643
|
"request_route": raw_apply.get("request_route"),
|
|
10194
10644
|
"warnings": warnings,
|
|
10195
10645
|
"output_profile": output_profile,
|
|
10196
10646
|
"data": {
|
|
10197
10647
|
"action": {"operation": operation, "executed": True},
|
|
10198
|
-
"resource":
|
|
10648
|
+
"resource": resource,
|
|
10199
10649
|
"verification": raw_apply.get("verification"),
|
|
10200
10650
|
"normalized_payload": normalized_payload,
|
|
10201
10651
|
"blockers": [],
|
|
@@ -10205,6 +10655,10 @@ class RecordTools(ToolBase):
|
|
|
10205
10655
|
"human_review": self._record_write_human_review_payload(operation, enabled=human_review),
|
|
10206
10656
|
},
|
|
10207
10657
|
}
|
|
10658
|
+
if record_id is not None:
|
|
10659
|
+
response["record_id"] = record_id
|
|
10660
|
+
if apply_id is not None:
|
|
10661
|
+
response["apply_id"] = apply_id
|
|
10208
10662
|
if output_profile == "verbose":
|
|
10209
10663
|
debug: JSONObject = {
|
|
10210
10664
|
"legacy_result": raw_apply.get("result"),
|
|
@@ -10223,9 +10677,10 @@ class RecordTools(ToolBase):
|
|
|
10223
10677
|
operation: str,
|
|
10224
10678
|
profile: str,
|
|
10225
10679
|
app_key: str,
|
|
10226
|
-
record_id:
|
|
10680
|
+
record_id: Any | None,
|
|
10227
10681
|
output_profile: str,
|
|
10228
10682
|
human_review: bool,
|
|
10683
|
+
write_executed: bool = True,
|
|
10229
10684
|
) -> JSONObject:
|
|
10230
10685
|
"""执行内部辅助逻辑。"""
|
|
10231
10686
|
error_payload: JSONObject = {
|
|
@@ -10266,11 +10721,14 @@ class RecordTools(ToolBase):
|
|
|
10266
10721
|
"ws_id": None,
|
|
10267
10722
|
"ok": False,
|
|
10268
10723
|
"status": "failed",
|
|
10724
|
+
"write_executed": write_executed,
|
|
10725
|
+
"verification_status": "failed" if write_executed else "not_requested",
|
|
10726
|
+
"safe_to_retry": not write_executed,
|
|
10269
10727
|
"request_route": request_route,
|
|
10270
10728
|
"warnings": [],
|
|
10271
10729
|
"output_profile": output_profile,
|
|
10272
10730
|
"data": {
|
|
10273
|
-
"action": {"operation": operation, "executed":
|
|
10731
|
+
"action": {"operation": operation, "executed": write_executed},
|
|
10274
10732
|
"resource": {"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
10275
10733
|
"verification": None,
|
|
10276
10734
|
"normalized_payload": None,
|
|
@@ -13658,11 +14116,15 @@ def _record_detail_associated_resource(raw: JSONObject) -> JSONObject:
|
|
|
13658
14116
|
view_key = _normalize_optional_text(raw.get("viewKey", raw.get("viewgraphKey")))
|
|
13659
14117
|
chart_key = _normalize_optional_text(raw.get("chartKey", raw.get("chartId")))
|
|
13660
14118
|
is_view = bool(view_key) or graph_type.endswith("view") or graph_type == "view"
|
|
14119
|
+
if is_view and not view_key and chart_key:
|
|
14120
|
+
view_key = chart_key
|
|
14121
|
+
chart_key = None
|
|
13661
14122
|
resource_type = "view" if is_view else "report"
|
|
13662
14123
|
view_type = _normalize_optional_text(raw.get("viewType", raw.get("viewgraphType", raw.get("graphType"))))
|
|
13663
14124
|
data_access = _record_detail_resource_data_access(resource_type=resource_type, view_type=view_type)
|
|
13664
14125
|
return {
|
|
13665
14126
|
"type": resource_type,
|
|
14127
|
+
"resource_type": resource_type,
|
|
13666
14128
|
"name": _normalize_optional_text(raw.get("viewName", raw.get("chartName", raw.get("name", raw.get("title"))))),
|
|
13667
14129
|
"app_key": _normalize_optional_text(raw.get("appKey", raw.get("targetAppKey"))),
|
|
13668
14130
|
"app_name": _normalize_optional_text(raw.get("formTitle", raw.get("appName", raw.get("targetAppName")))),
|
|
@@ -13670,10 +14132,15 @@ def _record_detail_associated_resource(raw: JSONObject) -> JSONObject:
|
|
|
13670
14132
|
"chart_key": chart_key,
|
|
13671
14133
|
"view_type": view_type,
|
|
13672
14134
|
"graph_type": raw.get("graphType"),
|
|
14135
|
+
"report_source": _record_detail_report_source(raw.get("sourceType")) if resource_type == "report" else None,
|
|
13673
14136
|
"data_access": data_access,
|
|
13674
14137
|
}
|
|
13675
14138
|
|
|
13676
14139
|
|
|
14140
|
+
def _record_detail_report_source(source_type: Any) -> str:
|
|
14141
|
+
return "dataset" if str(source_type or "").strip().upper() == "BI_DATASET" else "app"
|
|
14142
|
+
|
|
14143
|
+
|
|
13677
14144
|
def _record_detail_resource_data_access(*, resource_type: str, view_type: str | None) -> JSONObject:
|
|
13678
14145
|
if resource_type == "report":
|
|
13679
14146
|
return {
|
|
@@ -18316,6 +18783,105 @@ def _write_format_for_field(field: FormField) -> JSONObject:
|
|
|
18316
18783
|
return _write_support_payload(support_level="full", kind="scalar_text")
|
|
18317
18784
|
|
|
18318
18785
|
|
|
18786
|
+
def _ready_schema_format_hint(kind: str, write_format: JSONObject) -> str:
|
|
18787
|
+
if kind == "member":
|
|
18788
|
+
return "可直接填成员姓名;唯一匹配会自动解析,重名时会返回候选确认。也可传成员 id/value 对象。"
|
|
18789
|
+
if kind == "department":
|
|
18790
|
+
return "可直接填部门名称;唯一匹配会自动解析,重名时会返回候选确认。也可传部门 id/value 对象。"
|
|
18791
|
+
if kind == "relation":
|
|
18792
|
+
return "可传目标记录 apply_id/record_id 对象,也可填目标记录的可搜索文本;多候选时会返回确认。"
|
|
18793
|
+
if kind == "attachment":
|
|
18794
|
+
return "先调用 file_upload_local 上传文件,再写入上传返回的附件对象或 value/name。"
|
|
18795
|
+
if kind == "subtable":
|
|
18796
|
+
return "传 {'rows': [{...}]} 或直接传行对象数组;每行 key 使用子字段标题。"
|
|
18797
|
+
if kind == "address":
|
|
18798
|
+
return "传省/市/区/详细地址对象、地址明细字符串,或后端地址 parts 数组。"
|
|
18799
|
+
if kind == "single_select":
|
|
18800
|
+
return "传 options 中的一个选项文本。"
|
|
18801
|
+
if kind == "multi_select":
|
|
18802
|
+
return "传 options 中的多个选项文本数组。"
|
|
18803
|
+
if kind == "boolean":
|
|
18804
|
+
return "传 '是' 或 '否'。"
|
|
18805
|
+
if kind == "date":
|
|
18806
|
+
return "推荐传 'YYYY-MM-DD HH:MM:SS';只有日期时可传 'YYYY-MM-DD'。"
|
|
18807
|
+
if kind == "number":
|
|
18808
|
+
return "传数字或数字字符串。"
|
|
18809
|
+
if kind == "unsupported":
|
|
18810
|
+
reason = _normalize_optional_text(write_format.get("reason"))
|
|
18811
|
+
return reason or "该字段不支持直接写入。"
|
|
18812
|
+
return "传文本值。"
|
|
18813
|
+
|
|
18814
|
+
|
|
18815
|
+
def _ready_schema_example_value(
|
|
18816
|
+
kind: str,
|
|
18817
|
+
field: FormField,
|
|
18818
|
+
write_format: JSONObject,
|
|
18819
|
+
*,
|
|
18820
|
+
row_fields: list[JSONObject],
|
|
18821
|
+
) -> JSONValue:
|
|
18822
|
+
if kind == "member":
|
|
18823
|
+
return "张三"
|
|
18824
|
+
if kind == "department":
|
|
18825
|
+
return "直销部"
|
|
18826
|
+
if kind == "relation":
|
|
18827
|
+
return {"apply_id": "5001"}
|
|
18828
|
+
if kind == "attachment":
|
|
18829
|
+
return {"value": "<file_upload_local 返回的 value/url>", "name": "example.pdf"}
|
|
18830
|
+
if kind == "subtable":
|
|
18831
|
+
row: JSONObject = {}
|
|
18832
|
+
for item in row_fields:
|
|
18833
|
+
if not isinstance(item, dict):
|
|
18834
|
+
continue
|
|
18835
|
+
title = _normalize_optional_text(item.get("title"))
|
|
18836
|
+
if not title:
|
|
18837
|
+
continue
|
|
18838
|
+
row[title] = item.get("example_value", _ready_schema_template_scalar(item.get("kind")))
|
|
18839
|
+
if not row:
|
|
18840
|
+
row = {"子字段": "值"}
|
|
18841
|
+
return {"rows": [row]}
|
|
18842
|
+
if kind == "address":
|
|
18843
|
+
examples = write_format.get("examples")
|
|
18844
|
+
if isinstance(examples, list) and examples:
|
|
18845
|
+
return deepcopy(cast(JSONValue, examples[0]))
|
|
18846
|
+
return {"province": "上海市", "city": "上海市", "district": "闵行区", "detail": "浦江路99号"}
|
|
18847
|
+
if kind == "single_select":
|
|
18848
|
+
return field.options[0] if field.options else "选项A"
|
|
18849
|
+
if kind == "multi_select":
|
|
18850
|
+
return [field.options[0]] if field.options else ["选项A"]
|
|
18851
|
+
if kind == "boolean":
|
|
18852
|
+
return "是"
|
|
18853
|
+
if kind == "date":
|
|
18854
|
+
return "2026-03-13 10:00:00"
|
|
18855
|
+
if kind == "number":
|
|
18856
|
+
return 100
|
|
18857
|
+
if kind == "unsupported":
|
|
18858
|
+
return None
|
|
18859
|
+
return "示例文本"
|
|
18860
|
+
|
|
18861
|
+
|
|
18862
|
+
def _ready_schema_template_scalar(kind: Any) -> JSONValue:
|
|
18863
|
+
normalized = _normalize_optional_text(kind)
|
|
18864
|
+
if normalized == "number":
|
|
18865
|
+
return 100
|
|
18866
|
+
if normalized == "date":
|
|
18867
|
+
return "2026-03-13 10:00:00"
|
|
18868
|
+
if normalized == "boolean":
|
|
18869
|
+
return "是"
|
|
18870
|
+
if normalized == "member":
|
|
18871
|
+
return "张三"
|
|
18872
|
+
if normalized == "department":
|
|
18873
|
+
return "直销部"
|
|
18874
|
+
if normalized == "relation":
|
|
18875
|
+
return {"apply_id": "5001"}
|
|
18876
|
+
if normalized == "multi_select":
|
|
18877
|
+
return ["选项A"]
|
|
18878
|
+
if normalized == "attachment":
|
|
18879
|
+
return {"value": "<file_upload_local 返回的 value/url>", "name": "example.pdf"}
|
|
18880
|
+
if normalized == "address":
|
|
18881
|
+
return {"province": "上海市", "city": "上海市", "district": "闵行区", "detail": "浦江路99号"}
|
|
18882
|
+
return "值"
|
|
18883
|
+
|
|
18884
|
+
|
|
18319
18885
|
def _summarize_write_support(resolved_fields: list[JSONObject]) -> JSONObject:
|
|
18320
18886
|
summary: JSONObject = {
|
|
18321
18887
|
"full": [],
|