Skip to content

English · Español

Lab 01 — Escribe scripts/gen_corpus.py

Objetivo: implementa el generador de corpus. Enumera las 20 × 6 × 3 = 360 celdas de formas correctas con pares en español, más emite mis-conjugaciones de la taxonomía cerrada. La salida es data/raw/all_rows.jsonl + un manifest parcial.

Tiempo estimado: 4–6 horas.

Prerrequisito: lab 00 commiteado (data/corpus_spec.md).


Lo que produces

  • scripts/gen_corpus.py — punto de entrada. Lee la tabla de verbos desde src/minicorpus/verb_table.py. Emite data/raw/all_rows.jsonl.
  • src/minicorpus/__init__.py — re-exports.
  • src/minicorpus/verb_table.py — la tabla de 20 verbos como estructuras de datos Python (sin lógica).
  • src/minicorpus/conjugate.py — funciones puras: conjugate_english(verb, tense, person, table) -> str y el equivalente en español.
  • src/minicorpus/mis_conjugate.py — dada una forma correcta, produce una forma mis-conjugada para un tipo dado.
  • tests/test_conjugate.py — tests unitarios sobre un puñado de tuplas (verbo, tiempo, persona).

El generador no es un script monolítico — es un paquete pequeño. El script orquesta; el paquete conjuga.

TODOs

Bloque A — src/minicorpus/verb_table.py

  • Define VERB_TABLE: dict[str, VerbEntry] con una entrada por cada uno de los 20 verbos.
  • VerbEntry es un frozen dataclass con: english_lemma, spanish_lemma, english_regularity, spanish_regularity, english_past, english_participle, spanish_irregular_forms: dict[(tense, person), str] (sparse — solo rellena celdas irregulares; las celdas regulares se computan desde el lema).
  • Para cada uno de los 8 irregulares en inglés, puebla english_past y english_participle.
  • Para cada forma irregular en español, puebla spanish_irregular_forms.
  • Añade SPANISH_PRONOUNS: dict[person, str] mapeando 1sg → "yo", 2sg → "tú", 3sg → "él".
  • Añade ENGLISH_PRONOUNS: dict[person, str] mapeando 1sg → "I", 2sg → "you", 3sg → "he".

Bloque B — src/minicorpus/conjugate.py

  • conjugate_english(verb_lemma: str, tense: str, person: str, table=VERB_TABLE) -> str. Función pura. Busca la entrada, computa la forma superficial según las reglas en data/corpus_spec.md.
  • Maneja las 6 formas superficiales de tiempo:
  • infinitive: to <lemma> (o solo <lemma> — escoge la convención de la spec; documenta).
  • present_simple: <pronoun> <lemma> para 1sg/2sg; <pronoun> <lemma+s> para 3sg. Maneja -es para verbos que terminan en -sh, -ch, -s, -x, -o (watch → watches, study → studies vía -y → -ies).
  • past_simple: regulares obtienen <lemma+ed> (con -y → -ied para study); irregulares usan entry.english_past. El past de be tiene variantes por persona (was/were/was).
  • past_participle: regulares igual que past simple; irregulares usan entry.english_participle. Desnudo (sin pronombre).
  • future_will: <pronoun> will <lemma>.
  • future_going_to: <pronoun> <to-be-conjugated-for-person> going to <lemma>. P. ej., I am going to work, you are going to work, he is going to work.
  • conjugate_spanish(verb_lemma: str, tense: str, person: str, table=VERB_TABLE) -> str. Misma idea para español. Maneja patrones regulares -ar/-er/-ir; busca irregulares desde entry.spanish_irregular_forms.
  • Caso especial like → gustar con sintaxis invertida: en lugar de <yo/tú/él> <gustar-conjugation>, emite <me/te/le> <gusta> (3sg siempre) — documenta esta excepción en la función.

Bloque C — src/minicorpus/mis_conjugate.py

  • Para cada tipo de mis-conjugación, define una función apply_<type>(correct_form, verb_entry, tense, person) -> Optional[str].
  • Cada función:
  • Retorna None si el tipo no aplica a esta celda (p. ej., missing_third_person_s retorna None para celdas no-3sg, no-present-simple).
  • De lo contrario, retorna la forma desviante.
  • Las 6 funciones de mis-conjugación:
  • apply_missing_third_person_s(form, entry, tense, person) — quita la -s de 3sg present-simple. he works → he work.
  • apply_overregularization_past(form, entry, tense, person) — para verbos irregulares en past_simple o past_participle, genera la forma "qué pasaría si fuera regular". he went → he goed. eaten → eated.
  • apply_wrong_aux_will_with_to(form, entry, tense, person) — para future_will, inserta to tras will. he will work → he will to work.
  • apply_wrong_aux_going_to_missing_ing(form, entry, tense, person) — para future_going_to, cambia going por go. I am going to work → I am go to work.
  • apply_subject_verb_disagreement(form, entry, tense, person) — forma auxiliar incorrecta. Para you have worked, cambia a you has worked. Para (you, be, past_simple) you were → you was.
  • apply_bare_participle_missing_aux(form, entry, tense, person) — para past_participle, prepende un pronombre incorrecto + sin aux. gone → he gone (la forma "correcta" esperada sería he has gone).

  • Helper eligible_mis_conjugations(entry, tense, person) -> list[str] retorna los nombres de tipos que podrían aplicar a esta celda.

Bloque D — scripts/gen_corpus.py

  • CLI: argparse con --seed (default 42), --output (default data/raw/).
  • Llama seed_everything(args.seed).
  • Enumera las 20 verbos × 6 formas superficiales de tiempo × 3 personas = 360 celdas.
  • Para cada celda, emite una fila correct con text, spanish, todos los campos del esquema, label="correct", mis_conjugation_type=null, correct_form=null.
  • Dentro de cada celda, consulta eligible_mis_conjugations; si no está vacío, selecciona aleatoriamente 0–2 (condicionado por un coin flip basado en RNG) y emite cada una como una fila mis_conjugated con correct_form poblado.
  • Asigna id secuencial (con zero-padding a 4 dígitos).
  • Computa fingerprint = sha256(NFC(text)) por fila.
  • Verifica que la fila pase el JSONSchema antes de escribir (verificación ligera en-proceso; el validador completo es lab 02).
  • Escribe a data/raw/all_rows.jsonl.
  • Emite un manifest parcial (data/raw/MANIFEST_partial.json) con seed, sellos de versión y conteo total de filas.

Bloque E — tests/test_conjugate.py

  • Un test por cada una de las 6 formas superficiales de tiempo × 1 verbo regular × 3 personas.
  • Un test por cada una de las 6 formas superficiales de tiempo × 1 verbo irregular (go) × 3 personas.
  • Test de caso especial: (like, present_simple, 1sg) retorna I like (inglés) y me gusta (español).
  • Test de caso especial: (be, past_simple, 1sg) retorna I was; (be, past_simple, 2sg) retorna you were.
  • Test de caso especial: (study, present_simple, 3sg) retorna he studies.
  • Test de caso especial: (watch, present_simple, 3sg) retorna he watches.
  • Tests de mis-conjugación: al menos uno por cada uno de los 6 tipos, asegurando que la forma desviante sea lo que la spec dice.
  • mypy --strict src/minicorpus/ limpio.
  • ruff limpio.

Bloque F — print de cordura

  • En la parte inferior del script (o en un notebook separado en experiments/12-corpus-generation/), imprime:
  • Total de filas generadas.
  • Cobertura por celda (esperar 1 correcta + 0–2 mis-conjugadas por celda).
  • Muestrea 10 filas aleatorias para revisión humana.
  • Total de filas mis-conjugadas por tipo.

Restricciones

  • Python puro. No se necesita numpy (el corpus es texto estructurado, no numérico).
  • mypy --strict limpio.
  • ruff limpio.
  • bandit limpio.
  • Determinismo asegurado vía la fixture de semilla en tests/conftest.py.
  • Sin lógica de conjugación impulsada por regex. Usa operaciones de string explícitas + lookups de tabla. Los regex para morfología son difíciles de depurar.
  • NFC normaliza cada campo text y spanish emitido. Usa unicodedata.normalize('NFC', s).

Condiciones de parada

Hecho cuando:

  1. Los cinco archivos Python commiteados bajo src/minicorpus/ y scripts/.
  2. Todos los tests pasan; pytest -q tests/test_conjugate.py está verde.
  3. mypy --strict src/minicorpus/ limpio.
  4. python scripts/gen_corpus.py --seed 42 produce data/raw/all_rows.jsonl con al menos 460 filas (360 correctas + ~100–200 mis-conjugadas).
  5. El print de cordura de muestra se ve correcto a ojos de Borja — sin español raro, sin typos en inglés.

Escollos

  • study → studies vs study → studys. La regla -y → -ies para verbos terminados en consonante + y necesita manejo explícito.
  • watch → watches, finish → finishes. Verbos terminados en -ch, -sh, -s, -x, -z añaden -es, no -s.
  • be es únicamente irregular en present simple. I am, you are, he is. No intentes derivarlos de una regla.
  • Cambios de stem en español. querer → quiero (e → ie), empezar → empiezo. Enumera, no derives por regla.
  • Inversión de sintaxis like → gustar. El sujeto entero se convierte en objeto indirecto. Mejor caso-especial like en conjugate_spanish.
  • Mis-conjugación que es realmente correcta. apply_missing_third_person_s("I work", entry, "present_simple", "1sg") debería retornar None — no hay -s que quitar para I work. Fácil olvidar el check de elegibilidad.
  • Bytes vs codepoints en NFC. Siempre llama a unicodedata.normalize('NFC', s) sobre strings de Python (no bytes). No mezcles niveles.
  • Capitalización de I. El pronombre I en inglés es mayúscula; todo lo demás en el corpus es minúscula. No pongas en minúscula el string completo al final.

Pista de último recurso

Si llevas 5 horas y la lógica de conjugación es un desastre: simplifica enumerando más y computando menos. La tabla de verbos puede tener todas las 360 formas hardcodeadas si escribir la regla es demasiado doloroso. El tradeoff reglas-vs-lookup favorece a lookup para nuestra escala.

Cuándo consultar solutions/

Tras que todos los tests pasen. Solución: solutions/01-implement-generator-ref.md (apertura de fase). La referencia contiene la tabla de verbos completa, las funciones de conjugación y los handlers de mis-conjugación.


Siguiente lab: lab/02-validate-and-split.md.