Files
feadulta/scripts/pretranslate_en_haiku.py

126 lines
4.4 KiB
Python

#!/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()