From bd1f6f25814b6b0140d6636dd1c40615624098c6 Mon Sep 17 00:00:00 2001 From: jsboige Date: Sat, 30 May 2026 12:34:23 +0200 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20remove=20auto=5Fenrich=5Fnotebooks?= =?UTF-8?q?.py=20=E2=80=94=20superseded=20by=20/enrich-notebooks=20LLM=20s?= =?UTF-8?q?kill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Script inserted generic template text ('Le code s\'exécute correctement...') with zero pedagogical value. Never executed on production notebooks (grep confirmed zero template traces in repo). Superseded by /enrich-notebooks skill which uses LLM for context-aware interpretations. Also removes old test file and docs reference. Co-Authored-By: Claude Opus 4.8 --- docs/scripts-reference.md | 1 - .../notebook_tools/auto_enrich_notebooks.py | 193 ------------------ .../tests/test_auto_enrich_notebooks.py | 138 ------------- 3 files changed, 332 deletions(-) delete mode 100644 scripts/notebook_tools/auto_enrich_notebooks.py delete mode 100644 scripts/notebook_tools/tests/test_auto_enrich_notebooks.py diff --git a/docs/scripts-reference.md b/docs/scripts-reference.md index 126752d5d..84769cecd 100644 --- a/docs/scripts-reference.md +++ b/docs/scripts-reference.md @@ -49,7 +49,6 @@ Les scripts `scripts/fix_*.py` / `scripts/recycle_*.py` à la racine sont des on | `qc_classify.py` | Classification stratégies QC (BROKEN/NEEDS_IMPROVEMENT/HEALTHY) | | `epita_prcon_autograde.py` | Autograde EPITA Programmation par Contraintes | | `weekly_digest.py` | Digest hebdomadaire d'activité | -| `auto_enrich_notebooks.py` | Enrichissement markdown pédagogique automatique | | `fix_audio_dependencies.py`, `optimize_dvs.py` | Dépendances audio / optimisation | | `_alpha_diag.py`, `generate_16e.py` | Diagnostics ponctuels | diff --git a/scripts/notebook_tools/auto_enrich_notebooks.py b/scripts/notebook_tools/auto_enrich_notebooks.py deleted file mode 100644 index 8177238f7..000000000 --- a/scripts/notebook_tools/auto_enrich_notebooks.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 -""" -Script d'enrichissement automatique de notebooks GenAI Image. -Ajoute des interprétations pédagogiques après les cellules de code significatives. -""" - -import json -import sys -from pathlib import Path -from typing import Dict, List, Any - - -def load_notebook(notebook_path: Path) -> Dict[str, Any]: - """Charge un notebook Jupyter.""" - with open(notebook_path, 'r', encoding='utf-8') as f: - return json.load(f) - - -def save_notebook(notebook_path: Path, notebook: Dict[str, Any]) -> None: - """Sauvegarde un notebook Jupyter.""" - with open(notebook_path, 'w', encoding='utf-8') as f: - json.dump(notebook, f, indent=1, ensure_ascii=False) - - -def calculate_ratio(notebook: Dict[str, Any]) -> float: - """Calcule le ratio markdown/code d'un notebook.""" - md = sum(1 for c in notebook['cells'] if c['cell_type'] == 'markdown') - code = sum(1 for c in notebook['cells'] if c['cell_type'] == 'code') - total = md + code - return (md / total * 100) if total > 0 else 0 - - -def create_interpretation_cell(title: str, content: str) -> Dict[str, Any]: - """Crée une cellule markdown d'interprétation.""" - return { - "cell_type": "markdown", - "metadata": {}, - "source": [ - f"### Interprétation - {title}\n", - "\n", - *content.splitlines(keepends=True), - "\n", - "---\n" - ] - } - - -def enrich_notebook(notebook_path: Path, min_ratio: float = 40.0) -> Dict[str, Any]: - """ - Enrichit un notebook avec des interprétations pédagogiques. - - Args: - notebook_path: Chemin du notebook à enrichir - min_ratio: Ratio cible (défaut: 40%) - - Returns: - Dictionnaire avec les statistiques d'enrichissement - """ - print(f"\n{'='*60}") - print(f"Traitement: {notebook_path.name}") - print(f"{'='*60}") - - # Chargement du notebook - notebook = load_notebook(notebook_path) - initial_ratio = calculate_ratio(notebook) - - print(f"Ratio initial: {initial_ratio:.1f}%") - - # Si le ratio est déjà suffisant, passer - if initial_ratio >= min_ratio: - print(f"✅ Ratio déjà conforme ({initial_ratio:.1f}% >= {min_ratio}%)") - return { - "status": "skip", - "initial_ratio": initial_ratio, - "final_ratio": initial_ratio, - "cells_added": 0 - } - - # Identification des cellules de code significatives - # (avec sorties ou contenu substantiel) - code_cells_with_output = [] - for i, cell in enumerate(notebook['cells']): - if cell['cell_type'] == 'code': - # Cellules avec sorties ou contenu > 5 lignes - has_output = 'outputs' in cell and len(cell['outputs']) > 0 - has_content = len(cell.get('source', [])) > 5 - if has_output or has_content: - code_cells_with_output.append(i) - - print(f"Cellules de code significatives: {len(code_cells_with_output)}") - - # Ajout d'interprétations après les cellules de code - # Travail de la fin vers le début pour éviter les décalages d'index - cells_added = 0 - for idx in reversed(code_cells_with_output): - cell = notebook['cells'][idx] - cell_id = cell.get('id', f'cell_{idx}') - - # Génération d'une interprétation générique mais pédagogique - interpretation = create_interpretation_cell( - title=f"Analyse de la Cellule {idx}", - content=f"""**Observation** : Cette cellule exécute du code Python avec des sorties significatives. - -**Points clés à retenir** : -- Le code s'exécute correctement et produit des résultats -- Les sorties peuvent inclure des visualisations, des statistiques ou des données -- Vérifiez toujours que les résultats correspondent aux attentes - -**Note technique** : Pour reproduire cette analyse, assurez-vous que les dépendances sont installées et que les données d'entrée sont disponibles. - ---- -""" - ) - - # Insertion après la cellule de code - notebook['cells'].insert(idx + 1, interpretation) - cells_added += 1 - - # Recalcul du ratio - final_ratio = calculate_ratio(notebook) - print(f"Ratio final: {final_ratio:.1f}%") - print(f"Cellules ajoutées: {cells_added}") - - # Sauvegarde si amélioration significative - if final_ratio > initial_ratio: - save_notebook(notebook_path, notebook) - print(f"✅ Notebook enrichi et sauvegardé") - - return { - "status": "success", - "initial_ratio": initial_ratio, - "final_ratio": final_ratio, - "cells_added": cells_added - } - else: - print(f"⚠️ Aucune amélioration significative") - return { - "status": "no_change", - "initial_ratio": initial_ratio, - "final_ratio": final_ratio, - "cells_added": 0 - } - - -def main(): - """Fonction principale.""" - # Notebooks à traiter (ceux nécessitant un enrichissement) - notebooks_to_enrich = [ - "MyIA.AI.Notebooks/GenAI/Image/02-Advanced/02-4-Z-Image-Lumina2.ipynb", - "MyIA.AI.Notebooks/GenAI/Image/03-Orchestration/03-3-Performance-Optimization.ipynb", - "MyIA.AI.Notebooks/GenAI/Image/04-Applications/04-1-Educational-Content-Generation.ipynb", - "MyIA.AI.Notebooks/GenAI/Image/04-Applications/04-2-Creative-Workflows.ipynb", - "MyIA.AI.Notebooks/GenAI/Image/04-Applications/04-3-Production-Integration.ipynb", - ] - - results = {} - - for notebook_path_str in notebooks_to_enrich: - notebook_path = Path(notebook_path_str) - - if not notebook_path.exists(): - print(f"❌ Notebook non trouvé: {notebook_path}") - results[notebook_path.name] = {"status": "not_found"} - continue - - try: - result = enrich_notebook(notebook_path) - results[notebook_path.name] = result - except Exception as e: - print(f"❌ Erreur: {e}") - results[notebook_path.name] = {"status": "error", "error": str(e)} - - # Résumé - print(f"\n{'='*60}") - print("RÉSUMÉ DE L'ENRICHISSEMENT") - print(f"{'='*60}") - - for name, result in results.items(): - status = result.get("status", "unknown") - if status == "success": - initial = result.get("initial_ratio", 0) - final = result.get("final_ratio", 0) - cells = result.get("cells_added", 0) - print(f"✅ {name}: {initial:.1f}% → {final:.1f}% (+{cells} cells)") - elif status == "skip": - ratio = result.get("initial_ratio", 0) - print(f"⏭️ {name}: {ratio:.1f}% (déjà conforme)") - else: - print(f"❌ {name}: {status}") - - -if __name__ == "__main__": - main() diff --git a/scripts/notebook_tools/tests/test_auto_enrich_notebooks.py b/scripts/notebook_tools/tests/test_auto_enrich_notebooks.py deleted file mode 100644 index 0b5acb04b..000000000 --- a/scripts/notebook_tools/tests/test_auto_enrich_notebooks.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Tests for auto_enrich_notebooks.py — notebook enrichment ratio and cell creation.""" - -import json -import sys -from pathlib import Path - -import pytest - -sys.path.insert(0, str(Path(__file__).parent.parent)) -from auto_enrich_notebooks import ( - calculate_ratio, - create_interpretation_cell, - load_notebook, - save_notebook, -) - - -def _nb(cells: list[dict]) -> dict: - """Build a minimal notebook dict.""" - return { - "cells": cells, - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 5, - } - - -# --- calculate_ratio --- - - -class TestCalculateRatio: - def test_all_markdown(self): - nb = _nb([ - {"cell_type": "markdown", "source": ["# Title"]}, - {"cell_type": "markdown", "source": ["## Section"]}, - ]) - assert calculate_ratio(nb) == 100.0 - - def test_all_code(self): - nb = _nb([ - {"cell_type": "code", "source": ["x = 1"]}, - {"cell_type": "code", "source": ["y = 2"]}, - ]) - assert calculate_ratio(nb) == 0.0 - - def test_balanced(self): - nb = _nb([ - {"cell_type": "markdown", "source": ["text"]}, - {"cell_type": "code", "source": ["x = 1"]}, - ]) - assert calculate_ratio(nb) == 50.0 - - def test_empty_notebook(self): - nb = _nb([]) - assert calculate_ratio(nb) == 0.0 - - def test_three_code_one_md(self): - nb = _nb([ - {"cell_type": "markdown", "source": ["text"]}, - {"cell_type": "code", "source": ["x = 1"]}, - {"cell_type": "code", "source": ["y = 2"]}, - {"cell_type": "code", "source": ["z = 3"]}, - ]) - assert calculate_ratio(nb) == 25.0 - - def test_ignores_raw_cells(self): - """Non-markdown, non-code cells are not counted in total.""" - nb = _nb([ - {"cell_type": "markdown", "source": ["text"]}, - {"cell_type": "raw", "source": ["raw content"]}, - ]) - # md=1, code=0, total=1 -> 100% - assert calculate_ratio(nb) == 100.0 - - -# --- create_interpretation_cell --- - - -class TestCreateInterpretationCell: - def test_returns_markdown(self): - cell = create_interpretation_cell("Test", "Some content") - assert cell["cell_type"] == "markdown" - - def test_contains_title(self): - cell = create_interpretation_cell("My Analysis", "Body text") - source = "".join(cell["source"]) - assert "My Analysis" in source - - def test_contains_content(self): - cell = create_interpretation_cell("Title", "Line1\nLine2") - source = "".join(cell["source"]) - assert "Line1" in source - assert "Line2" in source - - def test_source_is_list(self): - cell = create_interpretation_cell("Title", "Content") - assert isinstance(cell["source"], list) - - def test_has_metadata(self): - cell = create_interpretation_cell("Title", "Content") - assert "metadata" in cell - - def test_interpretation_header_prefix(self): - cell = create_interpretation_cell("Plot", "desc") - source = "".join(cell["source"]) - assert "Interprétation - Plot" in source - - -# --- load_notebook / save_notebook --- - - -class TestLoadSaveNotebook: - def test_roundtrip(self, tmp_path): - nb = _nb([ - {"cell_type": "code", "source": ["x = 1"]}, - ]) - p = tmp_path / "test.ipynb" - save_notebook(p, nb) - loaded = load_notebook(p) - assert loaded["cells"] == nb["cells"] - - def test_save_creates_file(self, tmp_path): - p = tmp_path / "new.ipynb" - save_notebook(p, _nb([])) - assert p.exists() - - def test_load_nonexistent_raises(self, tmp_path): - with pytest.raises(FileNotFoundError): - load_notebook(tmp_path / "nope.ipynb") - - def test_save_preserves_unicode(self, tmp_path): - nb = _nb([ - {"cell_type": "markdown", "source": ["# Résumé\nInterprétation"]}, - ]) - p = tmp_path / "unicode.ipynb" - save_notebook(p, nb) - loaded = load_notebook(p) - assert "Résumé" in "".join(loaded["cells"][0]["source"]) From b6ef02e56e93eb1b14604fe98611b3c9c8e0b2fd Mon Sep 17 00:00:00 2001 From: jsboige Date: Sat, 30 May 2026 12:46:27 +0200 Subject: [PATCH 2/2] chore: remove orphaned test_auto_enrich_notebooks.py merged via #1874 #1874 added scripts/tests/test_auto_enrich_notebooks.py (33 tests) for a script this PR deletes as superseded by /enrich-notebooks. Leaving it would orphan the test (ImportError on deleted module) and break the suite. Removing it completes the cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/tests/test_auto_enrich_notebooks.py | 321 -------------------- 1 file changed, 321 deletions(-) delete mode 100644 scripts/tests/test_auto_enrich_notebooks.py diff --git a/scripts/tests/test_auto_enrich_notebooks.py b/scripts/tests/test_auto_enrich_notebooks.py deleted file mode 100644 index 498c6dd76..000000000 --- a/scripts/tests/test_auto_enrich_notebooks.py +++ /dev/null @@ -1,321 +0,0 @@ -"""Tests for scripts/notebook_tools/auto_enrich_notebooks.py — auto enrichment. - -Tests focus on pure functions: calculate_ratio, create_interpretation_cell, -load_notebook, save_notebook, enrich_notebook — covering ratio calculation, -cell creation, skip/success/no_change status, insertion order, edge cases. -""" - -import json -import sys -from pathlib import Path - -import pytest - -sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "notebook_tools")) -from auto_enrich_notebooks import ( - calculate_ratio, - create_interpretation_cell, - enrich_notebook, - load_notebook, - save_notebook, -) - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _code(source: str | list, outputs: list | None = None, cell_id: str | None = None) -> dict: - cell = { - "cell_type": "code", - "source": [source] if isinstance(source, str) else source, - "execution_count": 1, - "outputs": outputs or [], - } - if cell_id is not None: - cell["id"] = cell_id - return cell - - -def _md(source: str, cell_id: str | None = None) -> dict: - cell = {"cell_type": "markdown", "source": [source], "metadata": {}} - if cell_id is not None: - cell["id"] = cell_id - return cell - - -def _write_nb(path: Path, cells: list[dict], metadata: dict | None = None) -> Path: - path.parent.mkdir(parents=True, exist_ok=True) - nb = {"cells": cells, "metadata": metadata or {}, "nbformat": 4, "nbformat_minor": 5} - path.write_text(json.dumps(nb), encoding="utf-8") - return path - - -# --------------------------------------------------------------------------- -# load_notebook / save_notebook -# --------------------------------------------------------------------------- - -class TestLoadSaveNotebook: - """Tests for notebook I/O.""" - - def test_load_notebook(self, tmp_path): - nb_path = _write_nb(tmp_path / "test.ipynb", [_code("x = 1")]) - nb = load_notebook(nb_path) - assert len(nb["cells"]) == 1 - assert nb["cells"][0]["cell_type"] == "code" - - def test_load_notebook_preserves_metadata(self, tmp_path): - nb_path = _write_nb(tmp_path / "test.ipynb", [], metadata={"kernelspec": {"name": "python3"}}) - nb = load_notebook(nb_path) - assert nb["metadata"]["kernelspec"]["name"] == "python3" - - def test_save_notebook_creates_file(self, tmp_path): - nb_path = tmp_path / "output.ipynb" - nb = {"cells": [_md("# Title")], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} - save_notebook(nb_path, nb) - assert nb_path.exists() - - loaded = json.loads(nb_path.read_text(encoding="utf-8")) - assert len(loaded["cells"]) == 1 - - def test_save_load_roundtrip(self, tmp_path): - original = {"cells": [_code("a = 1"), _md("text")], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} - nb_path = tmp_path / "roundtrip.ipynb" - save_notebook(nb_path, original) - loaded = load_notebook(nb_path) - assert len(loaded["cells"]) == len(original["cells"]) - - def test_save_notebook_utf8_encoding(self, tmp_path): - nb_path = tmp_path / "unicode.ipynb" - nb = {"cells": [_md("# Titre avec accents: ecaud")], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} - save_notebook(nb_path, nb) - content = nb_path.read_text(encoding="utf-8") - assert "ecaud" in content - - -# --------------------------------------------------------------------------- -# calculate_ratio -# --------------------------------------------------------------------------- - -class TestCalculateRatio: - """Tests for markdown/code ratio calculation.""" - - def test_equal_mix(self): - nb = {"cells": [_code("x = 1"), _md("text"), _code("y = 2"), _md("more")]} - assert calculate_ratio(nb) == 50.0 - - def test_all_markdown(self): - nb = {"cells": [_md("a"), _md("b"), _md("c")]} - assert calculate_ratio(nb) == 100.0 - - def test_all_code(self): - nb = {"cells": [_code("x = 1"), _code("y = 2")]} - assert calculate_ratio(nb) == 0.0 - - def test_empty_notebook(self): - nb = {"cells": []} - assert calculate_ratio(nb) == 0.0 - - def test_single_markdown(self): - nb = {"cells": [_md("only md")]} - assert calculate_ratio(nb) == 100.0 - - def test_single_code(self): - nb = {"cells": [_code("only code")]} - assert calculate_ratio(nb) == 0.0 - - def test_three_markdown_one_code(self): - nb = {"cells": [_md("a"), _md("b"), _md("c"), _code("x = 1")]} - assert calculate_ratio(nb) == 75.0 - - def test_raw_cells_ignored(self): - """Raw cells don't count as markdown or code.""" - nb = {"cells": [ - {"cell_type": "raw", "source": ["raw content"], "metadata": {}}, - _code("x = 1"), - ]} - assert calculate_ratio(nb) == 0.0 - - -# --------------------------------------------------------------------------- -# create_interpretation_cell -# --------------------------------------------------------------------------- - -class TestCreateInterpretationCell: - """Tests for interpretation cell creation.""" - - def test_returns_markdown_cell(self): - cell = create_interpretation_cell("Test", "Some content") - assert cell["cell_type"] == "markdown" - - def test_title_included(self): - cell = create_interpretation_cell("My Analysis", "content") - sources = "".join(cell["source"]) - assert "My Analysis" in sources - - def test_content_included(self): - cell = create_interpretation_cell("Title", "Line 1\nLine 2") - sources = "".join(cell["source"]) - assert "Line 1" in sources - assert "Line 2" in sources - - def test_source_is_list(self): - cell = create_interpretation_cell("Title", "Content") - assert isinstance(cell["source"], list) - - def test_has_metadata(self): - cell = create_interpretation_cell("Title", "Content") - assert "metadata" in cell - - def test_empty_content(self): - cell = create_interpretation_cell("Title", "") - assert cell["cell_type"] == "markdown" - - def test_multiline_content(self): - content = "First line\nSecond line\nThird line" - cell = create_interpretation_cell("Title", content) - sources = "".join(cell["source"]) - assert "First line" in sources - assert "Third line" in sources - - def test_separator_present(self): - cell = create_interpretation_cell("Title", "Content") - sources = "".join(cell["source"]) - assert "---" in sources - - -# --------------------------------------------------------------------------- -# enrich_notebook -# --------------------------------------------------------------------------- - -class TestEnrichNotebook: - """Tests for notebook enrichment logic.""" - - def test_skip_when_ratio_sufficient(self, tmp_path): - """Notebook with ratio >= min_ratio is skipped.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _md("text"), _md("more text"), _md("even more"), - _code("x = 1"), - ]) - result = enrich_notebook(nb_path, min_ratio=40.0) - assert result["status"] == "skip" - assert result["cells_added"] == 0 - - def test_enrich_low_ratio_notebook(self, tmp_path): - """Notebook with low ratio gets enriched.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1", outputs=[{"output_type": "stream"}]), - _code("y = 2", outputs=[{"output_type": "stream"}]), - _code("z = 3"), - ]) - result = enrich_notebook(nb_path, min_ratio=40.0) - assert result["status"] == "success" - assert result["cells_added"] > 0 - assert result["final_ratio"] > result["initial_ratio"] - - def test_no_change_when_no_significant_cells(self, tmp_path): - """Notebook with no significant code cells -> no_change.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1"), # short, no output - _code("y = 2"), # short, no output - ]) - result = enrich_notebook(nb_path, min_ratio=40.0) - # No cells have output or >5 source lines, so none qualify - assert result["status"] in ("skip", "no_change") - - def test_file_modified_on_success(self, tmp_path): - """File is written back when enrichment succeeds.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1", outputs=[{"output_type": "stream"}]), - _code("y = 2"), - _code("z = 3"), - ]) - original = nb_path.read_text(encoding="utf-8") - enrich_notebook(nb_path, min_ratio=40.0) - # File should be different after enrichment - modified = nb_path.read_text(encoding="utf-8") - assert modified != original - - def test_file_not_modified_on_skip(self, tmp_path): - """File is NOT written when ratio already sufficient.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _md("a"), _md("b"), _md("c"), - _code("x = 1"), - ]) - original = nb_path.read_text(encoding="utf-8") - enrich_notebook(nb_path, min_ratio=40.0) - assert nb_path.read_text(encoding="utf-8") == original - - def test_insertion_preserves_order(self, tmp_path): - """Enriched cells are inserted after the corresponding code cell.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1", outputs=[{"output_type": "stream"}], cell_id="c1"), - _code("y = 2"), - ]) - enrich_notebook(nb_path, min_ratio=10.0) - nb = load_notebook(nb_path) - # First cell should still be code, second should be markdown interpretation - assert nb["cells"][0]["cell_type"] == "code" - assert nb["cells"][1]["cell_type"] == "markdown" - - def test_long_source_qualifies(self, tmp_path): - """Code cell with >5 source lines qualifies even without output.""" - long_source = [f"line{i}" for i in range(7)] # 7 lines > 5 - nb_path = _write_nb(tmp_path / "test.ipynb", [ - {"cell_type": "code", "source": long_source, "outputs": [], "execution_count": 1}, - _code("short"), - ]) - result = enrich_notebook(nb_path, min_ratio=40.0) - assert result["cells_added"] >= 1 - - def test_returns_stats(self, tmp_path): - """Result dict contains required keys.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1", outputs=[{"output_type": "stream"}]), - ]) - result = enrich_notebook(nb_path, min_ratio=10.0) - assert "status" in result - assert "initial_ratio" in result - assert "final_ratio" in result - assert "cells_added" in result - - def test_min_ratio_custom(self, tmp_path): - """Custom min_ratio threshold works.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _md("a"), _code("x = 1"), - ]) - # 50% ratio, min_ratio=60 -> should try to enrich - result = enrich_notebook(nb_path, min_ratio=60.0) - assert result["initial_ratio"] == 50.0 - - def test_all_markdown_notebook_skip(self, tmp_path): - """Notebook with only markdown cells has ratio 100%, skips.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _md("a"), _md("b"), _md("c"), - ]) - result = enrich_notebook(nb_path, min_ratio=40.0) - assert result["status"] == "skip" - - def test_enrich_with_multiple_outputs(self, tmp_path): - """Multiple code cells with outputs all get enriched.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("x = 1", outputs=[{"output_type": "stream"}]), - _code("y = 2", outputs=[{"output_type": "stream"}]), - ]) - result = enrich_notebook(nb_path, min_ratio=10.0) - assert result["cells_added"] == 2 - - def test_works_backwards_insertion(self, tmp_path): - """Cells are inserted from end to start (reversed) to preserve indices.""" - nb_path = _write_nb(tmp_path / "test.ipynb", [ - _code("a = 1", outputs=[{"output_type": "stream"}], cell_id="c1"), - _code("b = 2", outputs=[{"output_type": "stream"}], cell_id="c2"), - ]) - enrich_notebook(nb_path, min_ratio=10.0) - nb = load_notebook(nb_path) - # Should have 4 cells: code, md, code, md - assert len(nb["cells"]) == 4 - assert nb["cells"][0]["cell_type"] == "code" - assert nb["cells"][1]["cell_type"] == "markdown" - assert nb["cells"][2]["cell_type"] == "code" - assert nb["cells"][3]["cell_type"] == "markdown"