Picarones / .github /workflows /perf_regression.yml
Claude
ci(workflows): align paths with post-rewrite layout + lock against drift
4309925 unverified
# 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}).`,
});
}