Escape some newlines in question extraction
This commit is contained in:
1 parent
e4e25eca55
commit
4c56b655fe
3 files changed
+42
-16
No files matched your search
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from copienator import configuration as config
|
||||
from copienator import utils
|
||||
@@ -22,7 +22,7 @@ from copienator import (
|
||||
workspace_from_args,
|
||||
)
|
||||
from copienator.platform import validate_windows_labels
|
||||
from copienator.latex_json import repair_latex_json
|
||||
from copienator.latex_json import repair_latex_json, normalize_literal_newlines
|
||||
from copienator.filesystem import staged_directory
|
||||
from copienator.utils import compile_to_pdf
|
||||
|
||||
@@ -43,7 +43,15 @@ MODEL_ID = config.MODEL_LITE_ID
|
||||
api_key = config.API_KEY
|
||||
|
||||
# --- Modèles pour la Requête 1 ---
|
||||
class QuestionOnlyItem(BaseModel):
|
||||
class StatementContent(BaseModel):
|
||||
@field_validator("question_content", "solution_content", "context_content",
|
||||
"rubric_content", "content", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_newlines(cls, value: str) -> str:
|
||||
return normalize_literal_newlines(value)
|
||||
|
||||
|
||||
class QuestionOnlyItem(StatementContent):
|
||||
label: str = Field(description="Label unique de la question (par exemple '1.a' ou 'Exercice 1').")
|
||||
question_content: str = Field(description="Texte source de la question, extrait exactement du fichier d’énoncé, SANS le label lui-même.")
|
||||
|
||||
@@ -51,7 +59,7 @@ class ExamQuestions(BaseModel):
|
||||
questions: list[QuestionOnlyItem]
|
||||
|
||||
# --- Modèles pour la Requête 2 ---
|
||||
class SolutionOnlyItem(BaseModel):
|
||||
class SolutionOnlyItem(StatementContent):
|
||||
label: str = Field(description="Label exact de la question fourni en entrée, à conserver sans traduction.")
|
||||
solution_content: str = Field(description="Texte source de la solution, extrait exactement du fichier de correction.")
|
||||
|
||||
@@ -59,7 +67,7 @@ class ExamSolutions(BaseModel):
|
||||
solutions: list[SolutionOnlyItem]
|
||||
|
||||
# --- Modèles pour la Requête 3 ---
|
||||
class ExtractedContext(BaseModel):
|
||||
class ExtractedContext(StatementContent):
|
||||
target_question_label: str = Field(description="Label exact de la PREMIÈRE question située immédiatement APRÈS cette information dans l’énoncé.")
|
||||
last_question_label: str = Field(description="Label exact de la DERNIÈRE question qui utilise cette information.")
|
||||
context_content: str = Field(description="Texte source des définitions, notations ou hypothèses, extrait de l’énoncé.")
|
||||
@@ -68,7 +76,7 @@ class ExamContext(BaseModel):
|
||||
contexts: list[ExtractedContext]
|
||||
|
||||
# --- Modèles pour la Requête 4 (Barèmes) ---
|
||||
class RubricItem(BaseModel):
|
||||
class RubricItem(StatementContent):
|
||||
label: str = Field(description="Label exact de la question, à conserver sans traduction.")
|
||||
rubric_content: str = Field(description="Barème détaillé sur 4 points, en français, avec formules LaTeX. Échapper chaque antislash LaTeX conformément à la syntaxe JSON.")
|
||||
|
||||
@@ -110,6 +118,8 @@ Exemple d’échappement dans un barème (adapte les clés au schéma demandé)
|
||||
Ne confonds pas les commandes LaTeX avec les échappements JSON :
|
||||
\\frac, \\rightarrow, \\not, \\theta et \\begin sont des commandes LaTeX ;
|
||||
\n est réservé à un véritable saut de ligne du texte.
|
||||
N'écris jamais \\n pour un saut de ligne : après décodage JSON, le texte
|
||||
doit contenir un véritable saut de ligne, pas une commande LaTeX \n.
|
||||
"""
|
||||
|
||||
|
||||
@@ -124,12 +134,12 @@ def validate_rubric_content(content: str) -> str:
|
||||
return content
|
||||
|
||||
# --- Modèle fusionné (pour le reste du script) ---
|
||||
class QuestionItem(BaseModel):
|
||||
class QuestionItem(StatementContent):
|
||||
label: str
|
||||
question_content: str
|
||||
solution_content: str
|
||||
|
||||
class ContextItem(BaseModel):
|
||||
class ContextItem(StatementContent):
|
||||
target_question_label: str
|
||||
last_question_label: str
|
||||
content: str # Juste une string encapsulée pour le différencier facilement
|
||||
@@ -915,7 +925,7 @@ def process_exam(
|
||||
with open(text_dir / safe_group_filename, "w", encoding="utf-8") as f_text:
|
||||
f_text.write("\n".join(text_content_lines))
|
||||
|
||||
print(f"Success! Processed {len(grouped_extraction.groups)} groups.")
|
||||
print(f"Wrote {len(grouped_extraction.groups)} groups; checking PDF compilation...")
|
||||
# ==========================================
|
||||
# PDF COMPILATION (4 Threads)
|
||||
# ==========================================
|
||||
@@ -926,7 +936,7 @@ def process_exam(
|
||||
try:
|
||||
content = tex_file.read_text(encoding="utf-8")
|
||||
pdf_path = tex_file.with_suffix(".pdf")
|
||||
compile_to_pdf(content, pdf_path)
|
||||
compile_to_pdf(content, pdf_path, strict=True)
|
||||
except Exception as e: # noqa: BLE001 - compiler worker boundary
|
||||
return f"Error compiling {tex_file.name}: {e}"
|
||||
return None
|
||||
|
||||
Reference in new issue
Block a user