# Sprint A5 (M-14) — anti-régression de performance OCR. # # Hebdomadaire (cron lundi 06:00 UTC) + manuel via workflow_dispatch. # **Pas** déclenché à chaque PR (coût Tesseract + stabilité statistique # CER nécessitent un corpus plus large que ce qu'on peut tolérer en PR # bloquante). Le but : détecter une dérive franche introduite par un # refactor de la normalisation, du runner, ou un upgrade de pytesseract. # # Sortie : un commentaire automatique sur l'issue #perf-baseline avec # le CER mesuré pour chaque doc + agrégat. Échec dur si CER moyen # > 15 % sur Tesseract (seuil large — détecte les régressions, pas # les variations normales). name: Perf regression (weekly) on: schedule: - cron: '0 6 * * 1' # Lundi 06:00 UTC workflow_dispatch: # Déclenchement manuel pull_request: paths: # Une PR qui touche le runner de benchmark, la normalisation ou # les adapters OCR déclenche aussi le check (cas où on veut # prouver qu'un refactor ne dégrade rien). - 'picarones/app/services/run_orchestrator.py' - 'picarones/app/services/run_orchestrator_execution.py' - 'picarones/app/services/run_orchestrator_helpers/**' - 'picarones/app/services/benchmark_service.py' - 'picarones/pipeline/executor.py' - 'picarones/pipeline/runner.py' - 'picarones/formats/text/normalization.py' - 'picarones/adapters/ocr/**' - '.github/workflows/perf_regression.yml' permissions: contents: read issues: write jobs: perf: name: CER regression check runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: Install Tesseract (Ubuntu) run: | sudo apt-get update -qq sudo apt-get install -y tesseract-ocr tesseract-ocr-fra tesseract-ocr-eng - name: Install Picarones run: | python -m pip install --upgrade pip setuptools wheel pip install -e ".[dev,web]" - name: Regenerate reference corpus (idempotent) run: python tests/fixtures/reference_corpus/_generate.py - name: Run benchmark on reference corpus id: bench run: | mkdir -p /tmp/perf_artifacts picarones run \ --corpus tests/fixtures/reference_corpus/ \ --engines tesseract \ --output /tmp/perf_artifacts/results.json \ --fail-if-cer-above 0.15 \ --no-progress - name: Generate report (lazy_images mode) if: always() run: | picarones report \ --results /tmp/perf_artifacts/results.json \ --output /tmp/perf_artifacts/perf_report.html \ --lazy-images || true - name: Upload artifacts if: always() uses: actions/upload-artifact@v4 with: name: perf-${{ github.run_id }} path: /tmp/perf_artifacts/ retention-days: 30 - name: Comment on tracking issue (success) if: success() && github.event_name == 'schedule' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const data = JSON.parse( fs.readFileSync('/tmp/perf_artifacts/results.json', 'utf-8') ); const eng = data.engine_reports?.[0] || {}; const meanCer = eng.aggregated_metrics?.cer?.mean ?? 'n/a'; const body = `Hebdomadaire — Tesseract CER moyen: **${meanCer}** ` + `(commit \`${context.sha.slice(0,7)}\`, ` + `${data.engine_reports?.[0]?.document_results?.length ?? 0} docs).`; // Cherche l'issue de tracking, en crée une si absente. const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, labels: 'perf-baseline', state: 'open', }); let issueNumber; if (issues.data.length > 0) { issueNumber = issues.data[0].number; } else { const created = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: '📈 Perf baseline (auto-tracking)', body: 'Issue de suivi du job hebdomadaire ' + '`perf_regression.yml`. Chaque exécution y commente ' + 'le CER moyen pour Tesseract.', labels: ['perf-baseline'], }); issueNumber = created.data.number; } await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, body: body, }); - name: Comment on tracking issue (failure) if: failure() && github.event_name == 'schedule' uses: actions/github-script@v7 with: script: | const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, labels: 'perf-baseline', state: 'open', }); if (issues.data.length > 0) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issues.data[0].number, body: '❌ Échec hebdomadaire — CER > 15 % ou crash. ' + `Voir le run [${context.runId}]` + `(${context.serverUrl}/${context.repo.owner}/` + `${context.repo.repo}/actions/runs/${context.runId}).`, }); }