diff --git a/apps/api/src/repositories/knowledge_repository.py b/apps/api/src/repositories/knowledge_repository.py index 9a65ee033..b9c0ce847 100644 --- a/apps/api/src/repositories/knowledge_repository.py +++ b/apps/api/src/repositories/knowledge_repository.py @@ -27,6 +27,7 @@ from src.models.knowledge import ( KnowledgeEntry, KnowledgeEntryCreate, ) +from src.utils.timezone import now_taipei logger = structlog.get_logger(__name__) @@ -199,6 +200,13 @@ def _normalize_tags(value: object) -> list[str]: return [str(value).strip()] if str(value).strip() else [] +def _normalize_int(value: object, *, fallback: int = 0) -> int: + try: + return int(value or fallback) + except (TypeError, ValueError): + return fallback + + def _normalized_category_expr(): return func.coalesce( func.nullif(func.trim(KnowledgeEntryRecord.category), ""), @@ -612,10 +620,10 @@ class KnowledgeDBRepository: # P1-1 M3 2026-04-28 ogt + Claude Sonnet 4.6: 冪等 key path_type=get("path_type"), symptoms_hash=get("symptoms_hash"), - view_count=int(get("view_count") or 0), + view_count=_normalize_int(get("view_count")), created_by=get("created_by"), - created_at=get("created_at"), - updated_at=get("updated_at"), + created_at=get("created_at") or now_taipei(), + updated_at=get("updated_at") or get("created_at") or now_taipei(), ) diff --git a/apps/api/tests/test_knowledge_repository_read_model.py b/apps/api/tests/test_knowledge_repository_read_model.py index 6d04486c0..a15e5c7fc 100644 --- a/apps/api/tests/test_knowledge_repository_read_model.py +++ b/apps/api/tests/test_knowledge_repository_read_model.py @@ -86,10 +86,21 @@ def test_knowledge_read_model_normalizes_legacy_tag_shapes_and_null_counts() -> json_tag_entry = repo._to_model(_record(tags='["telegram", " ai_loop "]', view_count=None)) scalar_tag_entry = repo._to_model(_record(tags="telegram")) + bad_count_entry = repo._to_model(_record(view_count="not-a-number")) assert json_tag_entry.tags == ["telegram", "ai_loop"] assert json_tag_entry.view_count == 0 assert scalar_tag_entry.tags == ["telegram"] + assert bad_count_entry.view_count == 0 + + +def test_knowledge_read_model_fills_missing_legacy_timestamps() -> None: + repo = KnowledgeDBRepository(db=object()) # type: ignore[arg-type] + + entry = repo._to_model(_record(created_at=None, updated_at=None)) + + assert entry.created_at is not None + assert entry.updated_at is not None def test_knowledge_read_model_defaults_null_or_blank_category() -> None: