Improve AI batch progress controls
This commit is contained in:
@@ -232,6 +232,7 @@ WORKFLOW_LOCK = threading.Lock()
|
||||
WORKFLOW_READY = False
|
||||
AI_JOB_LOCK = threading.Lock()
|
||||
AI_REVIEW_JOB: dict[str, Any] = {
|
||||
"kind": "ai_review",
|
||||
"running": False,
|
||||
"cancel_requested": False,
|
||||
"scope": "",
|
||||
@@ -248,6 +249,19 @@ AI_REVIEW_JOB: dict[str, Any] = {
|
||||
"last_record_id": None,
|
||||
"privacy_mode": True,
|
||||
}
|
||||
BULK_JOB_LOCK = threading.Lock()
|
||||
BULK_APPROVE_JOB: dict[str, Any] = {
|
||||
"kind": "approve_ai_passed",
|
||||
"running": False,
|
||||
"total": 0,
|
||||
"processed": 0,
|
||||
"updated": 0,
|
||||
"failed": 0,
|
||||
"message": "",
|
||||
"started_at": "",
|
||||
"finished_at": "",
|
||||
}
|
||||
APPROVE_BATCH_SIZE = 500
|
||||
DEFAULT_STATUS_CHECK_TIME = env("REVIEW_STATUS_CHECK_TIME", "03:00") or "03:00"
|
||||
DEFAULT_KIMI_API_BASE = env("MOONSHOT_API_BASE", env("KIMI_API_BASE", "https://api.moonshot.cn/v1")) or "https://api.moonshot.cn/v1"
|
||||
DEFAULT_KIMI_MODEL = env("KIMI_MODEL", "kimi-k2.6") or "kimi-k2.6"
|
||||
@@ -677,7 +691,7 @@ def ai_action_mode_to_override(mode: str) -> dict[str, Any]:
|
||||
def ai_scope_allowed(mode: str, scope: str) -> bool:
|
||||
mode = normalize_kimi_ai_scope_mode(mode)
|
||||
if mode == "all":
|
||||
return scope in {"current", "five", "fifty", "all", "ai_pending"}
|
||||
return scope in {"current", "five", "fifty", "all", "ai_pending", "privacy_blocked"}
|
||||
if mode == "five":
|
||||
return scope in {"current", "five"}
|
||||
if mode == "current":
|
||||
@@ -2716,8 +2730,8 @@ def apply_ai_review_with_retry(record_id: int, attempts: int = 3, kimi_override:
|
||||
|
||||
|
||||
def ai_target_ids(scope: str, record_id: int | None) -> list[int]:
|
||||
if scope not in {"current", "five", "fifty", "all", "ai_pending"}:
|
||||
raise HTTPException(status_code=400, detail="AI核验范围只能是 current/five/fifty/all/ai_pending")
|
||||
if scope not in {"current", "five", "fifty", "all", "ai_pending", "privacy_blocked"}:
|
||||
raise HTTPException(status_code=400, detail="AI核验范围只能是 current/five/fifty/all/ai_pending/privacy_blocked")
|
||||
if scope in {"current", "five", "fifty"} and not record_id:
|
||||
raise HTTPException(status_code=400, detail="缺少当前记录 ID")
|
||||
if scope == "current":
|
||||
@@ -2728,6 +2742,43 @@ def ai_target_ids(scope: str, record_id: int | None) -> list[int]:
|
||||
cur.execute(query, (AI_PENDING_STATUS,))
|
||||
rows = cur.fetchall()
|
||||
return [int(row["id"]) for row in rows]
|
||||
if scope == "privacy_blocked":
|
||||
query = sql.SQL(
|
||||
"""
|
||||
SELECT id
|
||||
FROM {table}
|
||||
WHERE review_status = %s
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_array_elements(COALESCE(review_logs, '[]'::jsonb)) AS log
|
||||
WHERE log->>'changed_by' = 'AI'
|
||||
AND (
|
||||
log->'ai_result'->>'ai_question' ILIKE %s
|
||||
OR log->'ai_result'->'parsed'->>'summary' ILIKE %s
|
||||
OR (log->'ai_result'->'parsed'->'remaining_issues')::text ILIKE %s
|
||||
)
|
||||
AND (
|
||||
(log->'ai_result'->'parsed'->'remaining_issues')::text ILIKE %s
|
||||
OR (log->'ai_result'->'parsed'->'remaining_issues')::text ILIKE %s
|
||||
)
|
||||
)
|
||||
ORDER BY id
|
||||
"""
|
||||
).format(table=table_identifier())
|
||||
with connect() as conn, conn.cursor() as cur:
|
||||
cur.execute(
|
||||
query,
|
||||
(
|
||||
AI_PENDING_STATUS,
|
||||
"%隐私模式本地判定%",
|
||||
"%隐私模式未上传敏感区域%",
|
||||
"%隐私模式不上传%",
|
||||
"%基本信息%",
|
||||
"%地址联系人%",
|
||||
),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [int(row["id"]) for row in rows]
|
||||
|
||||
where = sql.SQL("review_status = 'needs_review'")
|
||||
params: list[Any] = []
|
||||
@@ -2784,6 +2835,7 @@ def run_ai_review_job(scope: str, ids: list[int], kimi_override: dict[str, Any]
|
||||
privacy_mode = normalize_bool((kimi_override or {}).get("privacy_mode"), True)
|
||||
stop_event = threading.Event()
|
||||
update_ai_job(
|
||||
kind="ai_review",
|
||||
running=True,
|
||||
cancel_requested=False,
|
||||
scope=scope,
|
||||
@@ -2866,7 +2918,28 @@ def run_ai_review_job(scope: str, ids: list[int], kimi_override: dict[str, Any]
|
||||
)
|
||||
|
||||
|
||||
def mark_ai_no_issue_reviewed() -> int:
|
||||
def ai_no_issue_reviewed_ids() -> list[int]:
|
||||
query = sql.SQL(
|
||||
"""
|
||||
SELECT id
|
||||
FROM {table}
|
||||
WHERE review_status IN ('AI已处理-OK', 'AI复核-无问题')
|
||||
OR (
|
||||
review_status = 'auto_pass'
|
||||
AND COALESCE(review_logs, '[]'::jsonb) @> jsonb_build_array(jsonb_build_object('changed_by', 'AI'))
|
||||
)
|
||||
ORDER BY id
|
||||
"""
|
||||
).format(table=table_identifier())
|
||||
with connect() as conn, conn.cursor() as cur:
|
||||
cur.execute(query)
|
||||
rows = cur.fetchall()
|
||||
return [int(row["id"]) for row in rows]
|
||||
|
||||
|
||||
def mark_ai_no_issue_reviewed_batch(record_ids: list[int]) -> int:
|
||||
if not record_ids:
|
||||
return 0
|
||||
changed_at = datetime.now().isoformat(timespec="seconds")
|
||||
note = f"批量确认历史AI已通过({datetime.now().strftime('%Y-%m-%d %H:%M:%S')}): 确认为已人工复核"
|
||||
query = sql.SQL(
|
||||
@@ -2888,17 +2961,69 @@ def mark_ai_no_issue_reviewed() -> int:
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE review_status IN ('AI已处理-OK', 'AI复核-无问题')
|
||||
WHERE id = ANY(%s)
|
||||
AND (
|
||||
review_status IN ('AI已处理-OK', 'AI复核-无问题')
|
||||
OR (
|
||||
review_status = 'auto_pass'
|
||||
AND COALESCE(review_logs, '[]'::jsonb) @> jsonb_build_array(jsonb_build_object('changed_by', 'AI'))
|
||||
)
|
||||
)
|
||||
"""
|
||||
).format(table=table_identifier())
|
||||
with connect() as conn, conn.cursor() as cur:
|
||||
cur.execute(query, (datetime.now().strftime("%Y%m%d%H%M%S%f"), changed_at, note))
|
||||
cur.execute(query, (datetime.now().strftime("%Y%m%d%H%M%S%f"), changed_at, note, record_ids))
|
||||
count = cur.rowcount
|
||||
conn.commit()
|
||||
refresh_status_snapshot(source="bulk")
|
||||
return int(count)
|
||||
|
||||
|
||||
def update_bulk_approve_job(**updates: Any) -> dict[str, Any]:
|
||||
with BULK_JOB_LOCK:
|
||||
BULK_APPROVE_JOB.update(updates)
|
||||
return dict(BULK_APPROVE_JOB)
|
||||
|
||||
|
||||
def run_approve_ai_no_issue_job(record_ids: list[int]) -> None:
|
||||
update_bulk_approve_job(
|
||||
kind="approve_ai_passed",
|
||||
running=True,
|
||||
total=len(record_ids),
|
||||
processed=0,
|
||||
updated=0,
|
||||
failed=0,
|
||||
message="批量通过AI已通过中",
|
||||
started_at=datetime.now().isoformat(timespec="seconds"),
|
||||
finished_at="",
|
||||
)
|
||||
try:
|
||||
updated = 0
|
||||
for start in range(0, len(record_ids), APPROVE_BATCH_SIZE):
|
||||
batch = record_ids[start : start + APPROVE_BATCH_SIZE]
|
||||
updated += mark_ai_no_issue_reviewed_batch(batch)
|
||||
update_bulk_approve_job(
|
||||
processed=min(start + len(batch), len(record_ids)),
|
||||
updated=updated,
|
||||
message="批量通过AI已通过中",
|
||||
)
|
||||
time.sleep(0.05)
|
||||
refresh_status_snapshot(source="bulk")
|
||||
update_bulk_approve_job(
|
||||
running=False,
|
||||
processed=len(record_ids),
|
||||
updated=updated,
|
||||
message=f"批量通过完成,已更新 {updated} 条",
|
||||
finished_at=datetime.now().isoformat(timespec="seconds"),
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
update_bulk_approve_job(
|
||||
running=False,
|
||||
failed=1,
|
||||
message=f"批量通过失败:{exc}",
|
||||
finished_at=datetime.now().isoformat(timespec="seconds"),
|
||||
)
|
||||
|
||||
|
||||
def submit_reviewed_records() -> int:
|
||||
changed_at = datetime.now().isoformat(timespec="seconds")
|
||||
note = f"一键提交已人工复核项目({datetime.now().strftime('%Y-%m-%d %H:%M:%S')}): 已从患者首页复核工作台隐藏"
|
||||
@@ -3370,6 +3495,12 @@ def ai_review_status():
|
||||
return dict(AI_REVIEW_JOB)
|
||||
|
||||
|
||||
@app.get("/api/ai/review/approve-no-issue/status")
|
||||
def approve_ai_no_issue_status():
|
||||
with BULK_JOB_LOCK:
|
||||
return dict(BULK_APPROVE_JOB)
|
||||
|
||||
|
||||
@app.post("/api/ai/review/cancel")
|
||||
def cancel_ai_review():
|
||||
with AI_JOB_LOCK:
|
||||
@@ -3381,8 +3512,28 @@ def cancel_ai_review():
|
||||
|
||||
@app.post("/api/ai/review/approve-no-issue")
|
||||
def approve_ai_no_issue():
|
||||
count = mark_ai_no_issue_reviewed()
|
||||
return {"ok": True, "updated": count, "status_snapshot": load_local_settings().get("status_snapshot")}
|
||||
with BULK_JOB_LOCK:
|
||||
if BULK_APPROVE_JOB.get("running"):
|
||||
raise HTTPException(status_code=409, detail="已有批量通过任务正在运行")
|
||||
ids = ai_no_issue_reviewed_ids()
|
||||
if not ids:
|
||||
update_bulk_approve_job(
|
||||
kind="approve_ai_passed",
|
||||
running=False,
|
||||
total=0,
|
||||
processed=0,
|
||||
updated=0,
|
||||
failed=0,
|
||||
message="当前没有AI已通过记录需要批量通过",
|
||||
started_at=datetime.now().isoformat(timespec="seconds"),
|
||||
finished_at=datetime.now().isoformat(timespec="seconds"),
|
||||
)
|
||||
return dict(BULK_APPROVE_JOB)
|
||||
thread = threading.Thread(target=run_approve_ai_no_issue_job, args=(ids,), name="approve-ai-passed", daemon=True)
|
||||
thread.start()
|
||||
time.sleep(0.1)
|
||||
with BULK_JOB_LOCK:
|
||||
return dict(BULK_APPROVE_JOB)
|
||||
|
||||
|
||||
@app.post("/api/ai/review")
|
||||
|
||||
Reference in New Issue
Block a user