From 61ae3b0451f9c6a0c7e3ede4be2a41627d731199 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Fri, 7 Aug 2026 18:31:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(ECR-016):=20UserIntelligence=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B4=9E=E5=AF=9F=E5=8F=AA=E8=AF=BB=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=B9=B6=20Closed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 聚合 GET /admin/users/:id/insight(报告类型/派生标签/行为快照),admin-h5 洞察 Tab;无 migration / 无 UGC / 无真支付。 Co-authored-by: Cursor --- .ai/domain/entity-catalog.md | 6 +- .ai/product/feature-map.md | 2 +- .ai/product/feature-spec/README.md | 1 + .ai/product/feature-spec/ops-admin.md | 4 +- .../feature-spec/ops-user-intelligence.md | 39 ++ apps/admin-h5/src/api/client.ts | 24 +- apps/admin-h5/src/pages/UserDetailPage.vue | 334 ++++++++++++------ apps/api/internal/handler/admin.go | 1 + apps/api/internal/handler/admin_insight.go | 35 ++ .../internal/integration/user_insight_test.go | 124 +++++++ .../internal/repository/user_insight_repo.go | 86 +++++ apps/api/internal/service/admin/insight.go | 106 ++++++ .../BD-2026-016-user-intelligence.md | 24 ++ docs/CHANGELOG.md | 1 + docs/CODE_REVIEW/ECR-016.md | 9 + docs/CONTRACT_DIFF/ECR-016.yaml | 17 + docs/ECR/ECR-016-user-intelligence.md | 19 + .../ECR-016-user-intelligence.md | 8 + docs/HANDOFF/ECR-016-architect-to-engineer.md | 3 + docs/HANDOFF/ECR-016-engineer-to-reviewer.md | 3 + .../PRODUCT_SPEC/ECR-016-user-intelligence.md | 3 + docs/STATE/ECR-016.md | 6 + docs/TASKS/TASK-016-ECR016.yaml | 11 + docs/TEST_REPORT/ECR-016.md | 31 ++ docs/TRACEABILITY.md | 3 + docs/WAVE0/LOOP_AUTHORIZATION.md | 2 +- proto/openapi.yaml | 20 ++ 27 files changed, 807 insertions(+), 115 deletions(-) create mode 100644 .ai/product/feature-spec/ops-user-intelligence.md create mode 100644 apps/api/internal/handler/admin_insight.go create mode 100644 apps/api/internal/integration/user_insight_test.go create mode 100644 apps/api/internal/repository/user_insight_repo.go create mode 100644 apps/api/internal/service/admin/insight.go create mode 100644 docs/BACKEND_DESIGN/BD-2026-016-user-intelligence.md create mode 100644 docs/CODE_REVIEW/ECR-016.md create mode 100644 docs/CONTRACT_DIFF/ECR-016.yaml create mode 100644 docs/ECR/ECR-016-user-intelligence.md create mode 100644 docs/ENGINEERING_SPEC/ECR-016-user-intelligence.md create mode 100644 docs/HANDOFF/ECR-016-architect-to-engineer.md create mode 100644 docs/HANDOFF/ECR-016-engineer-to-reviewer.md create mode 100644 docs/PRODUCT_SPEC/ECR-016-user-intelligence.md create mode 100644 docs/STATE/ECR-016.md create mode 100644 docs/TASKS/TASK-016-ECR016.yaml create mode 100644 docs/TEST_REPORT/ECR-016.md diff --git a/.ai/domain/entity-catalog.md b/.ai/domain/entity-catalog.md index a07efae..63d8ce6 100644 --- a/.ai/domain/entity-catalog.md +++ b/.ai/domain/entity-catalog.md @@ -16,9 +16,9 @@ Status: `Draft` | Profile | Identity_Profile | UserIntelligence | 已存在 | | UserStatus | Identity_Profile | AccountLifecycle | **ECR-013B** 状态机 | | AccountStateTransition | Identity_Profile | AccountLifecycle | **ECR-013B** | -| BehaviorSnapshot | Identity_Profile | UserIntelligence | 未来读模型 | -| PsychologicalTagSet | Identity_Profile | UserIntelligence | 未来读模型 | -| MembershipPlan | Membership_Orders | CommerceEntitlement | 配置面后置 ECR | +| BehaviorSnapshot | Identity_Profile | UserIntelligence | **ECR-016** 读模型(analytics 聚合) | +| PsychologicalTagSet | Identity_Profile | UserIntelligence | **ECR-016** 由报告 type 派生 | +| MembershipPlan | Membership_Orders | CommerceEntitlement | **ECR-014 Closed** | | Entitlement | Membership_Orders | CommerceEntitlement | 后置 | | RedemptionCode | Membership_Orders | CommerceEntitlement | **ECR-015 Closed** | | Membership | Membership_Orders | CommerceEntitlement | 已存在 | diff --git a/.ai/product/feature-map.md b/.ai/product/feature-map.md index f9833b8..77b6c04 100644 --- a/.ai/product/feature-map.md +++ b/.ai/product/feature-map.md @@ -235,7 +235,7 @@ UI **不出现「塔罗」**。禁止神谕吉凶、恐吓话术。 | Phase A `[Ops]` | 登录 · 用户/订单查询 · 会员授予 · 审计 · `apps/admin-h5`(ECR-006 Closed) | | Phase B `[Ops]` | 行为分析:自有埋点 + 管理端「数据」看板(**ECR-007 Closed** · Spec `ops-analytics.md`) | | Phase C `[Ops]` | 内容:首页宫格 CRUD · 测评上下架(**ECR-008 Closed** · Spec `ops-content.md`) | -| Phase D+ | **Contract-First**:**ECR-013A/013B Closed** → **ECR-014** MembershipPlan(Loop continuous)。`docs/WAVE0/LOOP_AUTHORIZATION.md`。 | +| Phase D+ | **Contract-First**:**ECR-013A/B/014/015 Closed** → **ECR-016** UserIntelligence(Loop)。`docs/WAVE0/LOOP_AUTHORIZATION.md`。 | | 排除 | **UGC / 社区广场**(M10.2)仍 `[No]`;真支付最后 | 不计入 P1 Complete;不进入五 Tab。 diff --git a/.ai/product/feature-spec/README.md b/.ai/product/feature-spec/README.md index 736da22..ab8089e 100644 --- a/.ai/product/feature-spec/README.md +++ b/.ai/product/feature-spec/README.md @@ -25,6 +25,7 @@ | [ops-content.md](ops-content.md) | 运营内容(宫格+测评上下架) | §7 | `admin-h5` `/content` · `GET /home/tools` | Ops-C · ECR-008 Closed | | [ops-rbac.md](ops-rbac.md) | 运营 RBAC | §7 | `admin-h5` `/me` permissions · `/admin/roles*` | Ops-D · **ECR-013A Closed** | | [ops-redemption-code.md](ops-redemption-code.md) | 兑换码 RedemptionCode | §7 | `admin-h5` `/codes` · `POST /membership/redeem` | Ops-D · **ECR-015 Closed** | +| [ops-user-intelligence.md](ops-user-intelligence.md) | 用户洞察 UserIntelligence | §7 | `admin-h5` 用户详情「洞察」· `GET /admin/users/:id/insight` | Ops-D · **ECR-016 Closed** | 新功能:复制 `_TEMPLATE.md` → 填满 → 在本表登记 → 再编码。 diff --git a/.ai/product/feature-spec/ops-admin.md b/.ai/product/feature-spec/ops-admin.md index dfd2cc2..68fc6b1 100644 --- a/.ai/product/feature-spec/ops-admin.md +++ b/.ai/product/feature-spec/ops-admin.md @@ -211,6 +211,8 @@ Phase A 可先 `console`/本地;不挡验收。 | **E(ECR-013A Closed)** | Admin RBAC — Spec `ops-rbac.md` · Parent WAVE0-FROZEN | | **F(ECR-013B Closed)** | AccountLifecycle — Spec `ops-account-lifecycle.md` | | **G(ECR-014 Closed)** | MembershipPlan — Spec `ops-membership-plan.md` | -| 后置 | RedemptionCode / UserIntelligence / Entitlement 细权(Loop 续跑) | +| **H(ECR-015 Closed)** | RedemptionCode — Spec `ops-redemption-code.md` | +| **I(ECR-016 Closed)** | UserIntelligence — Spec `ops-user-intelligence.md` | +| 后置 | AskOperations / Entitlement 细权(Loop 续跑) | | D | 订单筛选 · 展示价 · 退款只读(另开 ECR) | | 后置 | 封禁加深(Account_Risk)· 推送占位 | diff --git a/.ai/product/feature-spec/ops-user-intelligence.md b/.ai/product/feature-spec/ops-user-intelligence.md new file mode 100644 index 0000000..bbea799 --- /dev/null +++ b/.ai/product/feature-spec/ops-user-intelligence.md @@ -0,0 +1,39 @@ +# Feature Spec: 用户洞察 UserIntelligence(Ops · ECR-016) + +> Status: `Active`(Loop continuous · **ECR-016 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-015 Closed +> Capability: `UserIntelligence` · BC: `Identity_Profile`(读)+ `Analytics_OpsB`(读) +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +写标签 · 改报告正文 · Ask 全文运营编辑 · UGC · 真支付 · 新表(本切片纯读模型聚合) + +## L2 读模型(无 migration) + +| 概念 | 来源 | +|------|------| +| `UserProfileView` | profiles 摘要(已有 detail) | +| `InsightReport` | growth_reports 类型计数 + 最近列表 | +| `PsychologicalTagSet` | 由报告 type 派生的稳定标签(非 NLP) | +| `BehaviorSnapshot` | analytics_events 最近页面/事件(若有) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/users/:id/insight` | `admin.users.read` | 聚合洞察只读 | + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | 有报告用户 → insight.reports_by_type 非空或 reports 列表 | +| AC-F-02 | tags 含报告类型映射标签 | +| AC-F-03 | 无埋点时 behavior.events=[] 仍 200 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 users.read → 403 | +| AC-P-01 | GET insight < 500ms 本机 | +| AC-O-01 | N/A 只读(无写审计) | + +contract_diff: `docs/CONTRACT_DIFF/ECR-016.yaml` diff --git a/apps/admin-h5/src/api/client.ts b/apps/admin-h5/src/api/client.ts index 7401b3c..fe68289 100644 --- a/apps/admin-h5/src/api/client.ts +++ b/apps/admin-h5/src/api/client.ts @@ -31,7 +31,17 @@ async function request(method: string, path: string, body?: unknown): Promise headers, body: body === undefined ? undefined : JSON.stringify(body), }) - const env = (await res.json()) as ApiEnvelope + const text = await res.text() + let env: ApiEnvelope + try { + env = JSON.parse(text) as ApiEnvelope + } catch { + throw new Error( + res.status === 404 + ? `接口不存在或后端未更新(${path})` + : `响应不是 JSON(HTTP ${res.status})`, + ) + } if (!res.ok || env.code !== 0) { throw new Error(env.message || `HTTP ${res.status}`) } @@ -130,6 +140,18 @@ export const adminApi = { created_at: string }> }>('GET', `/users/${id}/status-transitions`), + userInsight: (id: string) => + request<{ + user_id: string + profiles_count: number + reports_by_type: Array<{ type: string; count: number }> + recent_reports: Array<{ id: string; type: string; created_at: string }> + tags: Array<{ code: string; label: string }> + behavior: { + events: Array<{ name: string; page_path?: string; received_at: string }> + ask_thread_count: number + } + }>('GET', `/users/${id}/insight`), grant: (id: string, plan: string) => request<{ ok: boolean }>('POST', `/users/${id}/membership/grant`, { plan }), grantAskQuota: (id: string, delta: number) => diff --git a/apps/admin-h5/src/pages/UserDetailPage.vue b/apps/admin-h5/src/pages/UserDetailPage.vue index 28bf653..1501200 100644 --- a/apps/admin-h5/src/pages/UserDetailPage.vue +++ b/apps/admin-h5/src/pages/UserDetailPage.vue @@ -1,14 +1,20 @@