Просмотр исходного кода

feat(ledger): 被去重的分类来源占位行讲清与哪条搜索词重复 + 加粗放大

- CFA _demand_summary 新增 category_leaf_terms(分类叶子候选搜索词,element_bindings 元素名去重),
  供前端对比首轮 query。
- web2 DeducedSourceRows 改为:逐个分类词比对首轮 query,若与前面某非分类来源的词完全相同(被去重),
  补占位行并写明"「分类叶子元素」来源的搜索词『X』与前面「Y来源」的搜索词『X』完全相同,故此处不显示"。
- 该提示文案加粗放大(1.05rem / 700)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sam Lee 2 недель назад
Родитель
Сommit
ed3b56201e

+ 9 - 0
content_agent/flow_ledger_service.py

@@ -1833,6 +1833,14 @@ def _demand_summary(bundle: dict[str, Any]) -> dict[str, Any]:
             point_type = _text(sample_record.get("point_type"))
             suffix = " / ".join(item for item in [point_type, f"帖子 {post_id}" if post_id else ""] if item)
             extracted_points.append(f"{point_text}({suffix})" if suffix else point_text)
+    # 分类叶子来源的候选搜索词(element_bindings 元素名)。前端用它对比首轮 query,
+    # 讲清"分类来源的某词与前面哪条来源的同名词重复、被去重未单独显示"。
+    category_leaf_terms: list[str] = []
+    for binding in _list(evidence.get("element_bindings")):
+        for sample in _list(_record(binding).get("sample_elements")):
+            name = _text(_record(sample).get("name"))
+            if name:
+                category_leaf_terms.append(name)
     return {
         "id": _demand_id(bundle),
         "name": _text(raw.get("name") or source_context.get("name")),
@@ -1850,6 +1858,7 @@ def _demand_summary(bundle: dict[str, Any]) -> dict[str, Any]:
         "mining_config_id": _text(evidence.get("mining_config_id")),
         "category_paths": category_paths,
         "extracted_points": _dedupe_texts(extracted_points),
+        "category_leaf_terms": _dedupe_texts(category_leaf_terms),
     }
 
 

+ 21 - 23
web2/features/LedgerPage.tsx

@@ -277,38 +277,36 @@ function sourceKindClass(kind: string): string {
   return "unknown";
 }
 
-// 首轮取词三源:需求池直出(seed_term)/ 票圈帖子的点(query_seed_point)/ 分类叶子(category_leaf_element)。
-const FIRST_ROUND_SOURCE_TYPES = ["seed_term", "query_seed_point", "category_leaf_element"];
-
-function sourceTypeHasMaterial(type: string, demand?: DemandSummary): boolean {
-  if (!demand) return false;
-  if (type === "seed_term") return demand.seedTerms.length > 0;
-  if (type === "category_leaf_element") return demand.categoryPaths.length > 0;
-  if (type === "query_seed_point") return demand.extractedPoints.length > 0;
-  return false;
-}
-
-// CFA 取词时同一个词只搜一次,后命中的来源被去重丢弃(不入库),前端看不到。
-// 这里据需求素材补出"被去重的来源"占位行:那行留空 + 标注与前面哪些来源重复。
+// CFA 取词时同一个词只搜一次:分类叶子来源的某个词若与前面某来源的词完全相同,就被去重丢弃、不入库,
+// 前端看不到。这里据 demand 的 category_leaf_terms 比对首轮 query,把"被去重的分类词"补成占位行,
+// 并讲清它和前面哪条来源的哪个搜索词一模一样。
 function DeducedSourceRows({ rows, demand }: { rows: FlowLedgerRow[]; demand?: DemandSummary }) {
-  const present = new Set(rows.map((row) => row.source.sourceKind));
-  const shownLabels = FIRST_ROUND_SOURCE_TYPES.filter((type) => present.has(type)).map((type) => sourceLabel(type));
-  const deduped = FIRST_ROUND_SOURCE_TYPES.filter(
-    (type) => !present.has(type) && sourceTypeHasMaterial(type, demand)
-  );
+  const categoryTerms = demand?.categoryLeafTerms || [];
+  const deduped = categoryTerms
+    .map((term) => {
+      // 该分类词已真生成了分类来源 query → 正常显示,不补占位。
+      const asCategory = rows.find((row) => row.query.text === term && row.source.sourceKind === "category_leaf_element");
+      if (asCategory) return null;
+      // 该词出现在前面某条非分类来源里 → 被去重,补占位并指明与谁重复。
+      const match = rows.find((row) => row.query.text === term && row.source.sourceKind !== "category_leaf_element");
+      return match ? { term, dupLabel: sourceLabel(match.source.sourceKind) } : null;
+    })
+    .filter((item): item is { term: string; dupLabel: string } => item !== null);
   if (!deduped.length) return null;
   return (
     <>
-      {deduped.map((type) => (
-        <tr className="ledger-row deduped-source-row" key={`deduped-${type}`}>
+      {deduped.map(({ term, dupLabel }) => (
+        <tr className="ledger-row deduped-source-row" key={`deduped-${term}`}>
           <td className="source-cell sticky-source">
             <div className="cell-stack">
-              <span className={`source-kind-badge ${sourceKindClass(type)}`}>{sourceLabel(type)}</span>
+              <span className={`source-kind-badge ${sourceKindClass("category_leaf_element")}`}>{sourceLabel("category_leaf_element")}</span>
               <span className="muted">(此处留空)</span>
             </div>
           </td>
-          <td className="muted deduped-source-note" colSpan={4}>
-            这是「{sourceLabel(type)}」来源,搜索词与前面的{shownLabels.join(" / ") || "已展示"}来源重复(同词只搜一次),所以此处不单独显示。
+          <td className="deduped-source-note" colSpan={4}>
+            <strong style={{ fontSize: "1.05rem", fontWeight: 700, lineHeight: 1.6 }}>
+              「{sourceLabel("category_leaf_element")}」来源的搜索词「{term}」,与前面「{dupLabel}」来源的搜索词「{term}」完全相同(同词只搜一次),所以此处不单独显示。
+            </strong>
           </td>
         </tr>
       ))}

+ 2 - 1
web2/lib/flow-ledger/build.ts

@@ -193,7 +193,8 @@ function demandSummaryFromApi(row: RawRecord | null | undefined): DemandSummary
     patternExecutionId: text(row.pattern_execution_id, ""),
     miningConfigId: text(row.mining_config_id, ""),
     categoryPaths: textList(row.category_paths),
-    extractedPoints: textList(row.extracted_points)
+    extractedPoints: textList(row.extracted_points),
+    categoryLeafTerms: textList(row.category_leaf_terms)
   };
 }
 

+ 1 - 0
web2/lib/flow-ledger/types.ts

@@ -149,6 +149,7 @@ export type DemandSummary = {
   miningConfigId: string;
   categoryPaths: string[];
   extractedPoints: string[];
+  categoryLeafTerms: string[];
 };
 
 export type FlowLedgerRow = {