Skip to content

fix: webui表达方式审核状态持久化到数据库#1631

Closed
hsd221 wants to merge 1 commit into
Mai-with-u:devfrom
hsd221:fix/expression-review-db-persistence
Closed

fix: webui表达方式审核状态持久化到数据库#1631
hsd221 wants to merge 1 commit into
Mai-with-u:devfrom
hsd221:fix/expression-review-db-persistence

Conversation

@hsd221
Copy link
Copy Markdown
Contributor

@hsd221 hsd221 commented May 4, 2026

问题

表达方式自动审核(LLM 评估)结果未持久化,WebUI 始终显示"未审核"。

根因

  • expression_to_responsecheckedrejectedmodified_by 硬编码为 False/None
  • 自动审核任务 _evaluate_expression 仅写入内存缓存 (local_storage),未写数据库
  • 筛选未检查表达方式时也读内存缓存而非数据库字段

修改

  • src/webui/routers/expression.py: expression_to_response 直接读取 expression.checkedexpression.rejectedexpression.modified_by 数据库字段
  • src/learners/expression_auto_check_task.py:
    • 评估结果写入数据库字段 (checked, rejected, modified_by)
    • 筛选未检查表达方式改用 expr.checked 数据库字段
    • 移除已不再需要的 expression_review_store 导入

测试

语法检查通过,无新增函数/方法,本地已测试修复。

Summary by CodeRabbit

发行说明

Bug Fixes

  • 修复了表达式评估的筛选与持久化流程:未评估项现在能被正确识别并在评估时持久化为“已检查”或“已拒绝”状态,异常情况下会记录警告并中止写入。
  • 修复了界面显示问题:表达式的“已检查”“已拒绝”状态及最后修改者信息现在准确展示,不再显示固定默认值或空白。

@hsd221 hsd221 changed the title fix(webui): 表达方式审核状态持久化到数据库 fix: webui表达方式审核状态持久化到数据库 May 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Walkthrough

将表达式评估状态的判断与持久化从外部审查状态函数切换为直接使用 ORM 字段:选择未评估项时用 Expression.checked 过滤;评估时重新从数据库加载对应记录并直接写入 checkedrejectedmodified_by;响应映射同步返回这些字段值。

Changes

表达式评估状态管理重构

Layer / File(s) Summary
选择过滤
src/learners/expression_auto_check_task.py
_select_expressions 由基于 get_review_state(expr.id)["checked"] 的判断改为使用 ORM 字段 expr.checked 过滤未评估表达式;选择日志格式合并为单行。
结果持久化
src/learners/expression_auto_check_task.py
_evaluate_expression 不再调用 set_review_state(...),而是在 get_db_session() 中按 id 重新加载对应 Expression 并直接设置 checked=Truerejected=<not suitable>modified_by=ModifiedBy.AI 后写回;错误处理改为在检测到 error 时记录警告并返回。
响应层映射
src/webui/routers/expression.py
expression_to_responseExpressionResponse.checkedExpressionResponse.rejectedmodified_by 从硬编码/默认值改为直接读取 expression.checkedexpression.rejectedexpression.modified_by.value(若存在)。

估算代码审查工作量

🎯 2 (Simple) | ⏱️ ~12 分钟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确反映了PR的核心变更:将表达方式审核状态从内存缓存持久化到数据库。
Description check ✅ Passed PR描述完整覆盖了问题、根因、修改内容和测试情况,符合仓库模板的核心要求。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/learners/expression_auto_check_task.py (1)

84-108: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

不要在 check_expression_suitability() 返回 error 时落库审核结果。

这里会先把 checked/rejected/modified_by 写回数据库,再处理 error。一旦是临时的 LLM/网络异常,这条表达方式也会被永久标记为“已审核”,后续不会再进入待审核队列。src/learners/expression_learner.py:704-716 已经采用了“有 error 就直接返回、不持久化最终状态”的处理方式,这里也应保持一致。

建议修正
         suitable, reason, error = await check_expression_suitability(
             expression.situation,
             expression.style,
         )
+        if error:
+            logger.warning(f"表达方式评估时出现错误 [ID: {expression.id}]: {error}")
+            return False
 
         try:
             with get_db_session() as session:
                 expr = session.exec(select(Expression).where(Expression.id == expression.id)).first()
                 if expr:
                     expr.checked = True
                     expr.rejected = not suitable
                     expr.modified_by = ModifiedBy.AI
                     session.add(expr)
@@
-            if error:
-                logger.warning(f"表达方式评估时出现错误 [ID: {expression.id}]: {error}")
-
             logger.debug(f"表达方式 [ID: {expression.id}] 评估完成: {status}, reason={reason}")
             return suitable

As per coding guidelines, src/**/*.py: 请重点关注以下方面:- 异常处理是否覆盖了边界情况

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/learners/expression_auto_check_task.py` around lines 84 - 108, The DB
write currently persists checked/rejected/modified_by even when
check_expression_suitability returned an error; change the flow in the
expression suitability handling so that if error is truthy you do not write the
audit fields to the DB: call check_expression_suitability, then if error is
present immediately log/warn and return (or skip persistence) instead of
updating Expression via get_db_session(); only when error is None proceed to
open get_db_session(), fetch Expression (select(Expression).where(Expression.id
== expression.id)), set expr.checked, expr.rejected = not suitable,
expr.modified_by = ModifiedBy.AI, session.add(expr), and then log the status.
Ensure the behavior matches the pattern used in expression_learner.py
(early-return on error).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/learners/expression_auto_check_task.py`:
- Around line 84-108: The DB write currently persists
checked/rejected/modified_by even when check_expression_suitability returned an
error; change the flow in the expression suitability handling so that if error
is truthy you do not write the audit fields to the DB: call
check_expression_suitability, then if error is present immediately log/warn and
return (or skip persistence) instead of updating Expression via
get_db_session(); only when error is None proceed to open get_db_session(),
fetch Expression (select(Expression).where(Expression.id == expression.id)), set
expr.checked, expr.rejected = not suitable, expr.modified_by = ModifiedBy.AI,
session.add(expr), and then log the status. Ensure the behavior matches the
pattern used in expression_learner.py (early-return on error).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 425ecbf1-5361-4fdd-8bc5-7bb333691fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 14b7bc7 and 6af4dfc.

📒 Files selected for processing (2)
  • src/learners/expression_auto_check_task.py
  • src/webui/routers/expression.py

@hsd221 hsd221 force-pushed the fix/expression-review-db-persistence branch from 6af4dfc to 80aa2ad Compare May 4, 2026 15:00
check_expression_suitability 返回错误时 early-return,不写数据库,
对齐 expression_learner.py 的错误处理模式。
@hsd221 hsd221 force-pushed the fix/expression-review-db-persistence branch from 80aa2ad to 35caf96 Compare May 4, 2026 15:23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/learners/expression_auto_check_task.py (1)

56-56: ⚡ Quick win

将“未审核”过滤下推到数据库查询层

这里先全量读取再做 expr.checked 过滤,数据量上来后会放大内存和 I/O 开销。建议直接在 SQL 层过滤未审核记录,再做随机抽样。

♻️ 建议修改
-            with get_db_session(auto_commit=False) as session:
-                statement = select(Expression)
-                all_expressions = session.exec(statement).all()
-
-            unevaluated_expressions = [expr for expr in all_expressions if not expr.checked]
+            with get_db_session(auto_commit=False) as session:
+                statement = select(Expression).where(Expression.checked.is_(False))
+                unevaluated_expressions = session.exec(statement).all()

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/learners/expression_auto_check_task.py` at line 56, The comprehension
unevaluated_expressions = [expr for expr in all_expressions if not expr.checked]
pulls all records into memory and then filters; change the data fetch so the
"unchecked" predicate is applied in the DB query (e.g., add WHERE checked =
false / checked IS FALSE) and perform any random sampling/limit in the SQL
(ORDER BY random()/RAND() + LIMIT) instead of sampling after retrieval; update
both occurrences that reference all_expressions -> unevaluated_expressions (the
line creating unevaluated_expressions and the similar occurrence around line 65)
so the query that populates all_expressions (or replace it with a new query)
returns only unchecked items and optionally uses DB-level random sampling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/learners/expression_auto_check_task.py`:
- Around line 91-97: The current read-then-write in get_db_session() fetches
Expression and unconditionally sets checked/rejected, which can overwrite
concurrent human review; modify the update to only affect records still
unchecked by adding a checked.is_(False) condition (e.g., include
Expression.checked.is_(False) in the select/where or perform an UPDATE where
Expression.id == expression.id AND Expression.checked.is_(False)), and only set
modified_by = ModifiedBy.AI and session.add(expr) when the conditional query
returns a row so already-reviewed records are skipped.

---

Nitpick comments:
In `@src/learners/expression_auto_check_task.py`:
- Line 56: The comprehension unevaluated_expressions = [expr for expr in
all_expressions if not expr.checked] pulls all records into memory and then
filters; change the data fetch so the "unchecked" predicate is applied in the DB
query (e.g., add WHERE checked = false / checked IS FALSE) and perform any
random sampling/limit in the SQL (ORDER BY random()/RAND() + LIMIT) instead of
sampling after retrieval; update both occurrences that reference all_expressions
-> unevaluated_expressions (the line creating unevaluated_expressions and the
similar occurrence around line 65) so the query that populates all_expressions
(or replace it with a new query) returns only unchecked items and optionally
uses DB-level random sampling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cdf68766-efcc-4cb6-81d6-1a320782fa9c

📥 Commits

Reviewing files that changed from the base of the PR and between 80aa2ad and 35caf96.

📒 Files selected for processing (2)
  • src/learners/expression_auto_check_task.py
  • src/webui/routers/expression.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/webui/routers/expression.py

Comment on lines +91 to +97
with get_db_session() as session:
expr = session.exec(select(Expression).where(Expression.id == expression.id)).first()
if expr:
expr.checked = True
expr.rejected = not suitable
expr.modified_by = ModifiedBy.AI
session.add(expr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file and understand the context
cat -n src/learners/expression_auto_check_task.py | head -120 | tail -50

Repository: Mai-with-u/MaiBot

Length of output: 2060


🏁 Script executed:

# Look at the Expression model to understand the checked field
fd -t f "\.py$" | xargs grep -l "class Expression" | head -5

Repository: Mai-with-u/MaiBot

Length of output: 241


🏁 Script executed:

# Find get_db_session implementation
rg "def get_db_session" -A 10

Repository: Mai-with-u/MaiBot

Length of output: 3684


🏁 Script executed:

# Check Expression model definition
rg "class Expression" src/common/database/database_model.py -A 30

Repository: Mai-with-u/MaiBot

Length of output: 1150


🏁 Script executed:

# Get full get_db_session implementation
rg "def get_db_session" src/common/database/database.py -A 30

Repository: Mai-with-u/MaiBot

Length of output: 1631


🏁 Script executed:

# Check if there are other places where Expression is updated to see patterns
rg "Expression.*checked\s*=" src/ -B 2 -A 2

Repository: Mai-with-u/MaiBot

Length of output: 43


🏁 Script executed:

# Search for other updates to Expression model
rg "session.add\(expr" src/ -B 5 -A 2

Repository: Mai-with-u/MaiBot

Length of output: 2061


🏁 Script executed:

# Check if there are any locking mechanisms used elsewhere
rg "with_for_update\|lock\|Lock" src/common/database/ -i

Repository: Mai-with-u/MaiBot

Length of output: 43


🏁 Script executed:

# Look for other places that might be updating Expression concurrently
rg "Expression" src/learners/ -B 2 -A 2 | head -100

Repository: Mai-with-u/MaiBot

Length of output: 6817


🏁 Script executed:

# Check the broader context of the _evaluate_expression function
cat -n src/learners/expression_auto_check_task.py | sed -n '50,130p'

Repository: Mai-with-u/MaiBot

Length of output: 3457


缺少并发保护,可能覆盖人工审核结果

这里查询后直接写入会在"查询后到写入前"窗口覆盖其他审核者(例如 WebUI 人工审核)的审核状态。建议在更新时增加 Expression.checked.is_(False) 条件,确保仅更新仍为未审核的记录,已被审核则跳过。

建议修改
             with get_db_session() as session:
-                expr = session.exec(select(Expression).where(Expression.id == expression.id)).first()
-                if expr:
-                    expr.checked = True
-                    expr.rejected = not suitable
-                    expr.modified_by = ModifiedBy.AI
-                    session.add(expr)
+                expr = session.exec(
+                    select(Expression).where(
+                        Expression.id == expression.id,
+                        Expression.checked.is_(False),
+                    )
+                ).first()
+                if not expr:
+                    logger.debug(f"表达方式 [ID: {expression.id}] 已被其他流程审核,跳过写入")
+                    return False
+
+                expr.checked = True
+                expr.rejected = not suitable
+                expr.modified_by = ModifiedBy.AI
+                session.add(expr)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with get_db_session() as session:
expr = session.exec(select(Expression).where(Expression.id == expression.id)).first()
if expr:
expr.checked = True
expr.rejected = not suitable
expr.modified_by = ModifiedBy.AI
session.add(expr)
with get_db_session() as session:
expr = session.exec(
select(Expression).where(
Expression.id == expression.id,
Expression.checked.is_(False),
)
).first()
if not expr:
logger.debug(f"表达方式 [ID: {expression.id}] 已被其他流程审核,跳过写入")
return False
expr.checked = True
expr.rejected = not suitable
expr.modified_by = ModifiedBy.AI
session.add(expr)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/learners/expression_auto_check_task.py` around lines 91 - 97, The current
read-then-write in get_db_session() fetches Expression and unconditionally sets
checked/rejected, which can overwrite concurrent human review; modify the update
to only affect records still unchecked by adding a checked.is_(False) condition
(e.g., include Expression.checked.is_(False) in the select/where or perform an
UPDATE where Expression.id == expression.id AND Expression.checked.is_(False)),
and only set modified_by = ModifiedBy.AI and session.add(expr) when the
conditional query returns a row so already-reviewed records are skipped.

@hsd221 hsd221 closed this May 4, 2026
@github-project-automation github-project-automation Bot moved this to 已完成 in MaiM to the GATE May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant