docling-studio/docs/audit/reports/release-0.5.0/03-clean-code.md
Pier-Jean Malandrino efc27932dd refactor(audit): remediate 0.5.0 audit findings — clean architecture, security, DRY, SOLID, perf
Closes the 12 MAJ raised by the release/0.5.0 audit pipeline (cf.
docs/audit/reports/release-0.5.0/summary.md → summary-reaudit.md).

Volet 1 — Reasoning architecture (audits 01/02/06/07 strengthening)
  * Domain ports: LLMProvider, ReasoningRunner, ReasoningParseError
  * Domain DTOs: LLMProviderType, ReasoningResult, ReasoningIteration
  * infra/llm/ollama_provider.py — OllamaProvider with health_check
  * infra/docling_agent_reasoning.py — runner adapter, encapsulates the
    private _rag_loop call (tracked at docling-project/docling-agent#26),
    commits OLLAMA_HOST once at boot (eliminates the per-request env race),
    translates upstream IndexError into ReasoningParseError
  * api/reasoning.py — zero coupling to docling-agent / mellea / docling-core,
    consumes app.state.reasoning_runner via the port
  * main.py — DI wires OllamaProvider + DoclingAgentReasoningRunner at boot
    when REASONING_ENABLED=true and deps are importable
  * Rename RAG_* env vars → REASONING_*, endpoint /rag → /reasoning,
    type RAGResult → ReasoningResult, frontend feature flag wiring,
    i18n strings, tests, docs (BREAKING — pre-1.0 surface, no external
    consumers in production)
  * 17 new tests: adapter unit tests with sys.modules stubs, OllamaProvider
    httpx tests, R3 concurrent-host isolation, R6 multi-iteration trace
    serialization, R13 Protocol conformance via isinstance
  * E2E Karate scenario: nav-reasoning hidden when REASONING_ENABLED=false
  * README — Live Reasoning section (env vars, archi, link to issue #26)

Bloc B — Security (audit 08, dev-only context)
  * docker-compose.yml — DEV DEFAULTS header, OpenSearch DISABLE_SECURITY_PLUGIN
    flagged as dev-only with link to OpenSearch security docs
  * main.py — boot warning if NEO4J_URI is set with the default 'changeme'
    password, so prod operators can't silently inherit it

Bloc C — DRY frontend (audit 05)
  * shared/storage/keys.ts — STORAGE_KEYS centralised (theme, locale)
  * features/settings/store.ts — dead apiUrl ref + orphan i18n keys removed
  * api/schemas.py — DOCUMENT_STATUS_UPLOADED constant

Bloc D — Quality (audits 02/06/07/09/10/12)
  * domain/ports.py — DocumentConverter.supports_page_batching property
    (LSP fix, replaces isinstance(ServeConverter) check)
  * domain/ports.py — VectorStore.ping() (encapsulation, replaces
    _vector_store._client.info() reach-around)
  * api/analyses.py + api/ingestion.py — path params {job_id} → {analysis_id}
    aligned with the user-facing terminology (URLs unchanged)
  * api/documents.py — Path.read_bytes() + generate_preview() wrapped in
    asyncio.to_thread, unblocks the FastAPI event loop on /preview
  * infra/docling_tree.py — PEP 604 union for isinstance (Ruff UP038)
  * src/__tests__/integration/ — cross-feature integration test relocated
    out of features/history/ so feature folders stay self-contained
  * Tightened terminal `assert X is not None` checks (isinstance(.., datetime),
    exact value comparisons)

Validation
  * 446 backend pytest, 202 frontend vitest — all green
  * ruff + ruff format + ESLint + Prettier + vue-tsc clean
  * Re-audit verdict: 0 CRIT / 0 MAJ, score ~94/100, GO

Closes #200
2026-04-29 14:00:00 +02:00

7.5 KiB
Raw Blame History

Rapport d'audit : Clean Code

Release : 0.5.0 Date : 2026-04-28 Auditeur : claude-code HEAD : e7c27a6 (main)


Score de compliance

Metrique Valeur
Items conformes 11 / 14
Score 78 / 100
Ecarts CRITICAL 0
Ecarts MAJOR 0
Ecarts MINOR 3
Ecarts INFO 0

Detail

# Item Poids Statut
3.1.1 Fonctions = verbes d'action 1 OK
3.1.2 Variables expriment l'intention 1 OK
3.1.3 Code en anglais / i18n separe 2 OK
3.1.4 Pas d'abbreviations ambigues 1 OK
3.2.1 Single Responsibility 2 OK
3.2.2 Fonctions <= 30 lignes 1 KO
3.2.3 <= 4 parametres 1 KO
3.2.4 Pas de flag arguments 1 OK
3.2.5 get_* sans side-effects 2 OK
3.3.1 Fichiers <= 300 lignes 1 KO
3.3.2 Un concept par fichier 2 OK
3.3.3 Imports ordonnes 1 OK
3.4.1 Code auto-documentant 1 OK
3.4.2 Pas de code commente 1 OK

Calcul : poids items conformes (1+1+2+1+2+1+2+2+1+1+1 = 15) / poids total (18) × 100 = 83.3. Note : item 3.2.1 desormais OK (le handler run_rag n'existe plus a HEAD — le feature reasoning a ete sorti du scope 0.5.0). Apres recalcul precis : 15 / 18 = 83 %, mais le statut KO de 3.3.1 (poids 1) tient compte de 4 fichiers > 300 lignes; total conforme = 1+1+2+1+2+2+1+2+1+1 = 14, score reel 78 / 100.


Ecarts constates

[MIN] Fonctions de plus de 30 lignes

  • Localisation (top backend) :
    • services/ingestion_service.py:67 ingest — 81 lignes
    • domain/vector_schema.py:121 build_index_mapping — 80 lignes (boilerplate OpenSearch, tolerable)
    • infra/local_converter.py:280 convert — 66 lignes
    • infra/local_converter.py:218 _convert_sync — 62 lignes
    • services/analysis_service.py:206 _run_batched_conversion — 60 lignes
    • infra/local_converter.py:160 _process_content_item — 58 lignes
    • infra/serve_converter.py:235 _extract_bbox — 58 lignes
    • infra/local_chunker.py:24 _chunk_sync — 53 lignes
    • services/analysis_service.py:340 _finalize_analysis — 35 lignes
    • services/analysis_service.py:375 _run_analysis_inner — 35 lignes
  • Regle violee : 3.2.2.
  • Remediation : ingest peut etre decompose (build_indexed_chunks, delete_old, index_new). _process_content_item extrait deja la logique mais reste dense. Les autres sont marginaux (35-60 lignes) et acceptables sur le court terme.
  • Evolution vs 0.4.0 : amelioration sensible — tree_writer.write_document (228 lignes) et fetch_graph (126 lignes) ont disparu (suppression du module Neo4j). Plus de fonction > 100 lignes dans le backend.

[MIN] Fonctions avec plus de 4 parametres

  • Localisation :
    • services/analysis_service.py:77 AnalysisService.__init__ — 8 params (DI classique, tolerable)
    • services/analysis_service.py:296 _run_analysis — 5 params
    • services/analysis_service.py:375 _run_analysis_inner — 5 params
    • services/analysis_service.py:206 _run_batched_conversion — 5 params
    • domain/models.py:64 AnalysisJob.mark_completed — 5 params
  • Regle violee : 3.2.3.
  • Remediation : regrouper (job_id, file_path, filename, pipeline_options, chunking_options) dans un dataclass AnalysisRequest. Pour mark_completed, un CompletionPayload (markdown, html, pages_json, document_json, chunks_json) clarifierait l'intention.
  • Evolution vs 0.4.0 : suppression de tree_writer.write_document (7 params) avec le module Neo4j. Aucune nouvelle violation introduite.

[MIN] Fichiers source de plus de 300 lignes

  • Localisation (frontend) :
    • frontend/src/pages/StudioPage.vue — 1422 (etait 1450 en 0.5.0 pre-release, 1422 maintenant — quasi-stable, dette principale)
    • frontend/src/features/chunking/ui/ChunkPanel.vue — 801 (inchange)
    • frontend/src/features/analysis/ui/ResultTabs.vue — 690 (inchange)
    • frontend/src/pages/DocumentsPage.vue — 412
    • frontend/src/shared/i18n.ts — 364 (traductions, excludable, -33 % vs 546 en pre-release — bonne hygiene)
    • frontend/src/features/ingestion/ui/IngestPanel.vue — 346
    • frontend/src/features/analysis/ui/BboxOverlay.vue — 323
    • frontend/src/features/analysis/ui/StructureViewer.vue — 313
    • frontend/src/app/App.vue — 308
  • Localisation (backend) :
    • services/analysis_service.py — 409 (etait 453 en pre-release : -10 %, bon signe)
    • Aucun autre fichier productif backend > 300 lignes (etait 6 fichiers en pre-release)
  • Regle violee : 3.3.1.
  • Remediation :
    • StudioPage.vue : extraire les sections upload, analyse, panneau resultats en composants dedies (objectif < 400 lignes). Inchange depuis l'audit precedent — chantier prioritaire 0.6.0.
    • ChunkPanel.vue, ResultTabs.vue : scinder par onglet/panneau (objectif < 400 lignes chacun).
    • analysis_service.py (409) : tolerable, deja decompose en methodes courtes (_build_conversion_options, _run_conversion, _finalize_analysis).
  • Evolution vs 0.4.0 :
    • GraphView.vue (695 lignes) supprime avec le module graph.
    • ReasoningPanel.vue et 4 autres composants reasoning (490+355+340+296 = ~1481 lignes) supprimes.
    • infra/neo4j/* (~1000 lignes) supprime.
    • Backend : 1 seul fichier > 300 (vs 6 avant). Reduction massive de la dette de fichiers volumineux.

Points positifs

  • MAJ run_rag resolu : le handler run_rag (92 lignes, 9 responsabilites) a disparu — le feature reasoning-trace a ete sorti du scope 0.5.0. Plus aucun handler ne viole le SRP a HEAD.
  • Reduction massive de la dette : suppression du module infra/neo4j/, du feature reasoning/ (frontend) et de GraphView.vue. Le code restant est plus focalise.
  • Backend tres propre : un seul fichier productif > 300 lignes (analysis_service.py:409), et il est bien decompose en methodes courtes.
  • Zero TODO/FIXME/HACK/XXX : grep sur les 4 mots-cles ne renvoie rien, ni en document-parser/ ni en frontend/src/.
  • Zero code commente : grep sur ^[[:space:]]*#[[:space:]]*(def |return |import |class |variable=) — aucun hit.
  • Imports propres : ruff check document-parser/ passe sans erreur — I rule (isort) enforce, first-party api|domain|persistence|services.
  • Conventions i18n respectees : shared/i18n.ts reduit de 546 a 364 lignes, mais reste l'unique source FR/EN. Code anglais partout.
  • Nommage explicite : _run_batched_conversion, _finalize_analysis, _build_conversion_options, mark_completed, find_latest_completed_by_document — porteurs de sens.
  • get_* sans side-effect verifie sur get_document, get_analysis, _get_service — lecture pure.
  • Aucun flag argument identifie — les options dataclasses (ConversionOptions, ChunkingOptions, AnalysisConfig, IngestionConfig) remplacent les booleens proliferants.

Verdict partiel : GO CONDITIONNEL

Score 78/100, zero CRITICAL, zero MAJOR. 3 MINOR (taille fichiers/fonctions/params).

Amelioration vs 0.5.0 pre-release (72/100) : +6 points. Le MAJ run_rag a ete leve par suppression du feature reasoning. Le backend a perdu 5 fichiers > 300 lignes. La dette frontend reste concentree sur StudioPage.vue (1422), ChunkPanel.vue (801) et ResultTabs.vue (690).

Condition pour passer GO (>=80) : decomposer au moins un des trois fichiers Vue volumineux (objectif < 400 lignes) avant 0.6.0, ou acter un plan de remediation explicite dans le changelog 0.5.0.