"""Prepare blind listening reviews (Gemini via project harness). Excerpts are fixed-gain re-encodes only."""
import sys,json,hashlib,subprocess,datetime; from pathlib import Path
ROOT=Path('/workspaces/UGS/AdFactorySongFormat/trees/c06fb16085'); sys.path.insert(0,str(ROOT/'tools')); import music_first
R=ROOT/'runs/targ-delta-c06fb16085-v01'; (R/'audio').mkdir(parents=True,exist_ok=True); (R/'specs').mkdir(exist_ok=True); (R/'reviews').mkdir(exist_ok=True)
def sha(p):
    with open(p,'rb') as f: return hashlib.file_digest(f,'sha256').hexdigest()
def save(p,d): Path(p).write_text(json.dumps(d,ensure_ascii=False,indent=2)+'\n')
now=lambda: datetime.datetime.now(datetime.timezone.utc).isoformat()
TARG=Path('/workspaces/UGS/LevchykUGS/data/music_dna_raw/targ.mp4'); NEW=Path('/workspaces/UGS/AdFactorySongFormat/trees/ce99390b3b/runs/targ-refined-ce99390b3b-v01/song/accepted-v01/gopure-targ-uk-master.wav')
assert sha(TARG)=='1ad8be404d1599942b378b8d5fb5322351684fe74b08f1fcc29502d59cfd4ee6' and sha(NEW)=='aec6bcd423f6b65a66ccf53af1a2ea6377a7b150cd2142d64b819622947296ae'
GAIN={'targ':-3.8,'new':-2.4}
manifest={'schema':'adfactory.targ-delta-analysis/v1','id':R.name,'createdAt':now(),'scope':'Read-only comparative analysis of English Targ (owner 10/10) versus accepted Ukrainian master (owner 8-9/10). No song or film generation; listening reviews only.',
 'reference':{'id':'targ','source':{'path':str(TARG),'sha256':sha(TARG)},'duration':194.488934},
 'candidate':{'id':'new','source':{'path':str(NEW),'sha256':sha(NEW)},'duration':203.240998},
 'ownerRanking':'Reviewers are NOT told which track the owner prefers.'}
save(R/'manifest.json',manifest)
save(R/'execution-control.json',{'adaptationId':R.name,'status':'active','authority':'Owner goal 2026-09-16: analysis only; listening reviews allowed; no song generation, no film','allowed':['audio review','publication'],'forbidden':['music generation','new film','merge']})
def excerpt(tag,src,parent,span,channels,bitrate):
    out=R/'audio'/f'{tag}.mp3'; cmd=music_first.excerpt_command(parent,out,span,GAIN[src],channels,bitrate); subprocess.run(cmd,check=True)
    rec={'schema':'adfactory.listening-excerpt/v1','parent':{'path':str(parent),'sha256':sha(parent)},'asset':{'path':str(out),'sha256':sha(out)},'parentRange':list(span),'gainDb':GAIN[src],'channels':channels,'bitrate':bitrate,'retimed':False,'method':'ffmpeg fixed gain and re-encode only; reproducible through music_first.excerpt_command; no retiming or dynamics change'}
    dp=R/'audio'/f'{tag}.derivation.json'; save(dp,rec); return {'path':str(out),'sha256':rec['asset']['sha256'],'derivation':{'path':str(dp),'sha256':sha(dp)},'span':span,'src':src}
TD=194.488934; ND=203.240998
whole=[excerpt('whole-targ','targ',TARG,[0,TD],1,'56k'),excerpt('whole-new','new',NEW,[0,ND],1,'56k')]
pairs={'p1-mentor':[excerpt('p1-targ','targ',TARG,[79.7,108.0],2,'128k'),excerpt('p1-new','new',NEW,[74.4,98.1],2,'128k')],
 'p2-results':[excerpt('p2-targ','targ',TARG,[122.6,146.1],2,'128k'),excerpt('p2-new','new',NEW,[123.1,149.5],2,'128k')],
 'p3-return-coda':[excerpt('p3-targ','targ',TARG,[146.0,TD],2,'128k'),excerpt('p3-new','new',NEW,[149.5,ND],2,'128k')]}
COMMON='Return ONLY JSON (no prose outside JSON), <=1900 words, English. Required keys: mediaAccess(boolean), audioAccess(boolean), attachmentsHeard[{attachment,firstWordsHeard,lastWordsHeard,durationEstimateSeconds}], then the analysis keys listed below. No script is supplied: quote only words you actually hear and mark uncertain words with (?). Use attachment number plus LOCAL seconds of that attachment. Do not rate quality with numbers, do not assume either language or the original is better, do not reward louder mastering, more notes or softer timbre. Both are real songs: identify concrete transferable mechanisms, not taste.'
ROLE_W=lambda i,lab,dur: f'Attachment {i}; complete {lab} song, {dur:.1f}s, 56kbps mono listening proxy with fixed gain (do not grade codec quality); local time starts at 0.'
spec_whole={'out':str(R/'reviews/whole-delta-v01'),'reviewModel':'gemini-3.8-flash','reviewTransport':'gemini-native-inline','thinkingLevel':'high',
 'prompt':'Two complete sung advertorial story-songs about the same story: Attachment 1 is in English, Attachment 2 is a Ukrainian adaptation of the same story with a different product. Listen to BOTH whole songs end to end. Task: a songcraft and vocal-delivery comparison at the META level. Specifically analyze, with at least 10 timed landmarks per song: (a) how each singer ATTACKS and RELEASES words (crisp consonant endings vs open sustained vowels; which words get lengthened and whether they are the important words or unimportant ones); (b) syllable-length shaping: where syllables are clipped, where stretched, and whether stretch coincides with meaning; (c) melodic play with words: moments where the melody makes a joke, a turn, a question, a surprise, or lets a word "land" (describe the gesture); (d) rhythm: whether vocal phrases sit on the beat grid or float speech-like across it, and where syncopation or off-grid placement creates emphasis; (e) phrase endings and breath: what happens musically between phrases (band answers, held chord, silence, immediate next line); (f) how the arrangement reacts to story turns (drops, stops, fills, register changes); (g) register and timbre choices per scene; (h) form and pacing of information: where each song is fast and where it slows down, and whether density tracks drama. Then give: transferablePrinciples[{principle,evidenceAttachment1,evidenceAttachment2,howToApplyInUkrainianWithoutFragmentingSentences}], languageInherentDifferences[] (things caused by English vs Ukrainian word structure, not by the singer), strongestGesturesEach[], weakestGesturesEach[], whatAttachment2AlreadyDoesAsWellOrBetter[]. Constraint for recommendations: Ukrainian must stay natural, fully sung and intelligible; do NOT recommend chopping sentences into telegraphic fragments or inserting long instrumental gaps after every line. '+COMMON,
 'assets':[{'path':whole[0]['path'],'sha256':whole[0]['sha256'],'transport':'inline','role':ROLE_W(1,'English',TD)},{'path':whole[1]['path'],'sha256':whole[1]['sha256'],'transport':'inline','role':ROLE_W(2,'Ukrainian',ND)}],
 'musicReview':{'manifest':{'path':str(R/'manifest.json'),'sha256':sha(R/'manifest.json')},'stage':'candidate-whole','parents':[{'path':str(TARG),'sha256':sha(TARG)},{'path':str(NEW),'sha256':sha(NEW)}],'derivations':[w['derivation'] for w in whole]}}
save(R/'specs/whole-delta-v01.json',spec_whole)
PAIR_PROMPTS={'p1-mentor':'Two excerpts of the same scene from two sung story-songs (Attachment 1 English, Attachment 2 Ukrainian): the heroine picks up her daughter from dance class, meets an older woman with perfect skin, asks what she uses, gets asked back what SHE uses, lists her products, and is told to stop. Compare in fine detail: (1) the pickup line at the start: how long each syllable is, which word is stretched, whether the stretch carries meaning or is filler; (2) the questions: how melody makes them playful/pitched/teasing; the laugh; the look; the answering question; (3) the product list: rhythm, clipped or flowing; (4) the Stop: attack, what the band does, decay; (5) where each excerpt breathes. Give timed landmarks (attachment, local seconds, quoted words). Then: transferableMechanisms[{mechanism,howAttachment1DoesIt,whatAttachment2DoesInstead,concreteUkrainianAdjustmentWithoutTelegraphicFragments}], languageInherent[], whatAttachment2DoesWell[]. ',
 'p2-results':'Two excerpts of the same scene (Attachment 1 English, Attachment 2 Ukrainian): three weeks later she looks in the mirror, patches lighter; daughter says you look different, good different, brighter; by week six she is in shock, the problem almost gone, skin more even than in a decade; she goes out without covering it. Compare in fine detail: (1) how time jumps (three weeks, week six) are marked musically: held word, breath, register lift, band change; (2) the daughter quote: does the voice change character/register/timbre; (3) the surprise/shock: attack, pitch peak, syllable clipping; (4) release of the last line; (5) density: syllables per second and whether it drops for the payoff. Timed landmarks (attachment, local seconds, quoted words). Then: transferableMechanisms[{mechanism,howAttachment1DoesIt,whatAttachment2DoesInstead,concreteUkrainianAdjustmentWithoutTelegraphicFragments}], languageInherent[], whatAttachment2DoesWell[]. ',
 'p3-return-coda':'Two excerpts of the same closing (Attachment 1 English, Attachment 2 Ukrainian): June recital, the ex-husband\'s wife sits nearby, looks: not pity, not sympathy, just confused; after the recital she says you look really good; the same woman who made her sit in the car; thank you and I meant it; not because her opinion mattered but because mine finally did; then the product name and the link/ordering coda. Compare in fine detail: (1) the second gaze: separated negations, staccato or flowing, band under it; (2) the compliment: conversational vs sung, how the melody lands "by the way"; (3) her-opinion vs mine: which word gets the peak and how the cadence completes; (4) transition into the coda: does the music breathe before the brand; (5) the coda: how sparse, how sung, how many facts, how it ends. Timed landmarks (attachment, local seconds, quoted words). Then: transferableMechanisms[{mechanism,howAttachment1DoesIt,whatAttachment2DoesInstead,concreteUkrainianAdjustmentWithoutTelegraphicFragments}], languageInherent[], whatAttachment2DoesWell[], codaInformationLoad{attachment1Facts,attachment2Facts,secondsEach}. '}
for pid,ex in pairs.items():
    assets=[{'path':e['path'],'sha256':e['sha256'],'transport':'inline','role':f'Attachment {i}; {lab} excerpt; parent range {e["span"][0]}-{round(e["span"][1],2)}s of the full song; 128kbps stereo, fixed gain; local time starts at 0.'} for i,(e,lab) in enumerate(zip(ex,['English','Ukrainian']),1)]
    spec={'out':str(R/'reviews'/f'{pid}-v01'),'reviewModel':'gemini-3.8-flash','reviewTransport':'gemini-native-inline','thinkingLevel':'high','prompt':PAIR_PROMPTS[pid]+COMMON,'assets':assets,
     'musicReview':{'manifest':{'path':str(R/'manifest.json'),'sha256':sha(R/'manifest.json')},'stage':'candidate-local','parents':[{'path':str(TARG),'sha256':sha(TARG)},{'path':str(NEW),'sha256':sha(NEW)}],'derivations':[e['derivation'] for e in ex]}}
    save(R/'specs'/f'{pid}-v01.json',spec)
save(R/'review-decision-v01.json',{'at':now(),'purpose':'Analysis-only session: model listening as one lens beside signal measurements and text analysis. Reviewers blind to owner ranking. One POST per spec; unknown outcomes are never repeated.','hypotheses':['H1 English attacks/releases words with consonant endings and clipped function words; Ukrainian sustains open vowels on content words (measured: 77% vowel-final phrase endings vs 8%).','H2 English stretches unimportant lead-in words (By, The, That) as pickups and clips content words; Ukrainian stretches content words (забирала, Наступного, Глянула).','H3 Ukrainian information density is highest where English is sparsest (care/coda blocks).','H4 English phrases float off the beat grid more (speech rhythm); Ukrainian sits on the grid.'],'specs':[str(p) for p in sorted((R/'specs').glob('*.json'))],'paid':True,'generationPurchases':False})
for p in sorted((R/'audio').glob('*.mp3')): print(p.name,p.stat().st_size)
print('prepared')
