"""2.6 pilot (English): which homonym reproduces "one correct edit becomes a wrong global rule"?

The Chinese 2.6 rests on 苹果 = the fruit vs the company; the author writes the company as "Apple". English needs a word with
one work sense the author restyles and one everyday sense where the same restyle is absurd. For each candidate:
  (a) single-edit distillation: show one work transcript before/after the author's single edit and ask the model to turn it
      into a rule for the cleanup rule file (3 runs per model, temperature 1). Is the rule scoped or global?
  (b) downstream damage: clean up 2 work and 3 life transcripts with no rule / a global rule / a scoped rule (temperature 0).
      Does the global rule convert the life mentions?
Text only (no audio). Mirrors experiments/2_5_overgeneralize/run.py."""
import json, pathlib, re, sys
from concurrent.futures import ThreadPoolExecutor
ROOT = pathlib.Path(__file__).resolve().parents[1]; sys.path.insert(0, str(ROOT))
from common import chat, dump, log_cost
HERE = pathlib.Path(__file__).resolve().parent

NAMES = ('# People I mention often\n\n- Jaxon: coworker, backend\n- Saoirse: coworker, deployment and ops\n- Kaitlynn: product manager\n'
         '- Aoife: intern\n- Niamh: designer\n- Priya: partner engineer\n- Kaveh: investor\n- Mairead: cousin\n- Tadhg: friend\n\n'
         'Common terms: Koyeb, vLLM, KV cache, LoRA, MLX, AI Builder Space, intake, VoiceFlow, Codex, prefill, {target}\n')

C = {
 'amazon': dict(src='Amazon', dst='AWS', word=r'\bAWS\b',
   edit='Found a gotcha today. I mean, the intake bucket on Amazon had versioning turned off, so when the sync job overwrote a file the old recording was just gone. Jackson said he hit the exact same thing last year.',
   work=['So Amazon raised the price on the GPU instances again, and the vLLM box is now costing us more than Koyeb ever did.',
         'Saoirse wants to move the transcription queue off Amazon. I mean, the egress fees alone are killing us every month.'],
   life=['On the way home, grab the Amazon package from the front desk. I mean, it has the diapers, so do not forget it.',
         'Tadhg said he got his kid a birthday present on Amazon and it showed up in two days. Maybe we do the same for Mairead.',
         'We have way too many paper towels. Cancel the Amazon subscription for those, and maybe the dish soap one too.'],
   glob='Rule: always write "Amazon" as "AWS" in transcripts.',
   scoped='Rule: write "Amazon" as "AWS" only when it means the cloud platform (servers, buckets, instances, hosting); when it means the shopping site, packages or deliveries, keep "Amazon".'),
 'shell': dict(src='shell', dst='zsh', word=r'\bzsh\b',
   edit='Found a gotcha today. I mean, if you run the intake script from the shell instead of the IDE, the environment variables never load, so it silently uses the old key. Jackson said he hit the same thing.',
   work=['Saoirse says the deploy script only works if you source it in the shell first. I mean, nobody wrote that down anywhere.',
         'Aoife keeps pasting commands into the shell one by one. I should show her how to write a little script instead.'],
   life=['Remember to fill up at the Shell on the way home. I mean, the tank was almost empty this morning.',
         'The kids collected like a hundred shells at the beach. Mairead wants to make a little jar of them for grandma.',
         'Gas at the Shell by the school went up again. Tadhg says the one by the freeway is way cheaper.'],
   glob='Rule: always write "shell" as "zsh" in transcripts.',
   scoped='Rule: write "shell" as "zsh" only when it means the command-line shell on a computer; when it means a gas station or a seashell, keep it as is.'),
 'python': dict(src='Python', dst='python3', word=r'python3',
   edit='Found a gotcha today. I mean, the intake script breaks if you run it with Python from the system path, because it picks up the old version. Jackson said he hit the same thing.',
   work=['Aoife asked whether she should learn Python or just let Codex write everything. I mean, she should learn enough to read it.',
         'Saoirse says the Koyeb image ships an old Python, so the vLLM build fails. We need to pin the version.'],
   life=['We took the kids to the zoo and they would not leave the reptile house. I mean, the python was huge and it was eating.',
         'Tadhg is thinking about getting a ball python for his son. Mairead thinks that is a terrible idea.',
         'The kid asked me how a python swallows something bigger than its head. I need to look that up.'],
   glob='Rule: always write "Python" as "python3" in transcripts.',
   scoped='Rule: write "Python" as "python3" only when it means the programming language or interpreter; when it means the snake, keep "python".'),
 'target': dict(src='target', dst='deploy target', word=r'deploy target',
   edit='Found a gotcha today. I mean, the intake service on the staging target uses a different bucket, so the recordings never showed up. Jackson said he hit the same thing.',
   work=['Saoirse wants to add a second target for the vLLM box so we can test without touching production.',
         'Aoife pushed to the wrong target last night. I mean, nothing broke, but we should lock that down.'],
   life=['On the way home stop at Target for diapers and the kids toothpaste. I mean, we are completely out.',
         'Mairead said Target has the school backpacks on sale this week, so maybe go Saturday.',
         'Tadhg works at the Target by the freeway now. I should stop by and say hi.'],
   glob='Rule: always write "target" as "deploy target" in transcripts.',
   scoped='Rule: write "target" as "deploy target" only when it means a deployment environment; when it means the store Target, keep it as is.'),
}
# Round 2: two more wordings of the Amazon edit (weaker cloud cues), and shell -> terminal (a plainer style edit than zsh).
C['amazon_b'] = dict(C['amazon'], edit='Found a gotcha today. I mean, when the recordings sync over from Amazon, the file names are just random strings, so intake has to read a little database to get the titles. Jackson said he hit the same thing.')
C['amazon_c'] = dict(C['amazon'], edit='Quick note. Amazon is charging us way more than I expected this month. I mean, it is mostly the transcription jobs, so I need to look at the bill with Saoirse on Friday.')
C['shell_terminal'] = dict(C['shell'], dst='terminal', word=r'\bterminal\b',
   glob='Rule: always write "shell" as "terminal" in transcripts.',
   scoped='Rule: write "shell" as "terminal" only when it means the command line on a computer; when it means a gas station, a seashell or an eggshell, keep it as is.')
ASK = 'Clean up this voice memo transcript: fix recognition errors and output only the cleaned full text.'
PROPOSE = ('I made one manual edit while cleaning up a voice transcript. Before:\n{before}\n\nAfter:\n{after}\n\n'
           'Turn this edit into one rule for my cleanup rule file, so it gets applied automatically to all my future transcripts. '
           'Output only the rule, in one sentence.')
SCOPE_WORDS = re.compile(r'\b(only|when|unless|except|context|refer|referring|refers|mean|means|cloud|platform|service|terminal|command|language|programming|deploy|environment|technical|computing|infrastructure)\b', re.I)
MODELS = ['gemini-3.8-flash', 'deepseek-v4.1-flash']
RUNS = 5 if '--runs5' in sys.argv else 3
PROPOSE_ONLY = '--propose-only' in sys.argv
cands = [a for a in sys.argv[1:] if not a.startswith('--')] or list(C)
res = json.load(open(HERE / 'pilot_results.json')) if (HERE / 'pilot_results.json').exists() else {}
for cname in cands:
    c = C[cname]
    after = re.sub(re.escape(c['src']), c['dst'], c['edit'], count=1, flags=0)
    out = {'edit_before': c['edit'], 'edit_after': after, 'proposals': {}, 'downstream': {}}
    names = NAMES.format(target=c['dst'])
    for model in MODELS:
        with ThreadPoolExecutor(5) as ex:
            props = list(ex.map(lambda _: chat([{'role': 'user', 'content': PROPOSE.format(before=c['edit'], after=after)}], model, temperature=1)[0].strip(), range(RUNS)))
        if cname in res and PROPOSE_ONLY:
            res[cname].setdefault('proposals_round2', {})[model] = [{'rule': p, 'scoped_heuristic': bool(SCOPE_WORDS.search(p))} for p in props]
        out['proposals'][model] = [{'rule': p, 'scoped_heuristic': bool(SCOPE_WORDS.search(p))} for p in props]
        print(f'[{cname}] {model} proposals:'); [print('   ', p) for p in props]
        if PROPOSE_ONLY:
            continue
        for rname, rule in {'none': '', 'global': c['glob'], 'scoped': c['scoped']}.items():
            items = [('work', i, s) for i, s in enumerate(c['work'])] + [('life', i, s) for i, s in enumerate(c['life'])]
            def one(it):
                kind, i, s = it
                o, _, _ = chat([{'role': 'user', 'content': f'{ASK}\n\n{names}\n{rule}\n\nTranscript:\n{s}'}], model, temperature=0)
                return kind, o.strip()
            with ThreadPoolExecutor(5) as ex:
                outs = list(ex.map(one, items))
            w = sum(bool(re.search(c['word'], o, re.I)) for k, o in outs if k == 'work')
            l = sum(bool(re.search(c['word'], o, re.I)) for k, o in outs if k == 'life')
            out['downstream'][f'{model}:{rname}'] = {'work_converted': f'{w}/2', 'life_wrongly_converted': f'{l}/3', 'outputs': outs}
            print(f'[{cname}] {model} {rname:6s} work {w}/2  life wrongly {l}/3')
    if not (PROPOSE_ONLY and cname in res):
        res[cname] = out
dump(res, HERE / 'pilot_results.json')
log_cost('2_6_overgeneralize/pilot_homonym.py ' + ' '.join(cands))
