#!/usr/bin/env python3 """Pre-traduce a EN con Haiku los posts del gap que Gemma AÚN no ha alcanzado. Crea el post EN + enlace Polylang (reutiliza fea_translate_helper.php, igual que Gemma) ANTES de que Gemma llegue. Cuando Gemma llega, ve la traducción EN ya enlazada en Polylang y la salta (translate_post.py:233), haciendo solo FR/IT/PT. Así el EN se hace UNA vez y bien, sin el reprocesado posterior. Coordinación: recorre los posts en el MISMO orden que Gemma, localiza por dónde va (último :en en el state) y arranca `--margin` posts por delante para no colisionar con el que Gemma está procesando ahora. Haiku (API) es mucho más rápido que Gemma local, así que se aleja y nunca la alcanza. Uso: pretranslate_en_haiku.py # PLAN: muestra arranque y pendientes pretranslate_en_haiku.py --apply # crea los EN opciones: --margin N (def 2), --limit N """ import argparse import json import os import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import translate_post as tp # read_post, translation_exists, create_translation, carta_article_ids from translate_haiku import translate # Haiku # Mismo orden que translate_gap.sh CARTAS = "45018 44997 44975 44230 44229 44228 44090 44089 44088 44087 44086 44085 44084 44083 42590".split() def read_state(): """Lee el state de Gemma con reintentos (lo reescribe en vivo).""" for _ in range(6): try: d = json.loads(open(tp.STATE_FILE).read()) if d.get("done"): return d except (json.JSONDecodeError, FileNotFoundError): pass time.sleep(0.5) sys.exit("No pude leer el state de Gemma con contenido; aborto por seguridad.") def build_order(): """Lista global de post_ids en el orden exacto en que Gemma los procesa.""" g = [] for c in CARTAS: g.append(int(c)) g.extend(tp.carta_article_ids(int(c))) return g def main(): ap = argparse.ArgumentParser() ap.add_argument("--apply", action="store_true") ap.add_argument("--margin", type=int, default=2) ap.add_argument("--limit", type=int, default=0) args = ap.parse_args() state = read_state() done = state["done"] order = build_order() # Frente de Gemma = último índice con su :en ya hecho. front = -1 for i, pid in enumerate(order): if f"{pid}:en" in done: front = i if front < 0: sys.exit("No encuentro el frente de Gemma en la lista; aborto.") start = front + 1 + args.margin work = order[start:] if args.limit: work = work[:args.limit] cur = order[front] print(f"Gemma va por #{cur} (índice {front}/{len(order)-1}).") print(f"Margen {args.margin} → arranco en índice {start} (#{order[start] if start < len(order) else '—'}).") print(f"Posts pendientes a pre-traducir: {len(work)}") if work: print(f" primeros: {work[:5]}") print(f" últimos: {work[-5:]}") if not args.apply: print("\nMODO PLAN (no se crea nada). Añade --apply para ejecutar.") return tot_in = tot_out = 0.0 created = skipped = 0 for pid in work: if tp.translation_exists(pid, "en"): print(f"#{pid}: EN ya existe (Gemma se adelantó) — salto") skipped += 1 continue try: src = tp.read_post(pid) except Exception as e: # noqa: BLE001 print(f"#{pid}: no pude leer ({e}) — salto") continue if src.get("lang") and src["lang"] != "es": continue body, u1 = translate(src["content"], "en") title, u2 = translate(src["title"], "en", is_title=True) tot_in += u1.input_tokens + u2.input_tokens tot_out += u1.output_tokens + u2.output_tokens # Re-chequeo justo antes de crear (ventana de carrera con Gemma). if tp.translation_exists(pid, "en"): print(f"#{pid}: EN apareció mientras traducía — salto") skipped += 1 continue new_id = tp.create_translation(pid, "en", title, body, "draft") created += 1 print(f"#{pid} → EN #{new_id} «{title[:45]}»") cost = tot_in / 1e6 * 1.0 + tot_out / 1e6 * 5.0 print(f"\nCreados: {created} Saltados: {skipped}") print(f"Tokens in={int(tot_in)} out={int(tot_out)} coste=${cost:.4f}") if __name__ == "__main__": main()