docling-studio/docs/design/256-doc-tab-chunk-mode-404.md

35 KiB
Raw Blame History

Design: Doc tab: chunk mode returns 404

  • Issue: #256
  • Title on issue: [BUG] Doc tab: chunk mode returns 404
  • Author: Pier-Jean Malandrino
  • Date: 2026-05-07
  • Status: Draft
  • Target milestone: 0.6.0 — Doc-centric ingest
  • Impacted layers: backend: api · services · (persistence read-only — déjà en place) · frontend: features/chunks (zéro changement attendu, déjà aligné sur le contrat) · e2e: nouveau scénario Karate
  • Audit dimensions likely touched: Hexagonal Architecture · DDD · Decoupling · Tests · Documentation
  • ADR spawned?: no — le modèle canonical Chunk (port + repository) existe déjà depuis #205, on ne fait que combler une couche service + API absente.

1. Problem

Dans l'onglet Doc (DocWorkspacePageDocChunksTab), l'activation du mode chunk déclenche un HTTP 404.

L'investigation a montré que ce n'est pas un endpoint isolé qui manque, mais 9 routes complètes côté /api/documents/{id}/... qui sont appelées depuis le front mais n'existent pas côté backend. La couche frontend a été développée sur la cible 0.6.0 (doc-centric) avant que la couche API correspondante ne soit posée.

Le domain et la persistence du modèle canonical Chunk (entité de première classe, audit log via ChunkEdit, snapshots ChunkPush) existent déjà depuis l'issue #205 (document-parser/domain/models.py, domain/ports.py, persistence/chunk_repo.py, persistence/chunk_edit_repo.py). Ce qui manque, c'est la couche service et la couche API qui les exposent au front.

Note importante: le mode OCR Debug de StudioPage (/api/analyses/*) doit rester intact. Il opère sur des AnalysisJob éphémères avec pipeline options variables, ce qui est conceptuellement différent des chunks canonical d'un document. Les deux flux partagent le service de chunking sous-jacent (port DocumentChunker) mais exposent des ressources distinctes — pas de duplication d'endpoints.

2. Goals

  • Mode chunk de l'onglet Doc renvoie la liste des chunks canonical sans 404 (endpoint GET /api/documents/{id}/chunks opérationnel).
  • Toutes les actions d'édition fonctionnent: add, edit, delete, split, merge, rechunk (endpoints POST/PATCH/DELETE correspondants).
  • Le push vers un store et le diff par store fonctionnent (POST /push, GET /diff?store=…).
  • L'arbre structurel s'affiche sur l'onglet Inspect (GET /tree).
  • Le mode OCR Debug (StudioPage/api/analyses/*) reste totalement fonctionnel — non-régression.
  • Promotion automatique: à la première analyse réussie d'un document, ses chunks deviennent canonical (peuplent chunks table via ChunkRepository).
  • Tests: unit Vitest sur les nouveaux appels, pytest sur le service + router, e2e Karate sur le flow Doc tab → mode chunk.

3. Non-goals

  • Pas de refonte du modèle de données. Le domain Chunk / ChunkEdit / ChunkPush est déjà posé (#205). On ne touche ni aux dataclasses ni au schéma SQLite.
  • Pas de refonte de StudioPage / OCR Debug. Le flux /api/analyses/* reste tel quel; aucun port ni service de l'AnalysisService n'est modifié au-delà du hook de promotion canonical.
  • Pas de fusion features/chunking/features/chunks/ côté front. Ce sont deux features sémantiquement distinctes (acte de chunking debug vs état canonical persistant).
  • Pas de refactor des fichiers > 300 lignes identifiés dans l'audit (StudioPage, GraphView, ChunksEditor) — sera traité dans des issues séparées du milestone 0.7.0.
  • Pas d'ajout de nouvelle table ni de nouvelles colonnes — tout est déjà présent.
  • Pas de versioning d'API — première exposition de ces routes, pas de breaking change.

4. Context & constraints

Cartographie des 9 routes manquantes

Côté front (déjà écrit, fonctionne dès que le back répond):

Méthode URL Front caller
GET /api/documents/{id}/chunks features/chunks/api.ts:5
POST /api/documents/{id}/chunks features/chunks/api.ts:42
PATCH /api/documents/{id}/chunks/{chunkId} features/chunks/api.ts:13
DELETE /api/documents/{id}/chunks/{chunkId} features/chunks/api.ts:38
POST /api/documents/{id}/chunks/{chunkId}/split features/chunks/api.ts:31
POST /api/documents/{id}/chunks/merge features/chunks/api.ts:20
POST /api/documents/{id}/rechunk features/document/api.ts:31
GET /api/documents/{id}/diff?store={name} features/chunks/api.ts:49
POST /api/documents/{id}/chunks/push features/chunks/api.ts:56
POST /api/documents/{id}/push features/document/api.ts:35 (alias par doc — voir §5)
GET /api/documents/{id}/tree features/document/api.ts:42

Surface de code touchée

  • Backend (à créer):

    • document-parser/services/chunk_service.py (nouveau)
    • document-parser/api/document_chunks.py (nouveau router)
    • document-parser/api/schemas.py (DTOs camelCase)
    • document-parser/main.py (registration du router + injection du service)
    • document-parser/services/analysis_service.py (hook de promotion canonical)
    • document-parser/tests/services/test_chunk_service.py (nouveau)
    • document-parser/tests/api/test_document_chunks.py (nouveau)
  • Backend (existant, lecture seule):

    • document-parser/domain/models.pyChunk, ChunkEdit, ChunkPush
    • document-parser/domain/ports.pyChunkRepository, ChunkEditRepository, ChunkPushRepository, DocumentChunker
    • document-parser/persistence/chunk_repo.py, chunk_edit_repo.py
    • document-parser/services/ingestion_service.py (réutilisé pour push)
    • document-parser/services/store_service.py (réutilisé pour diff par store)
  • Frontend (zéro changement attendu):

    • frontend/src/features/chunks/api.ts — déjà aligné sur le contrat
    • frontend/src/features/document/api.ts — déjà aligné
  • E2E (à créer):

    • e2e/ui/src/test/resources/documents/doc-tab-chunk-mode.feature

Hexagonal Architecture

  • Le domain ne change pas. Aucun nouveau port n'est ajouté: tous les besoins sont déjà couverts (ChunkRepository, ChunkEditRepository, DocumentChunker).
  • Le nouveau ChunkService orchestre des ports existants et délègue à AnalysisService.rechunk pour le rechunk du doc canonical (DRY: la mécanique chunk-via-Docling est une seule implémentation).
  • L'API ne franchit pas de couche: le router document_chunks.py parle uniquement aux services, jamais à chunk_repo directement.
  • Conséquence: zéro nouvelle dépendance entre couches.

Deployment modes

  • Supporté: latest-local et latest-remote à l'identique. Aucun comportement spécifique au CONVERSION_ENGINEChunkService ne touche pas au converter, seulement au chunker (port DocumentChunker qui a une seule implémentation).
  • HF Space: identique à latest-remote. Pas de feature flag à ajouter dans /api/health — les chunks canonical font partie du flow doc-centric standard.

Hard constraints

  • Pas de migration SQLite (tables existent déjà).
  • Pas de breaking change API (premières routes exposées).
  • Performance: les opérations chunk lisent/écrivent ≤ N chunks pour un doc (typiquement < 200). Aucune requête non-bornée. Le rechunk lance Docling chunker en thread (asyncio.to_thread), comme analysis_service.rechunk actuel.

5. Proposed design

5.1 Domain

Aucun changement. Tout est en place depuis #205:

  • Chunk(id, document_id, sequence, text, headings, source_page, bboxes, doc_items, token_count, created_at, updated_at, deleted_at)
  • ChunkEdit(id, document_id, chunk_id, action ∈ INSERT|UPDATE|DELETE|MERGE|SPLIT, actor, at, before, after, parents, children, reason)
  • ChunkPush(id, document_id, store_id, chunkset_hash, chunk_ids, …)
  • Ports: ChunkRepository, ChunkEditRepository, ChunkPushRepository, DocumentChunker.

5.2 Persistence

Aucun changement. chunk_repo.py et chunk_edit_repo.py couvrent CRUD + audit. Le schéma SQLite existe déjà (vérifier database.py). Si une colonne ou un index manque pour les nouvelles requêtes (par ex. lookup chunks d'un doc ordonnés par sequence), ajouter dans la PR via un script idempotent.

5.3 Infra adapters

Aucun changement. DocumentChunker est déjà implémenté (le même qui sert analysis_service.rechunk).

5.4 Services

Nouveau: ChunkServicedocument-parser/services/chunk_service.py

Orchestre toutes les opérations sur le chunkset canonical d'un document. Délègue aux ports existants. Écrit les ChunkEdit en transaction avec chaque mutation.

class ChunkService:
    def __init__(
        self,
        chunk_repo: ChunkRepository,
        chunk_edit_repo: ChunkEditRepository,
        chunker: DocumentChunker,
        document_repo: DocumentRepository,
        analysis_service: AnalysisService,  # pour le rechunk
    ): ...

    async def list_chunks(self, doc_id: str) -> list[Chunk]: ...
    async def add_chunk(self, doc_id: str, text: str, after_id: str | None) -> Chunk: ...
    async def update_chunk(self, doc_id: str, chunk_id: str, *, text: str | None, headings: list[str] | None) -> Chunk: ...
    async def delete_chunk(self, doc_id: str, chunk_id: str) -> None: ...
    async def split_chunk(self, doc_id: str, chunk_id: str, cursor_offset: int) -> list[Chunk]: ...
    async def merge_chunks(self, doc_id: str, ids: list[str]) -> Chunk: ...
    async def rechunk_document(self, doc_id: str, opts: ChunkingOptions | None) -> list[Chunk]: ...
    async def get_tree(self, doc_id: str) -> list[DocTreeNode]: ...
    async def diff_against_store(self, doc_id: str, store: str) -> list[ChunkDiff]: ...
    async def push_to_store(self, doc_id: str, store: str) -> ChunkPush: ...

Règles d'invariants:

  • sequence des chunks reste dense ascendant. add_chunk insère et incrémente les suivants. delete_chunk est soft (marque deleted_at) pour préserver l'audit; les listings filtrent.
  • Toute mutation produit une ligne ChunkEdit atomiquement.
  • rechunk_document: appelle analysis_service.rechunk_for_document(doc_id, opts) (helper public à exposer dans AnalysisService si pas déjà), récupère la liste de chunks, remplace le chunkset canonical (delete soft de l'ancien + insert nouveau), enregistre un ChunkEdit RESET.
  • get_tree: dérive de la dernière AnalysisJob réussie (lecture de pages_json / document_json). Pas de calcul nouveau.
  • push_to_store et diff_against_store: délèguent à ingestion_service / store_service existants. Aucune logique dupliquée; le service expose juste l'interface "doc-centric".

Hook de promotion canonical — AnalysisService.create

À la fin d'une analyse réussie, si le document n'a pas encore de chunkset canonical (chunk_repo.count_for_document(doc_id) == 0), peupler depuis job.chunks_json:

async def create(self, ...) -> AnalysisJob:
    job = await self._run_analysis(...)
    if job.status == AnalysisStatus.COMPLETED and job.chunks_json:
        if await self._chunk_repo.count_for_document(job.document_id) == 0:
            await self._promote_to_canonical(job)  # nouveau private method
    return job

Promotion = parse chunks_json → instancie Chunk(document_id=…)chunk_repo.bulk_insert(chunks)chunk_edit_repo.insert(ChunkEdit(action=INSERT, actor='system:initial-analysis')).

C'est le seul point qui croise les deux contextes (analyse → doc canonical). Reste local à AnalysisService.

5.5 API

Nouveau router: document-parser/api/document_chunks.py

Préfixe /api/documents/{doc_id}, tag documents. Toutes les routes injectent ChunkService via Depends.

@router.get("/{doc_id}/chunks", response_model=list[ChunkResponse])
@router.post("/{doc_id}/chunks", response_model=ChunkResponse)                 # body: AddChunkRequest
@router.patch("/{doc_id}/chunks/{chunk_id}", response_model=ChunkResponse)     # body: UpdateChunkRequest
@router.delete("/{doc_id}/chunks/{chunk_id}", status_code=204)
@router.post("/{doc_id}/chunks/{chunk_id}/split", response_model=list[ChunkResponse])  # body: SplitChunkRequest { cursorOffset }
@router.post("/{doc_id}/chunks/merge", response_model=ChunkResponse)           # body: MergeChunksRequest { ids }
@router.post("/{doc_id}/rechunk", response_model=list[ChunkResponse])          # body: RechunkRequest (optionnel)
@router.get("/{doc_id}/tree", response_model=list[DocTreeNodeResponse])
@router.get("/{doc_id}/diff", response_model=list[ChunkDiffResponse])          # query: store
@router.post("/{doc_id}/chunks/push", response_model=PushSummaryResponse)      # body: PushRequest { store }

Note: deux URLs présentes côté front pour push (/{id}/push dans document/api.ts et /{id}/chunks/push dans chunks/api.ts). Une seule route doit exister côté backend pour respecter la directive "no duplicate". Choix: garder /{id}/chunks/push (sémantiquement plus juste — c'est bien des chunks qu'on pousse). Mettre à jour features/document/api.ts:34-39 pour cibler la même URL et virer la duplication.

DTOs Pydantic: tous camelCase (alias_generator), chunkId au lieu de chunk_id côté wire, sourcePage etc.

Erreurs:

  • 404 si doc inexistant ou chunk inexistant
  • 409 si merge_chunks reçoit des ids non contigus (invariant sequence)
  • 400 si split reçoit un cursorOffset hors range

5.6 Frontend — feature module

Zéro changement attendu. features/chunks/api.ts et features/document/api.ts sont déjà alignés.

Une seule correction à apporter pour respecter "no duplicate endpoints": modifier features/document/api.ts:35 pour pointer sur /api/documents/${id}/chunks/push au lieu de /api/documents/${id}/push. Ensuite cette fonction pushDocumentToStore peut être supprimée et le caller (document/store.ts) bascule sur pushChunksToStore de features/chunks/api.ts.

Bug latents identifiés par l'audit, à corriger dans la même PR (scope #256):

  • ChunksEditor.vue:235saveTimer non clearé sur onUnmounted. Ajouter onBeforeUnmount(() => clearTimeout(saveTimer)).
  • ChunksEditor.vue:215 — remplacer alert(...) natif par le pattern toast partagé.

5.7 Cross-cutting (feature flags, i18n, shared types)

  • Feature flags: aucun. Le mode chunk est un flow standard du doc-centric, pas optionnel.
  • i18n: aucune nouvelle clé requise; les strings existent déjà côté chunks.*.
  • Shared types: DocChunk, ChunkDiff, PushSummary, DocTreeNode existent déjà dans frontend/src/shared/types.ts. Vérifier la correspondance camelCase avec les nouveaux DTOs Pydantic; aucun champ nouveau attendu.

6. Alternatives considered

Alternative A — Recâbler le front sur /api/analyses/*

  • Summary: Côté front, résoudre docId → latest analysis jobId dans DocWorkspacePage, puis appeler les endpoints /api/analyses/{jobId}/... existants (rechunk, chunks/{idx}, etc.). Aucun nouveau endpoint backend.
  • Why not: Conceptuellement faux. Une analyse est une run éphémère avec ses propres pipeline options; un document a un état chunk canonical persistant édité dans le temps. Identifier les deux casserait le modèle 0.6.0 doc-centric et empêcherait d'avoir plusieurs analyses parallèles d'un doc (ce que StudioPage permet) sans muter l'état canonical. Casse aussi l'audit log par chunk (ChunkEdit) qui est par doc, pas par job.

Alternative B — Fusion AnalysisDocument chunks

  • Summary: Supprimer analysis_jobs.chunks_json, faire que toute analyse écrive directement dans la table chunks du doc. StudioPage devient une UI de re-chunking d'un doc.
  • Why not: Casse l'usage debug de StudioPage (plusieurs runs avec pipeline options ≠ ne peuvent pas coexister). Refactor majeur, hors scope d'un bug fix. Pourrait être envisagé en 0.7.0+ si l'usage de StudioPage évolue.

7. API & data contract

Endpoints (tous nouveaux, additifs)

Method Path Request Response Breaking?
GET /api/documents/{id}/chunks ChunkResponse[] additive
POST /api/documents/{id}/chunks AddChunkRequest {text, afterId?} ChunkResponse additive
PATCH /api/documents/{id}/chunks/{chunkId} UpdateChunkRequest {text?, headings?} ChunkResponse additive
DELETE /api/documents/{id}/chunks/{chunkId} 204 additive
POST /api/documents/{id}/chunks/{chunkId}/split SplitChunkRequest {cursorOffset} ChunkResponse[] additive
POST /api/documents/{id}/chunks/merge MergeChunksRequest {ids} ChunkResponse additive
POST /api/documents/{id}/rechunk RechunkRequest {chunkingOptions?} ChunkResponse[] additive
GET /api/documents/{id}/tree DocTreeNodeResponse[] additive
GET /api/documents/{id}/diff?store={name} ChunkDiffResponse[] additive
POST /api/documents/{id}/chunks/push PushRequest {store} PushSummaryResponse additive

Sérialisation camelCase côté wire (Pydantic alias_generator), snake_case interne. pages_json reste l'exception documentée si réutilisé.

Persistence schema

-- Aucune migration. Tables existantes (issue #205):
-- chunks(id, document_id, sequence, text, headings_json, source_page, bboxes_json,
--        doc_items_json, token_count, created_at, updated_at, deleted_at)
-- chunk_edits(id, document_id, chunk_id, action, actor, at, before_json, after_json,
--             parents_json, children_json, reason)
-- chunk_pushes(id, document_id, store_id, chunkset_hash, chunk_ids_json, ...)

Env vars / config

Name Default Allowed Notes
(aucune) Le ChunkService ne lit aucune nouvelle config.

Breaking changes

Aucun. Toutes les routes sont additives, premier passage live. Côté front, suppression de l'alias pushDocumentToStore (URL /push) au profit de pushChunksToStore (URL /chunks/push) — pas exposé externement.

8. Risks & mitigations

Risk Audit dimension Likelihood Impact How we notice Mitigation / rollback
Promotion canonical s'exécute deux fois (race) sur deux analyses simultanées d'un doc neuf, dupliquant le chunkset DDD / Decoupling basse élevée (corruption canonical) tests d'intégration concurrent + count_for_document inattendu en prod Garde idempotente dans _promote_to_canonical (vérif count + insert sous transaction unique)
Régression OCR Debug si analysis_service.create modifié maladroitement Tests / Decoupling moyenne élevée (StudioPage cassé) non-régression e2e Karate full-happy-path.feature Hook de promotion isolé en méthode privée, tests pytest qui couvrent les deux cas (analyse 1ère, analyse N)
Endpoint /diff lent si beaucoup de chunks × store comparé Performance basse moyenne logs de durée > 1s Borne dure (max 500 chunks par doc), pagination si dépassé en 0.7
ChunkEdit audit log non écrit en cas d'erreur partielle (ex: split crash après insert chunk 1/2) Hexagonal Architecture / DDD moyenne moyenne (audit incohérent) tests intégration + assert chunk_edit count après chaque mutation Toutes les mutations en transaction aiosqlite unique chunk + edit
pushDocumentToStore (URL legacy /push) encore appelé quelque part en prod Decoupling basse basse (404 silencieux) grep + e2e Suppression complète de la fonction TS, pas d'alias
Test e2e Karate flaky si l'analyse initiale n'a pas eu le temps de promouvoir Tests moyenne basse flaky en CI E2E poll sur lifecycle_state == 'parsed' avant de cliquer sur l'onglet chunk

9. Testing strategy

Backend — pytest (document-parser/tests/)

  • Unit tests/services/test_chunk_service.py:
    • list_chunks filtre les deleted_at non null
    • add_chunk insert + audit INSERT + recale les sequence suivants
    • update_chunk modifie + audit UPDATE avec before/after correctement remplis
    • delete_chunk soft-delete + audit DELETE
    • split_chunk produit 2 chunks + 2 audit SPLIT avec parents = [source_id]
    • merge_chunks produit 1 chunk + 1 audit MERGE avec parents = [ids]
    • merge_chunks rejette ids non contigus (409)
    • rechunk_document remplace canonical et écrit un audit RESET
    • push_to_store délègue à ingestion_service (mock)
    • diff_against_store délègue à store_service (mock)
  • Unit tests/services/test_analysis_service.py (existant, à étendre):
    • create() peuple chunks canonical à la 1ère analyse réussie
    • create() ne touche PAS au canonical aux analyses suivantes (idempotence)
    • create() ne crée rien si l'analyse échoue
  • Integration tests/api/test_document_chunks.py:
    • Chaque route → 200 happy path
    • Chaque route → 404 si doc inexistant
    • Chaque route mutante → 404 si chunk inexistant
    • merge → 409 si non contigus
    • split → 400 si offset hors range
    • DTOs camelCase respectés sur input ET output
  • Architecture tests: vérifier que api/document_chunks.py n'importe pas persistence/* directement.

Frontend — Vitest

  • features/chunks/api.test.ts (existant, à étendre):
    • Tests d'erreur HTTP (404, 500) sur fetchChunks, updateChunk, mergeChunks, splitChunk, dropChunk, addChunk, fetchChunkDiff, pushChunksToStore
  • features/chunks/store.test.ts (existant, à étendre): $reset à chaque changement de docId (corrige le bug latent identifié dans l'audit).
  • features/document/api.test.ts: ajouter tests pour fetchDocumentTree, rechunkDocument. Supprimer pushDocumentToStore (réécriture vers pushChunksToStore).
  • features/document/store.test.ts: idem.

E2E — Karate UI (e2e/)

  • Nouveau e2e/ui/src/test/resources/documents/doc-tab-chunk-mode.feature:
    • Setup via API: upload doc → wait lifecycle_state == 'parsed'
    • UI: ouvrir /docs/{id} → cliquer onglet Doc → activer mode chunk
    • Assert: liste de chunks visible, pas de bannière 404, count > 0
    • Edit un chunk → assert persistence après reload
    • Cleanup via API: delete doc
    • Tags: @critical @ui
  • Non-régression sur OCR Debug: vérifier que e2e/ui/src/test/resources/full-happy-path.feature (StudioPage) reste vert après les modifications.

Manual QA

  1. docker-compose -f docker-compose.dev.yml up
  2. Upload un PDF via /docs/new, attendre status parsed
  3. Ouvrir /docs/{id} → onglet Doc → bouton chunk → vérifier liste affichée
  4. Ouvrir /studio → upload + run analyse OCR → vérifier que c'est inchangé
  5. Push doc vers un store → vérifier /api/documents/{id}/diff?store=…

Performance / load

Pas d'objectif chiffré particulier. Surveiller: rechunk_document doit rester sous le budget MAX_CONCURRENT_ANALYSES (réutilise le même mécanisme que analysis_service.rechunk).

10. Rollout & observability

Release branch

release/0.6.0 — c'est l'objet du milestone "Doc-centric ingest". Branche de travail: fix/256-doc-tab-chunk-mode-404.

Feature flag / staged rollout

Aucun flag. Le mode chunk fait partie du flow standard 0.6.0; pas de fallback derrière un toggle. La promotion canonical des chunks à la 1ère analyse est inconditionnelle.

Observability

  • Logs structurés à ajouter dans ChunkService:
    • chunk.add docId=… chunkId=… sequence=…
    • chunk.update docId=… chunkId=…
    • chunk.delete docId=… chunkId=…
    • chunk.split docId=… sourceId=… newIds=…
    • chunk.merge docId=… sourceIds=… newId=…
    • chunk.rechunk docId=… count=… durationMs=…
    • chunk.push docId=… store=… count=…
    • chunk.promote docId=… count=… (initial-analysis)
  • Pas de nouvelle métrique Prometheus.
  • Erreurs: les HTTPException 4xx ne loggent pas (normal); les 5xx loggent avec stack via logger.exception (pattern existant dans documents.py:131).

Rollback plan

  • Le déploiement est entièrement additif (nouvelles routes + nouveau service). Rollback = revert du commit / image précédente. Aucun changement de schéma à défaire.
  • Si un bug critique apparaît côté promotion canonical: désactiver le hook dans analysis_service.create via revert ciblé; les analyses continuent de fonctionner sans canonical (l'onglet chunk redeviendrait 404, état antérieur). Pas de perte de données.
  • Si un bug critique apparaît côté API chunks: revert du router suffit; l'onglet chunk redevient 404, le reste de l'app reste fonctionnel.

Liens:

  • Deployment: docs/release/* (/release:deploy)
  • Rollback: /release:rollback
  • Incident: docs/operations/* (/ops:incident)

11. Open questions

  • Le port DocumentChunker accepte-t-il cursor_offset pour split, ou faut-il l'implémenter manuellement dans ChunkService.split_chunk (split textuel basique sans repasser dans Docling) ? À vérifier au moment de l'implémentation.
  • Faut-il enregistrer un ChunkEdit RESET lors du rechunk_document, ou un DELETE par chunk + INSERT par nouveau chunk ? Décision: une seule entrée RESET (lisibilité audit), avec parents = [old_ids], children = [new_ids].
  • get_tree: lit-on pages_json ou document_json ? À confirmer en lisant analysis_service.find_by_id — probablement document_json qui contient la structure docling complète.

12. References

  • Issue: https://github.com/scub-france/Docling-Studio/issues/256
  • Related PRs / commits:
  • ADRs: <ADR-NNN or "none planned">
  • Project docs:
    • Architecture: docs/architecture.md
    • Coding standards: docs/architecture/coding-standards.md
    • ADR guide / template: docs/architecture/adr-guide.md, docs/architecture/adr-template.md
    • Audit master: docs/audit/master.md
    • E2E conventions: e2e/CONVENTIONS.md
  • External: <specs, upstream issues, dashboards, third-party docs>