Files
brain-of-reese/alembic/versions/0026_documents_page_count.py
T
ducoterra b233c8d714
Build and Push Containers / build-and-push-app (push) Successful in 2m28s
Build and Push Containers / build-and-push-db (push) Successful in 16s
phase: 148_pdf_pages_in_sources
**Phase 148 — final verification pass: complete, 100% green.**

- **Verified (tasks 01–04 already implemented):** page render/persist at index (`_persist_page_copies`, fail-soft, stale-replace, qualifier-loss, prune hook via `_delete_page_copies`); public `GET /api/documents/{id}/pages/{n}` (uuid4-credential, 404-all-non-servable, `image/png` + cache header); `DocContent.page_urls` additive-omitted; shared `renderDocument` strip (lazy imgs, "Page N" captions, text + Summary suppressed, onerror note, createElement-only); migration 0026; `pdf_page_dir` setting; `.env.example` entry
- **Completion criteria:** (1) pictorial + fallback classes get `p{N}.png` + `page_count`, text-heavy gets neither — pinned, pass; (2) unchanged re-sync renders zero (fast path) — pass; (3) changed re-index replaces copies, qualifier-loss/prune delete — pass; (4) route 404 matrix + payload key absent-never-null — pass; (5) modal AND /document.html strip with suppression, text-heavy byte-identical — E2E pass; (6) see below; (7) no completed-phase drift — full suite green
- **Tests:** `uv run pytest --cov=app --cov-report=term-missing` → **3461 passed, 0 failed**, `app/` coverage **99%** (>90%); `uv run pytest tests/e2e/test_pdf_pages_in_sources.py -v --no-cov` → **4 passed** in isolation; `uv run ruff check .` clean; `uv run pyright` → 0 errors, 0 warnings
- **Documentation:** README had **1 pre-existing inconsistency** — the Production Deployment section's standalone `podman run` example (and compose note) omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the fail-loud boot gate the README's own Admin section documents — the container would refuse to start); fixed with the two `-e` flags + a note for the compose path. All else verified consistent: 70/70 `.env.example` vars documented, all defaults match `config.py`, pages table matches routes, Containerfile/esbuild/uid-10001/healthcheck/entrypoint claims accurate, QA commands match pytest config. **AGENTS.md: verified consistent — no changes needed**
- **Notable:** repo uses `Containerfile` (Podman) — README's `podman build` example is valid as-is; no live infrastructure touched
- **Next pending phase:** `149_sync_progress_box`
2026-10-01 16:58:01 -04:00

61 lines
2.5 KiB
Python

"""documents.page_count: the rendered page-strip's page count (phase 148, task 01)
Revision ID: 0026
Revises: 0025
Create Date: 2026-10-01
Phase 148 (pictorial PDFs show their PAGES in the sources — task 01,
storage only):
* ``documents.page_count`` — INTEGER NULLABLE, NO server default, NO
index: the number of rendered page PNGs persisted in
``settings.pdf_page_dir`` (``BOR_PDF_PAGE_DIR``) as
``<doc-id>-p{N}.png`` (N 1-based) for ONE page-qualifying document —
the PDF's page count. Set ONLY by the importer's PDF branch (task 01):
a qualifying PDF (the phase-133 fallback class — unusable text layer,
— or the phase-140 pictorial class — the sparse-page pass described
≥1 page) gets every page rendered at index time, and this column
records the page count (a per-page render failure leaves a GAP — the
file is simply absent, the route of task 02 404s that N, the viewer's
note covers it). NULL for every other document (text documents,
images, text-heavy PDFs) and for a qualifying PDF whose render/write
failed (fail-soft — the render is a display concern, it never gates
indexing); the prune cleanup deletes the copies with the row.
A display fact, never a query surface: the page-bytes route (task 02)
looks the document up by its uuid4 id FIRST (the unguessable id is the
credential — the phase-124 image-route trust model) and reads this
column from the ROW, so it carries no index of its own (the
``file_hash`` convention, 0025).
One additive, fully reversible migration (A13); no other schema change.
The render/persist/cleanup logic itself (task 01) is importer code —
this revision only carries the column.
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "0026"
down_revision = "0025"
branch_labels = None
depends_on = None
def upgrade() -> None:
# No index (a display fact, never a query surface — the page route
# reads it from the row after the uuid4 id lookup) and no server
# default (NULL is the meaningful state for every non-qualifying
# document and every fail-soft miss).
op.add_column("documents", sa.Column("page_count", sa.Integer(), nullable=True))
def downgrade() -> None:
# The page_count column is the only 0026 artefact — dropping it
# leaves 0025's schema byte-identical (A13, fully reversible). The
# rendered copies are re-derivable from the source files on the
# next sync, so no data is lost by the drop.
op.drop_column("documents", "page_count")