Skip to content

fix: Storyline 自动提取后无法匹配已有故事线#192

Open
liaoyl830 wants to merge 1 commit into
shenminglinyi:masterfrom
liaoyl830:fix/issue-187
Open

fix: Storyline 自动提取后无法匹配已有故事线#192
liaoyl830 wants to merge 1 commit into
shenminglinyi:masterfrom
liaoyl830:fix/issue-187

Conversation

@liaoyl830

@liaoyl830 liaoyl830 commented Jun 8, 2026

Copy link
Copy Markdown

Summary

  • Add fuzzy name matching in _resolve_storyline_by_name() method
  • Implement exact, normalized, and substring match strategies
  • Auto-create missing storylines when not found by LLM extraction
  • Add comprehensive test coverage for storyline resolution

Problem

Issue #187: LLM 提取的故事线名称与存储的名称不一致,导致 StateUpdater 无法找到对应故事线,造成故事线状态丢失和主线推进记录异常。

Solution

  1. 模糊名称匹配:在 state_updater.py 中新增 _resolve_storyline_by_name() 方法,支持:

    • 精确匹配
    • 归一化匹配(忽略大小写和空格)
    • 子串包含匹配(唯一匹配时返回)
    • 歧义匹配时返回 None 并记录警告
  2. 自动创建缺失故事线:当模糊匹配仍无法找到时,自动创建新故事线并保存,避免故事线状态丢失。

  3. 测试覆盖:新增 6 个测试用例覆盖各种匹配场景。

Test plan

  • Exact match test
  • Normalized match test
  • Substring unique match test
  • Ambiguous match returns None test
  • No match returns None test
  • Empty storyline list test
  • All 8 related tests pass

Closes #187

Summary by CodeRabbit

  • Improvements

    • Storyline resolution now uses a two-stage approach: attempts ID-based lookup first, then falls back to name-based matching
    • Automatically creates new storylines when they don't exist but a storyline name is provided
    • Enhanced name matching to handle exact matches, variations, and partial names
  • Tests

    • Added comprehensive unit tests for storyline resolution logic

- Add fuzzy name matching in _resolve_storyline_by_name()
- Implement exact, normalized, and substring match strategies
- Auto-create missing storylines when not found
- Add comprehensive test coverage for storyline resolution

Closes shenminglinyi#187
@liaoyl830 liaoyl830 requested a review from shenminglinyi as a code owner June 8, 2026 14:58
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

StateUpdater's chapter-to-storyline update now uses a fallback resolution strategy: attempt ID-based lookup, then fuzzy name matching (exact, normalized, substring); if no existing storyline is found but a name is provided, auto-create a new active storyline initialized with LLM-provided progress. Comprehensive tests validate all matching modes and edge cases.

Changes

Storyline Resolution and Auto-Creation

Layer / File(s) Summary
Fuzzy storyline name resolution
application/analyst/services/state_updater.py
New _resolve_storyline_by_name method performs prioritized matching: exact name, normalized exact (case/whitespace-insensitive), then substring-based fuzzy matching; returns only when exactly one candidate matches, logs warnings for ambiguous matches, returns None otherwise.
Storyline advancement with fallback and auto-creation
application/analyst/services/state_updater.py
update_from_chapter resolves target storylines via ID first, then uses new name-based fuzzy resolver; when resolution fails but storyline_name is present, auto-creates an active GROWTH-type storyline and initializes its progress/description from LLM-provided values.
Storyline resolution test suite
tests/unit/application/services/test_state_updater_storyline_resolution.py
Validates _resolve_storyline_by_name across exact matches, normalized matches, unique substring matches, and failure cases; provides mocked repository stubs and _make_storyline fixture helper to construct test storylines.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A fuzzy tale of names, so near and far,
When storylines hide from the updating star!
Now fallback paths and creative birth align,
Auto-creating threads with progress so fine,
No more lost plots in the shifting design! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers objectives, problem statement, and solution details, but does not follow the required Chinese template structure (变更类型, 架构影响, 测试 sections with test commands and checkboxes are missing). Restructure the description to follow the repository's Chinese template format: include 变更类型, 架构影响, and 测试 sections with actual test commands and verification checkboxes completed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main issue being fixed: storyline matching after LLM extraction. It accurately reflects the primary change in the changeset.
Linked Issues check ✅ Passed The PR successfully implements fuzzy name matching (#187 requirement), auto-creates missing storylines when unmatched, and adds comprehensive test coverage addressing all coding objectives from the linked issue.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing #187: the state_updater.py modifications implement fuzzy matching and auto-creation, and test file additions cover the new functionality with no extraneous changes.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ 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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
application/analyst/services/state_updater.py (1)

390-390: ⚡ Quick win

Consider using List[Storyline] for consistency.

Line 390 uses list[Storyline] (PEP 585 syntax, Python 3.9+) while the file imports and uses List from typing elsewhere (line 4). For consistency with the rest of the file's type annotations, consider using List[Storyline] instead.

♻️ Proposed fix
-        substring_matches: list[Storyline] = []
+        substring_matches: List[Storyline] = []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/analyst/services/state_updater.py` at line 390, The declaration
of substring_matches uses PEP 585 syntax "list[Storyline]" which is inconsistent
with the rest of the file that imports and uses "List" from typing; change the
type annotation for the variable "substring_matches" to use "List[Storyline]" so
it matches other annotations and the imported typing symbol "List" (ensure
"Storyline" remains the element type and no additional imports are required).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/application/services/test_state_updater_storyline_resolution.py`:
- Line 27: The test helper _make_storyline currently uses a parameter named id
which shadows the Python builtin; rename that parameter to a more specific
identifier (e.g., storyline_id or sl_id) in the _make_storyline function
signature and update all uses within the function and its callers to use the new
name so the builtin is not shadowed (refer to symbol _make_storyline to locate
and change the parameter and its references).

---

Nitpick comments:
In `@application/analyst/services/state_updater.py`:
- Line 390: The declaration of substring_matches uses PEP 585 syntax
"list[Storyline]" which is inconsistent with the rest of the file that imports
and uses "List" from typing; change the type annotation for the variable
"substring_matches" to use "List[Storyline]" so it matches other annotations and
the imported typing symbol "List" (ensure "Storyline" remains the element type
and no additional imports are required).
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c34330b-bb02-42fd-9b50-aa4f15fa7038

📥 Commits

Reviewing files that changed from the base of the PR and between 1c7df5e and 845d98b.

📒 Files selected for processing (2)
  • application/analyst/services/state_updater.py
  • tests/unit/application/services/test_state_updater_storyline_resolution.py

)
self.novel_id = NovelId("novel-test")

def _make_storyline(self, id: str, name: str) -> Storyline:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename parameter to avoid shadowing Python builtin.

The parameter name id shadows the Python builtin function. Consider using a more specific name like storyline_id or sl_id.

🛠️ Proposed fix
-    def _make_storyline(self, id: str, name: str) -> Storyline:
+    def _make_storyline(self, storyline_id: str, name: str) -> Storyline:
         return Storyline(
-            id=id,
+            id=storyline_id,
             novel_id=self.novel_id,
             storyline_type=StorylineType.GROWTH,
             status=StorylineStatus.ACTIVE,
             estimated_chapter_start=1,
             estimated_chapter_end=50,
             name=name,
         )
📝 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
def _make_storyline(self, id: str, name: str) -> Storyline:
def _make_storyline(self, storyline_id: str, name: str) -> Storyline:
return Storyline(
id=storyline_id,
novel_id=self.novel_id,
storyline_type=StorylineType.GROWTH,
status=StorylineStatus.ACTIVE,
estimated_chapter_start=1,
estimated_chapter_end=50,
name=name,
)
🧰 Tools
🪛 Ruff (0.15.15)

[error] 27-27: Function argument id is shadowing a Python builtin

(A002)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/application/services/test_state_updater_storyline_resolution.py`
at line 27, The test helper _make_storyline currently uses a parameter named id
which shadows the Python builtin; rename that parameter to a more specific
identifier (e.g., storyline_id or sl_id) in the _make_storyline function
signature and update all uses within the function and its callers to use the new
name so the builtin is not shadowed (refer to symbol _make_storyline to locate
and change the parameter and its references).

Source: Linters/SAST tools

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.

Storyline 自动提取后无法匹配已有故事线

1 participant