feat(notion-search): add candidate enrichment (title, properties, excerpt)

For each candidate page, extract the title, flatten common property
types (status/select/multi_select/date/checkbox/number/url/etc.) to
display values, and fetch the first text-bearing block as a 200-char
excerpt. Empty excerpt is acceptable when the page has no leading text.

4 tests: title+properties, paragraph excerpt, empty fallback,
truncation. 21 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 07:56:35 +09:00
parent a40d1f06b5
commit 8aa0fa26e9
2 changed files with 193 additions and 0 deletions

View File

@@ -261,6 +261,105 @@ def test_search_passes_property_filter_to_data_sources_query():
"prop_filter passed through as `filter` keyword argument")
def test_enrich_extracts_title_and_properties():
"""Candidate with properties → title and properties extracted."""
from unittest.mock import MagicMock
import notion_search
notion = MagicMock()
notion.blocks.children.list.return_value = {"results": []} # no body
candidate = {
"id": "page-a",
"url": "https://notion.so/a",
"properties": {
"Name": {
"type": "title",
"title": [{"plain_text": "Test Page"}],
},
"Status": {
"type": "status",
"status": {"name": "Done"},
},
"Topic": {
"type": "multi_select",
"multi_select": [{"name": "AI"}, {"name": "MCP"}],
},
},
}
enriched = notion_search.enrich_candidates(notion, [candidate])
_assert(len(enriched) == 1, "one enriched candidate")
e = enriched[0]
_assert(e["title"] == "Test Page", "title extracted from title property")
_assert(e["properties"]["Status"] == "Done", "status flattened to name")
_assert(e["properties"]["Topic"] == ["AI", "MCP"], "multi_select flattened to names list")
def test_enrich_extracts_first_paragraph_excerpt():
"""First paragraph block → excerpt (200-char max)."""
from unittest.mock import MagicMock
import notion_search
notion = MagicMock()
notion.blocks.children.list.return_value = {
"results": [
{
"type": "paragraph",
"paragraph": {
"rich_text": [{"plain_text": "This is the first paragraph of the page."}]
},
}
]
}
candidate = {
"id": "page-a", "url": "https://notion.so/a",
"properties": {"Name": {"type": "title", "title": [{"plain_text": "Test"}]}},
}
enriched = notion_search.enrich_candidates(notion, [candidate])
_assert(enriched[0]["excerpt"] == "This is the first paragraph of the page.",
"excerpt is first paragraph plain text")
def test_enrich_falls_back_to_empty_excerpt():
"""Page leads with image/table only → excerpt is empty string, no crash."""
from unittest.mock import MagicMock
import notion_search
notion = MagicMock()
notion.blocks.children.list.return_value = {
"results": [
{"type": "image", "image": {}},
{"type": "divider", "divider": {}},
]
}
candidate = {
"id": "page-a", "url": "https://notion.so/a",
"properties": {"Name": {"type": "title", "title": [{"plain_text": "Test"}]}},
}
enriched = notion_search.enrich_candidates(notion, [candidate])
_assert(enriched[0]["excerpt"] == "", "empty excerpt when no text-bearing block")
def test_enrich_truncates_long_excerpt():
"""Excerpt is capped at 200 chars."""
from unittest.mock import MagicMock
import notion_search
notion = MagicMock()
long_text = "x" * 500
notion.blocks.children.list.return_value = {
"results": [
{"type": "paragraph", "paragraph": {"rich_text": [{"plain_text": long_text}]}}
]
}
candidate = {
"id": "page-a", "url": "https://notion.so/a",
"properties": {"Name": {"type": "title", "title": [{"plain_text": "Test"}]}},
}
enriched = notion_search.enrich_candidates(notion, [candidate])
_assert(len(enriched[0]["excerpt"]) == 200, "excerpt truncated to 200 chars")
def run_all():
tests = [
test_call_claude_dispatches_to_sdk_when_available,
@@ -280,6 +379,10 @@ def run_all():
test_search_uses_data_source_query_when_databases_specified,
test_search_filters_only_pages,
test_search_passes_property_filter_to_data_sources_query,
test_enrich_extracts_title_and_properties,
test_enrich_extracts_first_paragraph_excerpt,
test_enrich_falls_back_to_empty_excerpt,
test_enrich_truncates_long_excerpt,
]
for t in tests:
print(f"\n{t.__name__}")