Flux correction
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,6 +19,7 @@ from .auth import (
|
||||
)
|
||||
from .curriculum import QUESTIONS
|
||||
from .program_content import (
|
||||
apply_program_review_updates,
|
||||
build_program_tree,
|
||||
decode_asset_token,
|
||||
format_adaptive_kit_context,
|
||||
@@ -52,6 +53,7 @@ async def lifespan(app: FastAPI):
|
||||
seed_admin_user(db)
|
||||
finally:
|
||||
db.close()
|
||||
apply_program_review_updates()
|
||||
yield
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ def default_content_root() -> Path:
|
||||
CONTENT_ROOT = Path(os.getenv("PROGRAM_CONTENT_ROOT", default_content_root())).resolve()
|
||||
LESSON_BACKGROUND = "#f8fafc"
|
||||
REVIEW_STATE_PATH = Path(os.getenv("PROGRAM_REVIEW_STATE_PATH", CONTENT_ROOT / ".program_review_state.json"))
|
||||
REVIEW_UPDATES_PATH = Path(os.getenv("PROGRAM_REVIEW_UPDATES_PATH", CONTENT_ROOT / "review_updates"))
|
||||
|
||||
GRADE_ALIASES = {
|
||||
"CM1": "cm1",
|
||||
@@ -298,6 +299,71 @@ def save_review_state(state: dict) -> None:
|
||||
)
|
||||
|
||||
|
||||
def iter_review_update_files() -> list[Path]:
|
||||
candidates = []
|
||||
single_file = CONTENT_ROOT / ".program_review_updates.json"
|
||||
if single_file.exists():
|
||||
candidates.append(single_file)
|
||||
if REVIEW_UPDATES_PATH.exists():
|
||||
candidates.extend(sorted(REVIEW_UPDATES_PATH.glob("*.json")))
|
||||
return candidates
|
||||
|
||||
|
||||
def apply_program_review_updates() -> dict:
|
||||
state = load_review_state()
|
||||
applied = 0
|
||||
skipped = 0
|
||||
errors = []
|
||||
|
||||
for path in iter_review_update_files():
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append({"file": str(path), "error": f"JSON invalide: {exc}"})
|
||||
continue
|
||||
|
||||
if payload.get("schema") != "professeur-top-review-updates-v1":
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
payload_updated_at = payload.get("updated_at") or datetime.utcnow().isoformat(timespec="seconds") + "Z"
|
||||
payload_reviewer = payload.get("updated_by") or "codex"
|
||||
for index, update in enumerate(payload.get("updates") or []):
|
||||
node_key = update.get("node_key") or update.get("path")
|
||||
if not node_key:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
update_id = update.get("update_id") or f"{path.name}:{index}:{node_key}"
|
||||
existing = state.get(node_key, {})
|
||||
if existing.get("codex_update_id") == update_id:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
next_review = {
|
||||
**existing,
|
||||
"status": update.get("status") or existing.get("status") or "treated",
|
||||
"comment": update.get("comment", existing.get("comment", "")),
|
||||
"reviewer": update.get("reviewer") or payload_reviewer,
|
||||
"updated_at": update.get("updated_at") or payload_updated_at,
|
||||
"codex_update_id": update_id,
|
||||
}
|
||||
if update.get("treated_note"):
|
||||
next_review["treated_note"] = update["treated_note"]
|
||||
state[node_key] = next_review
|
||||
applied += 1
|
||||
|
||||
if applied:
|
||||
save_review_state(state)
|
||||
|
||||
return {
|
||||
"applied": applied,
|
||||
"skipped": skipped,
|
||||
"errors": errors,
|
||||
"files": [str(path) for path in iter_review_update_files()],
|
||||
}
|
||||
|
||||
|
||||
def collect_lesson_status(lesson: dict) -> dict:
|
||||
lesson_dir = CONTENT_ROOT / lesson["id"]
|
||||
kit_dir = lesson_dir / "kit_adaptatif"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<circle cx="725" cy="530" r="10" fill="#16a34a"/>
|
||||
<text x="190" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">0</text>
|
||||
<text x="893" y="570" font-family="Inter, Arial, sans-serif" font-size="20" fill="#1f2937">1</text>
|
||||
<text x="725" y="486" text-anchor="middle" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">3</text>
|
||||
<line x1="708" y1="496" x2="742" y2="496" stroke="#166534" stroke-width="3"/>
|
||||
<text x="725" y="522" text-anchor="middle" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">4</text>
|
||||
<text x="725" y="458" text-anchor="middle" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">3</text>
|
||||
<line x1="708" y1="468" x2="742" y2="468" stroke="#166534" stroke-width="3"/>
|
||||
<text x="725" y="494" text-anchor="middle" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#166534">4</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"schema": "professeur-top-review-updates-v1",
|
||||
"updated_by": "codex",
|
||||
"updated_at": "2026-05-07T19:55:00Z",
|
||||
"updates": [
|
||||
{
|
||||
"update_id": "codex-2026-05-07-fractions-kit-parcours-fraction-support",
|
||||
"node_key": "cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01B_fractions/kit_adaptatif/svg/parcours_fraction_support.svg",
|
||||
"status": "treated",
|
||||
"comment": "Aucune fraction ne doit etre representee avec un trait oblique, il faut un trait horizontal avec les enfants de cet age",
|
||||
"reviewer": "codex",
|
||||
"updated_at": "2026-05-07T19:55:00Z",
|
||||
"treated_note": "Fractions visibles remplacees par une disposition numerateur, barre horizontale, denominateur. Etiquette de la demi-droite remontee au-dessus du point vert."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user