diff --git a/backend/app/services/article_writer.py b/backend/app/services/article_writer.py index 5b1059d..7a08c17 100644 --- a/backend/app/services/article_writer.py +++ b/backend/app/services/article_writer.py @@ -214,6 +214,10 @@ def write_article(db: Session, topic: str, category_id: int | None = None, article_service.record_slug(db, article) db.commit() db.refresh(article) + # The section index is what makes the body searchable at all. This writer + # produced 323 articles without it, so the library's whole depth was + # invisible to search while every one of them looked fine on the page. + article_service.reindex(db, article) return {"topic": topic, "status": "written", "article_id": article.id, "sections": len(sections), "references": len(article.references_json or []), "passages": len(passages)} diff --git a/backend/scripts/reindex_article_search.py b/backend/scripts/reindex_article_search.py index ccf12c7..ae9dba5 100644 --- a/backend/scripts/reindex_article_search.py +++ b/backend/scripts/reindex_article_search.py @@ -36,6 +36,8 @@ import sys import time from datetime import datetime +from sqlalchemy import text as sa_text + from app.database import SessionLocal from app.models.article import Article, ArticleSectionIndex from app.services import article_service, embedding_service @@ -59,6 +61,22 @@ def _needs_article_vector(article, active_model: str, started: datetime, force: return force and article.embedded_at < started +def _settle(db, articles) -> None: + """Record that the stored vector matches the row as it now stands. + + `Article.updated_at` carries `onupdate`, so writing the vector bumps it a + few milliseconds past the `embedded_at` the same write set. An exact + `embedded_at < updated_at` test therefore reports every article stale + forever, and a re-run pays for the whole corpus again. Raw SQL because a + Core update would fire `onupdate` once more and lose the race a second time. + """ + if not articles: + return + db.execute(sa_text("UPDATE articles SET embedded_at = GREATEST(embedded_at, updated_at) " + "WHERE id = ANY(:ids)"), {"ids": [a.id for a in articles]}) + db.commit() + + def main() -> int: apply_changes = "--apply" in sys.argv force = "--all" in sys.argv @@ -91,6 +109,7 @@ def main() -> int: for article in chunk: section_vectors += article_service.rebuild_section_index(db, article) db.commit() + _settle(db, wanted) except Exception as exc: # One bad article must not cost the run; it is reported and the # next chunk carries on, because a sweep that has to be restarted