star-chart-search-enhancer/docs/superpowers/plans/2026-05-18-market-audience-profile-export.md

9.3 KiB

Market Audience Profile Export Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a selected-creators-only 导出画像CSV flow that exports current market columns plus detail-page "连接用户" audience profile distributions.

Architecture: Keep the existing CSV export untouched and add a separate profile export path. The content controller reuses current selection and market row hydration, loads one selected creator profile at a time through a focused detail-page profile client, then writes a separate CSV with structured audience columns.

Tech Stack: TypeScript, Chrome MV3 content scripts, Xingtu authenticated pages, Vitest, jsdom, tsup


File Map

  • Modify: src/background/auth/controller.ts
    • Keep token-readable auth state behavior from the previous fix.
  • Modify: src/background/auth/state.ts
    • Keep logged-out lastError support from the previous fix.
  • Modify: tests/background-auth-controller.test.ts
    • Keep token-expired regression coverage.
  • Modify: src/content/market/auth-gate.ts
    • Render expired-login text when auth state carries a token-expired error.
  • Modify: src/content/index.ts
    • Pass auth failure text into the market auth gate if needed.
  • Modify: src/content/market/plugin-toolbar.ts
    • Add a 导出画像CSV button and handler.
  • Create: src/content/market/audience-profile-types.ts
    • Define normalized distribution and export-row types.
  • Create: src/content/market/audience-profile-client.ts
    • Load one creator detail page and extract normalized audience profile data.
  • Create: src/content/market/audience-profile-csv.ts
    • Build CSV columns from market records plus profile distributions.
  • Modify: src/content/market/index.ts
    • Add selected-only profile export flow and serial profile loading.
  • Test: tests/market-auth-gating.test.ts
    • Verify expired-login text.
  • Test: tests/plugin-toolbar.test.ts
    • Verify new toolbar button wiring.
  • Test: tests/audience-profile-csv.test.ts
    • Verify structured CSV column expansion.
  • Test: tests/audience-profile-client.test.ts
    • Verify parser behavior against representative detail-page payload/state shapes.
  • Modify: tests/market-content-entry.test.ts
    • Verify selected-only export behavior and failure handling.

Task 1: Expired Login Message

Files:

  • Modify: src/content/market/auth-gate.ts

  • Modify: src/content/index.ts

  • Test: tests/market-auth-gating.test.ts

  • Step 1: Write the failing auth gate test

Add a test where sendAuthMessage returns:

{
  ok: true,
  type: "auth:state",
  value: {
    isAuthenticated: false,
    lastError: "Token 已过期"
  }
}

Assert the page shows 登录已过期,请重新登录.

  • Step 2: Run the failing test

Run:

npm test -- tests/market-auth-gating.test.ts

Expected: FAIL because the gate only renders 请先登录插件.

  • Step 3: Implement minimal auth gate text support

Update renderMarketAuthGate to accept an optional message string and render it instead of the default title. Update bootContentScript to pass 登录已过期,请重新登录 when lastError contains token or 过期.

  • Step 4: Verify

Run:

npm test -- tests/market-auth-gating.test.ts tests/popup-entry.test.ts

Expected: PASS.

Task 2: Toolbar Button

Files:

  • Modify: src/content/market/plugin-toolbar.ts

  • Test: tests/plugin-toolbar.test.ts

  • Step 1: Write failing toolbar tests

Create tests that:

  • render the toolbar

  • assert a button with text 导出画像CSV exists

  • click it and assert onExportAudienceProfile was called

  • assert setToolbarBusyState disables the new button

  • Step 2: Run the failing tests

Run:

npm test -- tests/plugin-toolbar.test.ts

Expected: FAIL because the button and handler do not exist.

  • Step 3: Implement toolbar support

Add onExportAudienceProfile to PluginToolbarHandlers, add audienceProfileExportButton to PluginToolbarDom, render the new button, wire click handling, and include it in busy-state disabling.

  • Step 4: Verify

Run:

npm test -- tests/plugin-toolbar.test.ts tests/market-content-entry.test.ts

Expected: PASS.

Task 3: Profile CSV Builder

Files:

  • Create: src/content/market/audience-profile-types.ts

  • Create: src/content/market/audience-profile-csv.ts

  • Test: tests/audience-profile-csv.test.ts

  • Step 1: Write failing CSV tests

Define a sample market record and sample profile:

const profile = {
  status: "success",
  gender: [
    { label: "男性", value: "40.6%" },
    { label: "女性", value: "59.4%" }
  ],
  age: [{ label: "18-23", value: "28.6%" }],
  province: [{ label: "广东", value: "15%" }],
  regionTop: [{ label: "北京", value: "15%" }],
  cityTier: [{ label: "一线", value: "20%" }],
  interestTop: [{ label: "亲子", value: "18%" }],
  crowd: [{ label: "精致妈妈", value: "12%" }]
};

Assert the CSV contains separate headers like 连接用户-男性占比, 省份-广东占比, 地域TOP1名称, 地域TOP1占比.

  • Step 2: Run the failing tests

Run:

npm test -- tests/audience-profile-csv.test.ts

Expected: FAIL because the files do not exist.

  • Step 3: Implement CSV builder

Create:

  • AudienceProfileDistributionItem
  • AudienceProfileResult
  • buildAudienceProfileCsv(records, profilesByAuthorId)

Reuse escapeCsvCell and existing base/rate/backend metric column conventions. Add 画像抓取状态.

  • Step 4: Verify

Run:

npm test -- tests/audience-profile-csv.test.ts tests/csv-exporter.test.ts

Expected: PASS.

Task 4: Detail Profile Client and Parser

Files:

  • Create: src/content/market/audience-profile-client.ts

  • Test: tests/audience-profile-client.test.ts

  • Step 1: Use a logged-in browser to identify the real data source

Run a Playwright probe against an authenticated https://xingtu.cn/ad/creator/author-homepage/douyin-video/<authorId> page. Capture only /gw/api/... JSON responses and page Vue/ECharts state. Record representative payload/state samples in the test file as small fixtures.

  • Step 2: Write failing parser tests

Use the captured fixture to assert the parser returns normalized arrays for gender, age, province, region top 10, city tier, interest top 10, and crowd.

  • Step 3: Run the failing tests

Run:

npm test -- tests/audience-profile-client.test.ts

Expected: FAIL because the client/parser does not exist.

  • Step 4: Implement parser and client

Implement a small parser first. Then implement the client with injectable dependencies:

createAudienceProfileClient({
  fetchDetailPage?: (authorId: string) => Promise<unknown>;
  readProfileFromPage?: (authorId: string) => Promise<unknown>;
})

Prefer parsed API JSON. Fall back to page state when API JSON is unavailable.

  • Step 5: Verify

Run:

npm test -- tests/audience-profile-client.test.ts

Expected: PASS.

Task 5: Controller Export Flow

Files:

  • Modify: src/content/market/index.ts

  • Test: tests/market-content-entry.test.ts

  • Step 1: Write failing selected-only export tests

Add tests that:

  • select one of two visible rows

  • click 导出画像CSV

  • assert only the selected author profile is requested

  • assert onCsvReady receives a CSV containing profile columns

  • Step 2: Write failing no-selection test

Assert clicking 导出画像CSV with no selected rows sets status to 请先勾选需要导出画像的达人 and makes no profile requests.

  • Step 3: Write failing partial-failure test

Mock two selected profiles where one succeeds and one fails. Assert CSV is still generated with one success row and one 画像抓取状态=失败 row.

  • Step 4: Run failing tests

Run:

npm test -- tests/market-content-entry.test.ts

Expected: FAIL because the controller has no profile export flow.

  • Step 5: Implement controller flow

Add an injected option loadAudienceProfile?: (record: MarketRecord) => Promise<AudienceProfileResult>. Add handler:

  • sync selected state from DOM

  • reject empty selection

  • hydrate current-page selected records

  • load profiles serially

  • cache successful results by author ID

  • build profile CSV

  • call onCsvReady

  • update toolbar progress/status

  • Step 6: Verify

Run:

npm test -- tests/market-content-entry.test.ts tests/audience-profile-csv.test.ts tests/plugin-toolbar.test.ts

Expected: PASS.

Task 6: Full Verification

Files:

  • Modify only if failures identify necessary scoped fixes.

  • Step 1: Run focused suite

Run:

npm test -- tests/market-auth-gating.test.ts tests/plugin-toolbar.test.ts tests/audience-profile-csv.test.ts tests/audience-profile-client.test.ts tests/market-content-entry.test.ts

Expected: PASS.

  • Step 2: Run all tests

Run:

npm test

Expected: PASS.

  • Step 3: Run build

Run:

npm run build

Expected: PASS.

  • Step 4: Manual logged-in browser verification

Load the built extension in Chrome, select one or two market creators, click 导出画像CSV, and verify the downloaded CSV contains structured profile columns with detail-page data.