From c0b67fd87995c53fdb817ee8a5e70081847d8328 Mon Sep 17 00:00:00 2001 From: wuqi Date: Sun, 28 Jun 2026 21:40:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(launch=5Fconfirm):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E7=A1=AE=E8=AE=A4=E6=A0=B7=E6=9C=AC=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=86=85=E9=83=A8=E6=8E=A5=E5=8F=A3(=E4=BE=9B=20price?= =?UTF-8?q?bot=20distill=20=E5=9B=9E=E8=AF=BB)=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐「启动确认窗自进化闭环」里 app-server 这一侧的读路径:pricebot 的 launch_confirm LLM 兜底成功样本会 server→server 落到 app-server 的 launch_confirm_sample 表;本提交 新增「样本列表查询」内部接口, 供 pricebot 的 scripts/distill_launch_confirm.py 回读这些 样本、按「宿主包 × 文案变体」聚合沉淀出候选规则。 - app/api/internal/launch_confirm.py: 新增样本列表内部接口(server→server, 不走用户 JWT) - app/repositories/launch_confirm_sample.py: 样本列表查询 - app/schemas/launch_confirm_sample.py: 列表响应 schema Co-Authored-By: Claude Opus 4.8 (1M context) Reviewed-on: https://gitea.shaguabijia.com/WonderableAI/shaguabijia-app-server/pulls/91 Co-authored-by: wuqi Co-committed-by: wuqi --- app/api/internal/launch_confirm.py | 33 +++++++++++++++++++++++ app/repositories/launch_confirm_sample.py | 26 ++++++++++++++++++ app/schemas/launch_confirm_sample.py | 21 ++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/app/api/internal/launch_confirm.py b/app/api/internal/launch_confirm.py index 5f24d3f..d4dca53 100644 --- a/app/api/internal/launch_confirm.py +++ b/app/api/internal/launch_confirm.py @@ -11,6 +11,7 @@ from __future__ import annotations import hmac import logging +from datetime import datetime, timedelta, timezone from typing import Annotated from fastapi import APIRouter, Header, HTTPException, status @@ -21,6 +22,7 @@ from app.repositories import launch_confirm_sample as repo from app.schemas.launch_confirm_sample import ( LaunchConfirmSampleIn, LaunchConfirmSampleOut, + LaunchConfirmSampleRow, ) logger = logging.getLogger("shagua.internal.launch_confirm") @@ -61,3 +63,34 @@ def report_launch_confirm_sample( payload.exec_success, payload.trace_id, ) return LaunchConfirmSampleOut(id=sid) + + +@router.get( + "/launch-confirm-samples", + response_model=list[LaunchConfirmSampleRow], + summary="启动确认窗兜底样本列表(沉淀脚本 distill 读; server→server, 不走 JWT)", +) +def list_launch_confirm_samples( + db: DbSession, + x_internal_secret: Annotated[str | None, Header()] = None, + exec_success: bool | None = None, + host_package: str | None = None, + since_days: int | None = None, + limit: int = 1000, +) -> list[LaunchConfirmSampleRow]: + """读样本供 pricebot 的 distill_launch_confirm.py 聚合沉淀回 PROFILES。 + + 与上报端点同一把共享密钥;exec_success/host_package/since_days 均可选,limit 默认 1000。 + """ + _check_secret(x_internal_secret) + since = None + if since_days and since_days > 0: + since = datetime.now(timezone.utc) - timedelta(days=since_days) + rows = repo.list_samples( + db, + exec_success=exec_success, + host_package=host_package, + since=since, + limit=limit, + ) + return [LaunchConfirmSampleRow.model_validate(r) for r in rows] diff --git a/app/repositories/launch_confirm_sample.py b/app/repositories/launch_confirm_sample.py index 4d0b226..5e6ecbd 100644 --- a/app/repositories/launch_confirm_sample.py +++ b/app/repositories/launch_confirm_sample.py @@ -2,8 +2,10 @@ from __future__ import annotations import logging +from datetime import datetime from typing import Optional +from sqlalchemy import select from sqlalchemy.orm import Session from app.models.launch_confirm_sample import LaunchConfirmSample @@ -33,3 +35,27 @@ def insert_sample(db: Session, payload: LaunchConfirmSampleIn) -> int: db.commit() db.refresh(row) return row.id + + +def list_samples( + db: Session, + *, + exec_success: Optional[bool] = None, + host_package: Optional[str] = None, + since: Optional[datetime] = None, + limit: int = 1000, +) -> list[LaunchConfirmSample]: + """按条件查样本(pricebot 的 distill_launch_confirm.py 沉淀工具读)。created_at 升序。 + + 过滤项都可空:exec_success(只看放行成功的)/ host_package(只看某宿主包)/ + since(>= created_at)。limit 兜底防一次拉爆全表。 + """ + stmt = select(LaunchConfirmSample) + if exec_success is not None: + stmt = stmt.where(LaunchConfirmSample.exec_success == exec_success) + if host_package: + stmt = stmt.where(LaunchConfirmSample.host_package == host_package) + if since is not None: + stmt = stmt.where(LaunchConfirmSample.created_at >= since) + stmt = stmt.order_by(LaunchConfirmSample.created_at.asc()).limit(limit) + return list(db.execute(stmt).scalars().all()) diff --git a/app/schemas/launch_confirm_sample.py b/app/schemas/launch_confirm_sample.py index e2ed223..7c44636 100644 --- a/app/schemas/launch_confirm_sample.py +++ b/app/schemas/launch_confirm_sample.py @@ -1,7 +1,9 @@ """启动确认窗兜底样本的内部上报模型(pricebot → app-server)。""" from __future__ import annotations -from pydantic import BaseModel +from datetime import datetime + +from pydantic import BaseModel, ConfigDict class LaunchConfirmSampleIn(BaseModel): @@ -24,3 +26,20 @@ class LaunchConfirmSampleOut(BaseModel): """落库结果。""" id: int + + +class LaunchConfirmSampleRow(BaseModel): + """单条样本(列表读出;沉淀脚本 distill 用,payload 原样带出)。""" + + model_config = ConfigDict(from_attributes=True) + + id: int + created_at: datetime + trace_id: str | None = None + device_id: str | None = None + host_package: str | None = None + target_app: str | None = None + system_locale: str | None = None + exec_success: bool = False + dialog_title: str | None = None + payload: dict | None = None