new design repo
This commit is contained in:
@@ -606,6 +606,8 @@ def start_session(
|
||||
"Avancement affiché: début de leçon\n"
|
||||
"Règle: commence directement sur cette card. Ne fais pas de reprise de la dernière séance.\n"
|
||||
)
|
||||
if assigned_lesson.get("lesson_context"):
|
||||
lesson_context += f"\nLecon complete de reference:\n{assigned_lesson['lesson_context'].strip()}\n"
|
||||
if first_section and first_section.get("context"):
|
||||
lesson_context += f"\nTexte pédagogique de la card courante:\n{first_section['context'].strip()}"
|
||||
adaptive_context = format_adaptive_kit_context(assigned_lesson)
|
||||
@@ -665,6 +667,8 @@ def chat(
|
||||
f"Avancement affiché: {payload.step_percent if payload.step_percent is not None else 'non précisé'}%\n"
|
||||
"Règle: réponds uniquement à propos de cette card et vérifie la compréhension de l'élève.\n"
|
||||
)
|
||||
if current_lesson and current_lesson.get("lesson_context"):
|
||||
lesson_context += f"\nLecon complete de reference:\n{current_lesson['lesson_context'].strip()}\n"
|
||||
if payload.section_context:
|
||||
lesson_context += f"\nTexte pédagogique de la card courante:\n{payload.section_context.strip()}"
|
||||
adaptive_context = format_adaptive_kit_context(current_lesson)
|
||||
|
||||
@@ -85,8 +85,12 @@ def build_section_url(relative_path: str, section_index: int) -> str:
|
||||
|
||||
|
||||
def find_section_context_path(svg_path: Path, asset_number: int, section_number: int) -> Path | None:
|
||||
fiche_slug = svg_path.parent.name if svg_path.name == "fiche.svg" else svg_path.stem
|
||||
lesson_dir = svg_path.parents[2] if svg_path.name == "fiche.svg" else svg_path.parent.parent
|
||||
canonical_context_dir = lesson_dir / "cards" / fiche_slug
|
||||
context_dir = svg_path.parent.parent / "card_contexts" / svg_path.stem
|
||||
candidates = [
|
||||
canonical_context_dir / f"card_{section_number:02d}.md",
|
||||
context_dir / f"Fiche{asset_number}Card{section_number}.md",
|
||||
context_dir / f"fiche_{asset_number:02d}_card_{section_number:02d}.md",
|
||||
context_dir / f"card_{section_number:02d}.md",
|
||||
@@ -94,6 +98,13 @@ def find_section_context_path(svg_path: Path, asset_number: int, section_number:
|
||||
return next((candidate for candidate in candidates if candidate.exists()), None)
|
||||
|
||||
|
||||
def find_section_svg_path(svg_path: Path, section_number: int) -> Path | None:
|
||||
fiche_slug = svg_path.parent.name if svg_path.name == "fiche.svg" else svg_path.stem
|
||||
lesson_dir = svg_path.parents[2] if svg_path.name == "fiche.svg" else svg_path.parent.parent
|
||||
candidate = lesson_dir / "cards" / fiche_slug / f"card_{section_number:02d}.svg"
|
||||
return candidate if candidate.exists() else None
|
||||
|
||||
|
||||
def svg_attr(attrs: str, name: str, default: float | None = None) -> float | None:
|
||||
match = re.search(rf'\b{name}="(?P<value>[-\d.]+)"', attrs)
|
||||
if match:
|
||||
@@ -126,11 +137,14 @@ def extract_svg_sections(svg_path: Path, asset_number: int | None = None) -> lis
|
||||
margin = 28
|
||||
section_number = len(sections) + 1
|
||||
context_path = find_section_context_path(svg_path, asset_number or 1, section_number)
|
||||
section_svg_path = find_section_svg_path(svg_path, section_number)
|
||||
section_relative = section_svg_path.relative_to(CONTENT_ROOT).as_posix() if section_svg_path else ""
|
||||
sections.append(
|
||||
{
|
||||
"index": len(sections),
|
||||
"title": title,
|
||||
"context_key": f"Fiche{asset_number or 1}Card{section_number}",
|
||||
"path": section_relative,
|
||||
"context": context_path.read_text(encoding="utf-8", errors="ignore").strip()
|
||||
if context_path
|
||||
else "",
|
||||
@@ -188,23 +202,43 @@ def render_svg_section(path: Path, section_index: int) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def discover_lesson_dirs() -> list[Path]:
|
||||
lesson_dirs: set[Path] = set()
|
||||
for svg_dir in CONTENT_ROOT.glob("cycle_*/*/*/**/svg"):
|
||||
if svg_dir.is_dir() and "kit_adaptatif" not in svg_dir.relative_to(CONTENT_ROOT).parts:
|
||||
lesson_dirs.add(svg_dir.parent)
|
||||
for fiches_dir in CONTENT_ROOT.glob("cycle_*/*/*/**/fiches"):
|
||||
if fiches_dir.is_dir() and list(fiches_dir.glob("*/fiche.svg")):
|
||||
lesson_dirs.add(fiches_dir.parent)
|
||||
return sorted(lesson_dirs)
|
||||
|
||||
|
||||
def lesson_fiche_svg_paths(lesson_dir: Path) -> list[Path]:
|
||||
canonical = sorted((lesson_dir / "fiches").glob("*/fiche.svg"))
|
||||
if canonical:
|
||||
return canonical
|
||||
return sorted((lesson_dir / "svg").glob("*.svg"))
|
||||
|
||||
|
||||
def fiche_markdown_for_svg(svg_path: Path) -> Path | None:
|
||||
if svg_path.name == "fiche.svg":
|
||||
candidate = svg_path.with_name("fiche.md")
|
||||
return candidate if candidate.exists() else None
|
||||
candidate = svg_path.parent.parent / f"{svg_path.stem}.md"
|
||||
return candidate if candidate.exists() else None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def list_lessons() -> list[dict]:
|
||||
lessons: list[dict] = []
|
||||
if not CONTENT_ROOT.exists():
|
||||
return lessons
|
||||
|
||||
for svg_dir in sorted(CONTENT_ROOT.glob("cycle_*/*/*/**/svg")):
|
||||
if not svg_dir.is_dir():
|
||||
continue
|
||||
if "kit_adaptatif" in svg_dir.relative_to(CONTENT_ROOT).parts:
|
||||
continue
|
||||
|
||||
svg_files = sorted(svg_dir.glob("*.svg"))
|
||||
for lesson_dir in discover_lesson_dirs():
|
||||
svg_files = lesson_fiche_svg_paths(lesson_dir)
|
||||
if not svg_files:
|
||||
continue
|
||||
|
||||
lesson_dir = svg_dir.parent
|
||||
relative_lesson = lesson_dir.relative_to(CONTENT_ROOT).as_posix()
|
||||
parts = relative_lesson.split("/")
|
||||
if len(parts) < 4:
|
||||
@@ -228,16 +262,21 @@ def list_lessons() -> list[dict]:
|
||||
for asset_index, svg_path in enumerate(svg_files, start=1):
|
||||
relative_path = svg_path.relative_to(CONTENT_ROOT).as_posix()
|
||||
sections = extract_svg_sections(svg_path, asset_index)
|
||||
fiche_md = fiche_markdown_for_svg(svg_path)
|
||||
fiche_slug = svg_path.parent.name if svg_path.name == "fiche.svg" else svg_path.stem
|
||||
assets.append(
|
||||
{
|
||||
"name": svg_path.stem,
|
||||
"title": title_from_slug(svg_path.stem),
|
||||
"name": fiche_slug,
|
||||
"title": title_from_slug(fiche_slug),
|
||||
"path": relative_path,
|
||||
"url": build_asset_url(relative_path),
|
||||
"content": read_review_text(fiche_md) if fiche_md else "",
|
||||
"sections": [
|
||||
{
|
||||
**section,
|
||||
"url": build_section_url(relative_path, section["index"]),
|
||||
"url": build_asset_url(section["path"])
|
||||
if section.get("path")
|
||||
else build_section_url(relative_path, section["index"]),
|
||||
}
|
||||
for section in sections
|
||||
],
|
||||
@@ -253,6 +292,7 @@ def list_lessons() -> list[dict]:
|
||||
"cycle": cycle,
|
||||
"asset_count": len(assets),
|
||||
"assets": assets,
|
||||
"lesson_context": read_review_text(lesson_dir / "lecon.md") if (lesson_dir / "lecon.md").exists() else "",
|
||||
"adaptive_kit": collect_adaptive_kit(lesson_dir),
|
||||
}
|
||||
)
|
||||
@@ -366,17 +406,28 @@ def apply_program_review_updates() -> dict:
|
||||
|
||||
def collect_lesson_status(lesson: dict) -> dict:
|
||||
lesson_dir = CONTENT_ROOT / lesson["id"]
|
||||
kit_dir = lesson_dir / "kit_adaptatif"
|
||||
legacy_kit_dir = lesson_dir / "kit_adaptatif"
|
||||
fiche_md = list((lesson_dir / "fiches").glob("*/fiche.md"))
|
||||
fiche_svg = list((lesson_dir / "fiches").glob("*/fiche.svg"))
|
||||
card_md = list((lesson_dir / "cards").glob("*/*.md"))
|
||||
card_svg = list((lesson_dir / "cards").glob("*/*.svg"))
|
||||
tests_dir = lesson_dir / "tests"
|
||||
parcours_dir = lesson_dir / "exercices" / "parcours_adaptes"
|
||||
return {
|
||||
"micro_fiches": len([path for path in lesson_dir.glob("*.md") if path.name.startswith(tuple("0123456789"))]),
|
||||
"lesson_svg": len(lesson.get("assets") or []),
|
||||
"cards": sum(len(asset.get("sections") or []) for asset in lesson.get("assets") or []),
|
||||
"contexts": len(list((lesson_dir / "card_contexts").rglob("Fiche*Card*.md"))),
|
||||
"lesson_md": 1 if (lesson_dir / "lecon.md").exists() else 0,
|
||||
"micro_fiches": len(fiche_md) or len([path for path in lesson_dir.glob("*.md") if path.name.startswith(tuple("0123456789"))]),
|
||||
"lesson_svg": len(fiche_svg) or len(lesson.get("assets") or []),
|
||||
"cards": len(card_svg) or sum(len(asset.get("sections") or []) for asset in lesson.get("assets") or []),
|
||||
"contexts": len(card_md) or len(list((lesson_dir / "card_contexts").rglob("Fiche*Card*.md"))),
|
||||
"text_exercises": len(list((lesson_dir / "exercices").rglob("exercices.md"))),
|
||||
"exercise_svg": len(list((lesson_dir / "exercices_svg").glob("*.svg"))),
|
||||
"adaptive_tests": len([path for path in (kit_dir / "test_diagnostic").glob("*.md") if path.name != "README.md"]) if kit_dir.exists() else 0,
|
||||
"adaptive_profiles": len([path for path in (kit_dir / "parcours_profils").glob("*.md") if path.name != "README.md"]) if kit_dir.exists() else 0,
|
||||
"adaptive_svg": len(list((kit_dir / "svg").glob("*.svg"))) if kit_dir.exists() else 0,
|
||||
"exercise_svg": len(list((lesson_dir / "exercices").rglob("exercices.svg"))) or len(list((lesson_dir / "exercices_svg").glob("*.svg"))),
|
||||
"tests": len([path for path in tests_dir.glob("*.md") if path.name != "README.md"])
|
||||
if tests_dir.exists()
|
||||
else len([path for path in (legacy_kit_dir / "test_diagnostic").glob("*.md") if path.name != "README.md"]) if legacy_kit_dir.exists() else 0,
|
||||
"test_svg": len(list(tests_dir.glob("*.svg"))) if tests_dir.exists() else len(list((legacy_kit_dir / "svg").glob("*.svg"))) if legacy_kit_dir.exists() else 0,
|
||||
"adaptive_profiles": len([path for path in parcours_dir.glob("*.md") if path.name != "README.md"])
|
||||
if parcours_dir.exists()
|
||||
else len([path for path in (legacy_kit_dir / "parcours_profils").glob("*.md") if path.name != "README.md"]) if legacy_kit_dir.exists() else 0,
|
||||
"validations": sorted(path.stem for path in lesson_dir.glob("VALIDATION_PHASE_*.md")),
|
||||
}
|
||||
|
||||
@@ -397,12 +448,13 @@ def read_review_text(path: Path, max_chars: int = 6000) -> str:
|
||||
|
||||
|
||||
def collect_adaptive_kit(lesson_dir: Path) -> dict | None:
|
||||
kit_dir = lesson_dir / "kit_adaptatif"
|
||||
if not kit_dir.exists():
|
||||
tests_dir = lesson_dir / "tests"
|
||||
parcours_dir = lesson_dir / "exercices" / "parcours_adaptes"
|
||||
legacy_kit_dir = lesson_dir / "kit_adaptatif"
|
||||
if not tests_dir.exists() and not parcours_dir.exists() and not legacy_kit_dir.exists():
|
||||
return None
|
||||
|
||||
def markdown_items(folder: str) -> list[dict]:
|
||||
base = kit_dir / folder
|
||||
def markdown_items(base: Path) -> list[dict]:
|
||||
if not base.exists():
|
||||
return []
|
||||
return [
|
||||
@@ -415,24 +467,34 @@ def collect_adaptive_kit(lesson_dir: Path) -> dict | None:
|
||||
if path.name != "README.md"
|
||||
]
|
||||
|
||||
supports = []
|
||||
svg_dir = kit_dir / "svg"
|
||||
if svg_dir.exists():
|
||||
for path in sorted(svg_dir.glob("*.svg")):
|
||||
def svg_items(base: Path) -> list[dict]:
|
||||
if not base.exists():
|
||||
return []
|
||||
items = []
|
||||
for path in sorted(base.glob("*.svg")):
|
||||
relative = path.relative_to(CONTENT_ROOT).as_posix()
|
||||
supports.append(
|
||||
items.append(
|
||||
{
|
||||
"id": relative,
|
||||
"title": title_from_slug(path.stem),
|
||||
"url": build_asset_url(relative),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
diagnostics = markdown_items(tests_dir)
|
||||
profiles = markdown_items(parcours_dir)
|
||||
supports = svg_items(tests_dir) + svg_items(parcours_dir)
|
||||
if legacy_kit_dir.exists():
|
||||
diagnostics = diagnostics or markdown_items(legacy_kit_dir / "test_diagnostic")
|
||||
profiles = profiles or markdown_items(legacy_kit_dir / "parcours_profils")
|
||||
supports = supports or svg_items(legacy_kit_dir / "svg")
|
||||
|
||||
return {
|
||||
"id": kit_dir.relative_to(CONTENT_ROOT).as_posix(),
|
||||
"title": "Kit adaptatif",
|
||||
"diagnostics": markdown_items("test_diagnostic"),
|
||||
"profiles": markdown_items("parcours_profils"),
|
||||
"id": lesson_dir.relative_to(CONTENT_ROOT).as_posix(),
|
||||
"title": "Tests et exercices adaptes",
|
||||
"diagnostics": diagnostics,
|
||||
"profiles": profiles,
|
||||
"supports": supports,
|
||||
}
|
||||
|
||||
@@ -445,7 +507,7 @@ def format_adaptive_kit_context(lesson: dict | None) -> str:
|
||||
diagnostics = ", ".join(item["title"] for item in kit.get("diagnostics") or [])
|
||||
profiles = ", ".join(item["title"] for item in kit.get("profiles") or [])
|
||||
return (
|
||||
"Kit adaptatif disponible pour cette leçon.\n"
|
||||
"Tests et exercices adaptes disponibles pour cette leçon.\n"
|
||||
f"Tests diagnostics: {diagnostics or 'aucun'}.\n"
|
||||
f"Parcours de reprise: {profiles or 'aucun'}.\n"
|
||||
"Règle adaptative: si l'élève se trompe nettement, semble bloqué, inverse les notions ou demande de l'aide, "
|
||||
@@ -485,13 +547,15 @@ def build_program_tree() -> dict:
|
||||
lesson_node["status"] = collect_lesson_status(lesson)
|
||||
lesson_node["review"] = reviews.get(lesson["id"], {})
|
||||
lesson_node["assets"] = lesson.get("assets") or []
|
||||
lesson_node["content"] = lesson.get("lesson_context") or ""
|
||||
for asset in lesson.get("assets") or []:
|
||||
asset_key = asset["path"]
|
||||
asset_node = append_tree_child(lesson_node, asset_key, asset["title"], "fiche", asset_key)
|
||||
asset_node["url"] = asset.get("url")
|
||||
asset_node["content"] = asset.get("content") or ""
|
||||
asset_node["review"] = reviews.get(asset_key, {})
|
||||
for section in asset.get("sections") or []:
|
||||
section_key = f"{asset_key}#{section.get('context_key') or section.get('index')}"
|
||||
section_key = section.get("path") or f"{asset_key}#{section.get('context_key') or section.get('index')}"
|
||||
section_node = append_tree_child(
|
||||
asset_node,
|
||||
section_key,
|
||||
@@ -500,36 +564,43 @@ def build_program_tree() -> dict:
|
||||
section_key,
|
||||
)
|
||||
section_node["url"] = section.get("url")
|
||||
section_node["content"] = section.get("context") or ""
|
||||
section_node["review"] = reviews.get(section_key, {})
|
||||
lesson_dir = CONTENT_ROOT / lesson["id"]
|
||||
kit_dir = lesson_dir / "kit_adaptatif"
|
||||
if kit_dir.exists():
|
||||
kit_key = f"{lesson['id']}/kit_adaptatif"
|
||||
kit_node = append_tree_child(lesson_node, kit_key, "Kit adaptatif", "kit", kit_key)
|
||||
kit_node["review"] = reviews.get(kit_key, {})
|
||||
|
||||
tests_node = append_tree_child(kit_node, f"{kit_key}/test_diagnostic", "Tests diagnostics", "group", f"{kit_key}/test_diagnostic")
|
||||
for test_path in sorted((kit_dir / "test_diagnostic").glob("*.md")):
|
||||
if test_path.name == "README.md":
|
||||
exercises_dir = lesson_dir / "exercices"
|
||||
if exercises_dir.exists():
|
||||
exercises_node = append_tree_child(lesson_node, f"{lesson['id']}/exercices", "Exercices", "group", f"{lesson['id']}/exercices")
|
||||
for exercise_md in sorted(exercises_dir.rglob("*.md")):
|
||||
if exercise_md.name == "README.md":
|
||||
continue
|
||||
relative = test_path.relative_to(CONTENT_ROOT).as_posix()
|
||||
test_node = append_tree_child(tests_node, relative, title_from_slug(test_path.stem), "diagnostic", relative)
|
||||
test_node["content"] = read_review_text(test_path)
|
||||
relative = exercise_md.relative_to(CONTENT_ROOT).as_posix()
|
||||
exercise_node = append_tree_child(exercises_node, relative, title_from_slug(exercise_md.parent.name), "exercice", relative)
|
||||
exercise_node["content"] = read_review_text(exercise_md)
|
||||
exercise_svg = exercise_md.with_suffix(".svg") if exercise_md.stem != "exercices" else exercise_md.with_name("exercices.svg")
|
||||
if exercise_svg.exists():
|
||||
exercise_node["url"] = build_asset_url(exercise_svg.relative_to(CONTENT_ROOT).as_posix())
|
||||
exercise_node["review"] = reviews.get(relative, {})
|
||||
for exercise_svg in sorted(exercises_dir.rglob("*.svg")):
|
||||
if exercise_svg.with_suffix(".md").exists() or (exercise_svg.name == "exercices.svg" and (exercise_svg.parent / "exercices.md").exists()):
|
||||
continue
|
||||
relative = exercise_svg.relative_to(CONTENT_ROOT).as_posix()
|
||||
support_node = append_tree_child(exercises_node, relative, title_from_slug(exercise_svg.stem), "support", relative)
|
||||
support_node["url"] = build_asset_url(relative)
|
||||
support_node["review"] = reviews.get(relative, {})
|
||||
|
||||
tests_dir = lesson_dir / "tests"
|
||||
if tests_dir.exists():
|
||||
tests_node = append_tree_child(lesson_node, f"{lesson['id']}/tests", "Tests", "group", f"{lesson['id']}/tests")
|
||||
for test_md in sorted(tests_dir.glob("*.md")):
|
||||
if test_md.name == "README.md":
|
||||
continue
|
||||
relative = test_md.relative_to(CONTENT_ROOT).as_posix()
|
||||
test_node = append_tree_child(tests_node, relative, title_from_slug(test_md.stem), "test", relative)
|
||||
test_node["content"] = read_review_text(test_md)
|
||||
test_node["review"] = reviews.get(relative, {})
|
||||
|
||||
profiles_node = append_tree_child(kit_node, f"{kit_key}/parcours_profils", "Parcours profils", "group", f"{kit_key}/parcours_profils")
|
||||
for profile_path in sorted((kit_dir / "parcours_profils").glob("*.md")):
|
||||
if profile_path.name == "README.md":
|
||||
continue
|
||||
relative = profile_path.relative_to(CONTENT_ROOT).as_posix()
|
||||
profile_node = append_tree_child(profiles_node, relative, title_from_slug(profile_path.stem), "parcours", relative)
|
||||
profile_node["content"] = read_review_text(profile_path)
|
||||
profile_node["review"] = reviews.get(relative, {})
|
||||
|
||||
svg_node = append_tree_child(kit_node, f"{kit_key}/svg", "Supports visuels", "group", f"{kit_key}/svg")
|
||||
for svg_path in sorted((kit_dir / "svg").glob("*.svg")):
|
||||
relative = svg_path.relative_to(CONTENT_ROOT).as_posix()
|
||||
support_node = append_tree_child(svg_node, relative, title_from_slug(svg_path.stem), "support", relative)
|
||||
for test_svg in sorted(tests_dir.glob("*.svg")):
|
||||
relative = test_svg.relative_to(CONTENT_ROOT).as_posix()
|
||||
support_node = append_tree_child(tests_node, relative, title_from_slug(test_svg.stem), "support", relative)
|
||||
support_node["url"] = build_asset_url(relative)
|
||||
support_node["review"] = reviews.get(relative, {})
|
||||
return root
|
||||
|
||||
Reference in New Issue
Block a user