Skip to content

feat: 高级检索式双向转换与登录用户收藏(#196) - #197

Open
youngfish42 wants to merge 7 commits into
mainfrom
feat/advanced-query-favorites
Open

youngfish42 wants to merge 7 commits into
mainfrom
feat/advanced-query-favorites

Conversation

@youngfish42

Copy link
Copy Markdown
Owner

关联 Issue

Closes #196

功能 1:高级检索式「UI 行 ⇄ 纯文本」双向转换 + 复制

  • queryDsl.ts 新增 parseDslToRows():文本 DSL 解析回构建器行。单层 AND/OR/NOT 链、引号短语、SO= 列表、PY= 区间均可无损往返;嵌套分组 / NEAR / 前导 NOT 等无法行化的结构降级为单条原文行并提示(重编译后语义完全等价)。
  • 高级搜索页预览区新增复制按钮(Clipboard API + textarea 兜底);新增**「从文本导入检索式」**输入区,粘贴文本一键解析回构建器(年份行自动回填年份范围框)。
  • 查询接力打通:主页「高级搜索」入口与导航栏跳转携带当前搜索框内容(/#/advanced?q=...),高级搜索页自动解析填入,消费后清除 URL 参数。

功能 2:登录用户收藏检索式(服务端持久化)

  • 后端新增 /api/v1/saved_queries(GET/POST/PATCH/DELETE):按 OAuth provider:id(或 admin 会话)归属用户,统一错误信封(401/403/404/400),每用户配额 100 条(PAPERVAULT_SAVED_QUERIES_MAX_PER_USER)。
  • 存储为独立 users.sqlite3(PAPERVAULT_USER_DB_PATH 可配置),不触碰只读且会原子重建的 papers.sqlite3;配额检查与插入在同一写锁内完成。
  • 前端高级搜索页新增「收藏」(命名保存,保存时自动记录当前结果数)与「我的收藏」抽屉:载入构建器 / 复制 DSL / 手动刷新结果数 / 重命名 / 删除。
  • 新增共享登录态 composables/useAuth.ts;中英双语 i18n 已补齐;AGENTS.md 已同步。

测试

  • 后端:tests/test_saved_queries.py 9 项(401 / CRUD / 用户隔离 / 配额 / 校验边界 / last_count 置空);全量 pytest -q 仅余 2 个与本 PR 无关的既有 Windows 平台失败(已在干净 main 上复现确认)。
  • 前端:node:test DSL 回归套件 33/33(新增 12 个往返/降级用例);type-check / lint:check 零告警;生产构建通过。

未覆盖

OAuth 登录的浏览器端到端冒烟依赖真实 GitHub/知乎凭据,未在本机走通;该路径由后端 session 注入测试与前端构建覆盖。

Issue #196 part 2: logged-in users (OAuth provider:id or admin session)
can persist named advanced-search DSL strings with a last-seen result
count. Storage lives in a dedicated users.sqlite3 (WAL, lock-guarded
writes, quota check inside the write lock) so the read-only, atomically
rebuilt papers.sqlite3 is never touched.

- UserStore service, lazily mounted on app.extensions["user_store"]
- GET/POST /api/v1/saved_queries, PATCH/DELETE /api/v1/saved_queries/<id>
  behind require_user; unified ApiError envelope (401/403/404/400)
- SavedQueryCreateIn/UpdateIn/Out Pydantic schemas; explicit null
  last_count clears the stored count, null name/dsl rejected as no-op
- Settings: PAPERVAULT_USER_DB_PATH, PAPERVAULT_SAVED_QUERIES_MAX_PER_USER
- tests/test_saved_queries.py: auth guard, CRUD, per-user isolation and
  quota, validation boundaries, last_count set/clear semantics
Issue #196 part 1 + favorites frontend:

- queryDsl.ts: new parseDslToRows() — parses a text DSL back into builder
  rows (flat AND/OR/NOT chains of term/list/range leaves round-trip
  faithfully; nested groups / NEAR / leading NOT degrade to a single
  raw-text topic row with supported=false, semantics preserved).
  renderRow now also parens-wraps values with a leading/inner minus NOT.
- AdvancedSearchView: copy button on the generated expression, a
  paste-and-parse import area, ?q= hand-off consumption (watched, cleared
  after apply), and a favorites drawer (save with initial result count,
  load into builder, copy, manual count refresh, rename, delete). Only
  the first AND-joined year row folds into the year-range inputs so NOT/
  OR year clauses keep their semantics.
- useAuth composable: shared module-level login state; MainNavBar now
  consumes it instead of fetching /auth/me itself.
- savedQueries API wrapper + clipboard helper (async Clipboard API with
  textarea fallback); zh/en i18n for all new labels.
- queryDsl.test.mjs: 12 new round-trip / degrade cases (33 total).
@monkeyscan

monkeyscan Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Title: feat: 高级检索式双向转换与登录用户收藏(#196)

Commit: ea61378

整体评估

本次变更为高级检索引入了「文本 DSL ⇄ 可视化构建器」互转(复制/导入)与「收藏检索式」功能,并配套新增后端 UserStore、收藏 API、schema 与配置项,前端新增共享登录态 composable、剪贴板工具与收藏抽屉,同时把首页与导航栏的登录态统一到 useAuth。整体方向清晰,后端按用户隔离存储、配额检查、前端解析回退等处理都比较细致,测试也覆盖了 CRUD、隔离、配额与 DSL 往返的主要路径。

总结

总体实现质量较好,但收藏功能的「结果数」计算存在实质性缺陷:前端把 DSL 原文直接当作后端 q 参数,而后端只做子串匹配,导致每条收藏的命中数恒为 0,建议合并前修复;此外首页跳转高级搜索会顺带触发一次多余的首页搜索。

Findings (2)

  • 🟠 [HIGH] web-vue/src/views/AdvancedSearchView.vue:196 — 收藏结果数把 DSL 原文当作后端 q 参数,导致命中数恒为 0
  • 🟡 [MEDIUM] web-vue/src/views/HomeView.vue:100 — 首页跳转高级搜索会触发首页重复搜索与全屏 loading
Prompt To Fix All With AI
# Review 1

This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/views/AdvancedSearchView.vue
Line: 193-196

Comment:
收藏结果数把 DSL 原文当作后端 q 参数,导致命中数恒为 0

fetchResultCount 把 composedDsl 原文直接作为 q 传给 GET /api/v1/papers,但后端 PaperRepository.search 只是把 q 按空格切词、再对标题/摘要做全部子串 AND 匹配(services/papers.py 的 _substring_all_sql),并不解析 WoS DSL 语法。于是像 TS=federated AND PY=2024-2026 这样的表达式会被要求把 ts=federated、and、py=2024-2026 等字面片段同时作为子串命中,几乎必然返回 total=0,结果每条收藏保存或「刷新结果数」后都是 0 篇,该功能展示的核心数字完全失真。

# Review 2

This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/views/HomeView.vue
Line: 99-100

Comment:
首页跳转高级搜索会触发首页重复搜索与全屏 loading

HomeView 的 goAdvanced(以及 MainNavBar 中同样的改动)现在会把当前表达式作为 ?q= 推入 /advanced,而 HomeView 已有的 watch(() => route.query.q) 会在离开首页、组件卸载之前先触发 consumeQueryParam,进而调用 useHomeSearch.search 再发起一次 /v1/papers 检索。结果是用户点「高级搜索」时首页会多发一次无意义的搜索请求,且该搜索会弹出全屏 ElLoading 遮罩(lock: true),在请求返回前一直盖在已切换过去的高级搜索页面上。
---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread web-vue/src/views/AdvancedSearchView.vue Outdated
Comment thread web-vue/src/views/HomeView.vue
…te watchers

- HIGH: saved-query result counts no longer forward raw DSL as the
  backend `q` param (substring AND over literal tokens like `ts=a`
  always returned 0). New `dslToCoarseParams` in queryDsl.ts mirrors
  useHomeSearch's split: hoists q/author/conf/since/until via
  splitForBackend; when nothing is hoistable it collects the actual
  text-term leaves from the AST (skipping NOT subtrees and non-text
  fields) as a coarse q instead of scanning the whole corpus.
- MEDIUM: navigating home -> /advanced?q=... fired HomeView's
  route.query.q watcher on the way out, launching a redundant search
  with the fullscreen loading mask. Both watchers (HomeView and
  AdvancedSearchView) now guard on route.path.
- docs: AGENTS.md no longer claims the backend parses the DSL in `q`;
  documents splitForBackend / dslToCoarseParams as the client-side split.
- tests: 7 new dslToCoarseParams cases (40 total in the DSL suite).
@monkeyscan

monkeyscan Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Title: feat: 高级检索式双向转换与登录用户收藏(#196)

Commit: d569d7f

整体评估

本轮为针对上一轮评审的修复增量(提交 d569d7f):在 queryDsl.ts 新增 dslToCoarseParams,让收藏结果数改用粗粒度后端参数而非原始 DSL;并在 AdvancedSearchView / HomeView 的路由 watcher 中加入路径守卫,避免页面切换时误消费 outgoing query 与多余搜索。改动方向与上一轮结论一致,两条原发现基本得到处理,结构清晰。

总结

修复总体有效:原始 DSL 不再被当作 q 直接下发(原恒为 0 的严重问题已消除),离开页面时的重复搜索与全屏 loading 也被拦住,路由路径校验与实际路由一致。剩余风险集中在结果数近似本身:对顶层 OR 表达式,dslToCoarseParams 把各文本词用 AND 拼成 q,得到的是命中数的下界而非注释所称上界,仍可能显示 0 或明显偏小,建议合并前一并修正或调整口径。

Findings (1)

  • 🟡 [MEDIUM] web-vue/src/utils/queryDsl.ts:1010 — 顶层 OR 表达式的结果数被 AND 拼接,收藏命中数会被低估甚至显示 0
Prompt To Fix All With AI
This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/utils/queryDsl.ts
Line: 1010

Comment:
顶层 OR 表达式的结果数被 AND 拼接,收藏命中数会被低估甚至显示 0

dslToCoarseParams 在无可 hoist 子句(顶层 OR/NOT/NEAR)时,把 AST 里所有文本词用空格拼成 q 下发给后端,例如 TS=federated OR TS="transfer learning" 得到 q="federated transfer learning";而后端把 q 当作 AND 子串过滤,等于要求同一篇论文同时包含全部关键词。对 OR 语义来说这是命中数的下界而非注释与 AdvancedSearchView.fetchResultCount 文档所称的“上界”,像关键词 OR 合并这类本仓库最常见的查询会得到明显偏小甚至为 0 的结果数(旧问题是恒为 0,本次修复并未完全消除该误导)。
---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@youngfish42

Copy link
Copy Markdown
Owner Author

感谢评审,两条意见均已确认属实并在 d569d7f 修复:

🟠 HIGH — 收藏结果数恒为 0
确认有效:后端 /api/v1/papers 并不解析 DSL,q 只做子串 AND 匹配,原始 DSL 文本会被拆成 ts=federated、and、py=2024-2026 等字面 token 而恒零命中。
修复:新增 queryDsl.ts: dslToCoarseParams(),与主页搜索同一套拆分逻辑——splitForBackend 提升 q/author/conf/since/until;无法提升时(顶层 OR/NOT/NEAR)从 AST 采集真正的文本词叶作为粗粒度 q(跳过 NOT 子树和 author/conf/year 等非文本字段,避免把不会出现在标题/摘要里的值 AND 进去)。收藏的保存与刷新均改走该参数拆分。附 7 个新回归用例(含"NOT 排除词不得进入 q""字段标签不得残留在 q"等)。
说明:与主搜索 UI 一致,含 OR/NOT/NEAR 的检索式计数为后端粗过滤后的上界(精确过滤在客户端进行),与搜索结果页显示的 total 口径相同。

🟡 MEDIUM — 首页跳转高级搜索触发多余搜索与全屏 loading
确认有效。修复:HomeView 的 route.query.q watcher 增加 route.path !== '/' 守卫;同时为对称场景(高级搜索页 runSearch 跳回 /?q=... 时,本页 watcher 会在卸载前消费掉 outgoing query 并用 router.replace 把它从 URL 抹掉)在 AdvancedSearchView 侧加了 route.path !== '/advanced' 守卫,避免修复引入回归。

顺带修正了 AGENTS.md 中"后端在 papers.py 内解析 DSL"的过时描述。

验证:DSL 套件 40/40 通过,type-check / lint:check 零告警,生产构建通过。

Comment thread web-vue/src/utils/queryDsl.ts
Second-round review on PR #197: squeezing a top-level OR into one coarse
q ANDs every text term, yielding a lower bound that collapses to 0 for
the synonym-merge queries this app produces most (the doc comment's
'upper bound' claim was wrong).

- queryDsl.ts: new dslToCountPlan — a top-level OR becomes one coarse
  param set per leg (field-qualified legs hoist their own author/conf/
  year params; legs that narrow nothing are dropped). fetchResultCount
  counts legs concurrently and combines with Math.max: never 0 while any
  leg hits, never inflated by overlap the way a sum would be.
- favorites UI count is now labelled as an approximation (约 {n} 篇 /
  ~{n} papers); refreshed messages unchanged.
- tests: 5 new dslToCountPlan cases (45 total in the DSL suite);
  AGENTS.md convention updated accordingly.
@monkeyscan

monkeyscan Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Title: feat: 高级检索式双向转换与登录用户收藏(#196)

Commit: 7ddf409

整体评估

本轮增量(提交 7ddf409)承接上一轮结论,把收藏结果数的近似从「整表达式压成一组参数」改为 queryDsl.ts 新增的 dslToCountPlan:顶层 OR 拆成每条 leg 一组参数,AdvancedSearchView 逐腿计数后用 Math.max 合并,UI 文案改为「约/~」,并同步了 AGENTS.md 与单测。方向正确,上一轮的核心问题得到针对性处理,测试也覆盖了文本 OR、字段限定 OR 与空 leg 丢弃等主路径。

总结

上一轮指出的「顶层 OR 被 AND 成单一 q、命中数恒为 0」已被修复,OR 查询不会再塌缩为 0。剩余风险集中在计数口径:仅剩非文本 leg 的顶层 OR 会退化成空参数并把全库总量当作结果数,以及各 leg 用 Math.max 合并在关键词集合不重叠时明显低估,建议一并收敛回退逻辑与合并口径。

Findings (2)

  • 🟡 [MEDIUM] web-vue/src/utils/queryDsl.ts:1056 — 仅剩非文本 leg 的顶层 OR 会退化为空参数,收藏结果数变成全库总量
  • 🟡 [MEDIUM] web-vue/src/views/AdvancedSearchView.vue:228 — 顶层 OR 用 Math.max 合并各 leg 计数,关键词集合不重叠时明显低估结果数
Prompt To Fix All With AI
# Review 1

This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/utils/queryDsl.ts
Line: 1056

Comment:
仅剩非文本 leg 的顶层 OR 会退化为空参数,收藏结果数变成全库总量

dslToCountPlan 在顶层 OR 可用 leg 少于 2 条时回退为 coarseParamsFromAst(整个表达式),而 splitForBackend 不会展开顶层 OR、collectTextTerms 又跳过 author/conf/year 叶子,因此 AU="Yang Liu" OR NOT survey、PY=2024 OR NOT x 这类表达式会返回 { kind:'single', params:{} }。fetchResultCount 随即用空参数调用 searchPapers({size:1}),拿到的是完全没有过滤的全库 meta.total,于是收藏/刷新显示并写入的「约 N 篇」会把整个语料库的篇数当成该检索式的结果数。

# Review 2

This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/views/AdvancedSearchView.vue
Line: 228

Comment:
顶层 OR 用 Math.max 合并各 leg 计数,关键词集合不重叠时明显低估结果数

fetchResultCount 对顶层 OR 的各 leg 单独计数后取 Math.max,而 max 只是并集的下界:当各 leg 命中的论文几乎不重叠时,真实并集接近各 leg 之和,而 max 只约等于最大的那一条 leg;新增测试采用的 AU="Yang Liu" OR TS=llm OR SO=ICLR 正是这种不同字段、几乎不重叠的情形。上方注释声称 max 是“close estimate”对这些查询并不成立,收藏/刷新写入并展示的「约 N 篇」可能只有真实篇数的几分之一。
---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@youngfish42

Copy link
Copy Markdown
Owner Author

第二轮评审意见已确认并在 7ddf409 修复:

🟡 MEDIUM — 顶层 OR 计数被 AND 拼接(下界而非上界,可归零)
确认有效。后端没有文本 OR 能力,单一数字必然近似,关键在于近似方式的选择。修复方案:

  • queryDsl.ts 新增 dslToCountPlan():顶层 OR 拆成逐腿独立的粗粒度参数集(字段限定的腿如 AU="Yang Liu" OR TS=llm OR SO=ICLR 各自提升 author/q/conf 参数;无法收窄的腿如裸 NOT 会被丢弃)。
  • fetchResultCount 对 OR 计划并发计数后以 Math.max 合并:不会像 AND 拼接那样在同义词合并查询上归零,也不会像求和那样被腿间重叠放大——对本仓库最常见的 AI 同义词 OR 合并场景(腿间高度重叠),max 是最贴近并集的估计。
  • 口径调整:收藏计数标签改为近似展示(「约 {n} 篇」/「~{n} papers」),相关注释中错误的"上界"表述已更正;AGENTS.md 约定同步(计数必须走 dslToCountPlan,禁止把顶层 OR AND 成单一 q)。
  • 附 5 个新回归用例(OR 逐腿拆分、混合字段腿、不可用腿丢弃回退等),DSL 套件 45/45 通过;type-check / lint:check 零告警,生产构建通过。

Comment thread web-vue/src/utils/queryDsl.ts
Comment thread web-vue/src/views/AdvancedSearchView.vue Outdated
Third-round review on PR #197:

- dslToCountPlan fell back to coarse params of the whole OR expression
  when fewer than two usable legs survived, yielding an empty param set
  for e.g. 'AU="Yang Liu" OR NOT survey' — the caller would then fire an
  unfiltered query and record the whole-corpus total as the saved count.
  A single surviving leg now becomes the single param set directly, and
  fetchResultCount maps an empty param set to null (count unknown)
  instead of an unfiltered request.
- Math.max merging underestimated disjoint OR legs (different fields
  barely overlap). Legs now combine by shape: homogeneous bare text-term
  legs (synonyms, heavy overlap) use max; heterogeneous legs sum.
  Comments and AGENTS.md no longer claim max is universally close.
- tests: 7 new/updated dslToCountPlan cases (47 total in the DSL suite).
@monkeyscan

monkeyscan Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Title: feat: 高级检索式双向转换与登录用户收藏(#196)

Commit: 6231e12

整体评估

本轮增量(commit 6231e12)承接上一轮关于收藏检索式近似结果数的结论,把顶层 OR 的计数计划改为按形状合并:全文本 leg 用 Math.max,异字段 leg 求和,并让无可收窄参数的表达式返回「未知」而非全库总量;单 leg 情形直接复用该 leg 的参数。上一轮指出的两处问题得到针对性处理,改动与文档、单测保持一致,整体方向正确。

总结

上一轮两个问题已修复:仅剩单个可用 leg 的顶层 OR 不再退化为空参数(AU="Yang Liu" OR NOT survey、PY=2024 OR NOT x 现在返回真实参数),且 fetchResultCount 对空参数返回 null,不再把全库总量当作结果数;合并口径也从单一 Math.max 改为按形状合并。剩余风险集中在新的合并判据:textOnly 是「全部 leg 是否为纯文本」的布尔量,只要 OR 中存在一条非文本 leg(作者/年份)就会整体切换为求和,于是同一计划内高度重叠的文本同义 leg(如 AI 建议叠加在已有 AND 查询上产生的表达式)也被相加,结果数被放大数倍。建议把合并判据改为按 leg 自身的形状分别处理,而不是由整条计划的一个布尔量决定。

Findings (1)

  • 🟡 [MEDIUM] web-vue/src/views/AdvancedSearchView.vue:232 — 混合字段的顶层 OR 会把高度重叠的文本同义 leg 也相加,结果数被放大
Prompt To Fix All With AI
This is a comment left during a code review.
Repository: https://github.com/youngfish42/PaperVault
Path: web-vue/src/views/AdvancedSearchView.vue
Line: 232

Comment:
混合字段的顶层 OR 会把高度重叠的文本同义 leg 也相加,结果数被放大

fetchResultCount 用 plan.textOnly 这一个布尔量决定合并方式,而 textOnly 只有在「全部 leg 都是纯文本」时才为 true;只要 OR 里混入一条非文本 leg(作者/年份),整条计划就切换为求和。像 `(PY=2024 AND TS=llm) OR k1 OR k2`(AI 建议叠加在已有查询上,即 buildOrMerge 的常见产物)这样的表达式,k1/k2 本是高度重叠的同义词,却被与 AND leg 一起直接相加,保存/刷新的「约 N 篇」会被放大数倍,而设计本意只对彼此几乎不重叠的异字段 leg 求和。
---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@youngfish42

Copy link
Copy Markdown
Owner Author

第三轮评审的两条意见均已确认并在 6231e12 修复:

🟡 仅剩非文本 leg 的顶层 OR 退化为空参数(计数变全库总量)
确认有效。修复按建议双管齐下:

  • dslToCountPlan 在恰好剩 1 条可用腿时直接复用该腿的参数(AU="Yang Liu" OR NOT survey → {author:'Yang Liu'};PY=2024 OR NOT x → {since:2024,until:2024}),不再从整个 OR 表达式重新推导(那条路径必然得到空参数集);
  • fetchResultCount 对空参数集返回 null(计数未知,UI 显示「结果数未知」),彻底杜绝无过滤全库计数(如 NOT a OR NOT b)。

🟡 Math.max 对不重叠的异构 OR 腿低估
确认有效——max 只是并集下界,AU=… OR TS=… OR SO=… 这类跨字段腿几乎不重叠。修复为按腿形态分治:

  • 同构纯文本腿(每条腿仅含 q,典型为 AI 同义词合并,结果集高度重叠)→ 取 max(求和会被重叠放大数倍);
  • 异构腿(跨 author/venue/year/text 字段,重叠可忽略)→ 取 sum(max 只反映最大的一条腿)。
  • 注释与 AGENTS.md 中"max 是紧估计"的过度表述已更正;UI 计数标签保持「约 {n} 篇」的近似口径。

新增/更新 7 个 dslToCountPlan 用例(单腿复用、空参数、textOnly 标志等),DSL 套件 47/47 通过;type-check / lint:check 零告警,生产构建通过。

补充说明:OR 计数在"后端无文本 OR"的约束下必然是近似值,当前的形态分治在同义词合并(本仓库最常见)与跨字段混合两类典型场景下分别贴近真实并集;该数字的用途是让用户感知收藏的检索式是否有新增文献,相对变化比绝对值更有意义。

Comment thread web-vue/src/views/AdvancedSearchView.vue Outdated
Fourth-round review on PR #197: the plan-wide textOnly switch summed the
heavily-overlapping synonym legs of a mixed plan like
'(PY=2024 AND TS=llm) OR k1 OR k2', inflating the saved count
several-fold. The merge decision now happens per leg: new pure
combineLegCounts() groups legs carrying a text q (synonyms, heavy
overlap -> max) apart from legs on other fields (author/venue/year,
disjoint -> sum), and skips failed legs (null only when every leg
failed, while a genuine zero count still counts).

tests: 4 new combineLegCounts cases (51 total in the DSL suite).
@monkeyscan

monkeyscan Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Title: feat: 高级检索式双向转换与登录用户收藏(#196)

Commit: 317e687

整体评估

本轮增量(commit 317e687)承接上一轮关于收藏检索式近似结果数的结论,把顶层 OR 的合并判据从"整条计划的 textOnly 布尔开关"改为交由新增纯函数 combineLegCounts 按 leg 自身形状分组:携带文本 q 的 leg 用 Math.max 合并、其余字段 leg 求和,失败 leg 跳过、全部失败才返回 null。改动同步更新了 queryDsl.ts 与 AdvancedSearchView 的注释、清理了 CountPlan 上的 textOnly 字段、并补充了针对混合计划与失败 leg 的回归单测,范围收敛、方向正确。

总结

上一轮指出的"混合字段顶层 OR 会把高度重叠的文本同义 leg 一并相加、结果数被放大"已针对性修复并有单测锁定:(PY=2024 AND TS=llm) OR k1 OR k2 现在对文本组取 max、仅对其余字段求和。本轮的实现、调用方与测试三者一致,未发现新的阻塞性缺陷;剩余风险仅是结果数本身仍为近似值(已在 UI 以"约/~"标注),属既有取舍。

@youngfish42

Copy link
Copy Markdown
Owner Author

第四轮评审意见已确认并在 317e687 修复:

🟡 混合字段顶层 OR 把同义文本腿也相加(计数被放大)
确认有效:textOnly 计划级布尔量粒度过粗,(PY=2024 AND TS=llm) OR k1 OR k2 中高度重叠的同义腿 k1/k2 会被求和放大。

修复(按建议把合并决策下沉到腿级别):

  • queryDsl.ts 新增纯函数 combineLegCounts(legs, counts):按腿分组合并——携带文本 q 的腿(同义词,结果集高度重叠,含被年份/会议约束的文本腿)组内取 Math.max;不含 q 的腿(纯 author/venue/year,跨字段几乎不重叠)逐条求和;最终 文本组max + 其他组sum。混合计划既不会归零同义词也不会放大它们。
  • 顺带修正了合并函数对失败腿的处理:计数失败的腿跳过,全部失败才返回 null;真实 0 命中仍按 0 计(之前的 filter+length 写法不区分这两类)。
  • CountPlan 上的 textOnly 布尔量随之移除(决策不再需要计划级开关)。

验证:新增 4 个 combineLegCounts 用例(纯文本组取 max、异字段求和、混合分组、失败腿跳过/全失败 null/真实 0),DSL 套件 51/51 通过;type-check / lint:check 零告警,生产构建通过。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【功能改进】高级搜索功能加强及增加收藏功能

1 participant