
    D1jA                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlmZmZ ddlmZ e
j$                  j'                  dd       ddlmZ  ed      Zd	Zd
ZdZdZddZddZddZd dZd!dZd"dZ d#d$dZ!d%dZ"d%dZ#d&dZ$d'dZ%d(dZ&d)dZ'd)dZ(d*dZ)e*dk(  r e
jV                   e)              yy)+u0  KAR-565 — AKP Stage 3.5: Visual-Frame Analyse fuer Promote-Klassifizierte Videos.

Nach Stage 3 (deep) laeuft Stage 3.5 fuer Klassifikation in (Issue, Skill, Spike).
Liefert visual_insights die NICHT im Transcript stehen (Slides, Code-Demos, Diagramme).

Pipeline:
1. SQL: deep_processed Videos mit klassifikation IN (Issue, Skill, Spike) ohne `## Visual Insights` in Brain-Note
2. Frame-Extract via `claude-watch/scripts/extract.sh` (8 evenly-spaced JPGs, gecacht)
3. Multi-Modal-Call (Sonnet 4.6 Default, Gemini Flash via VISION_FORCE=gemini)
4. Append `## Visual Insights` Section an Brain-Note
5. Cost-Tracking in `daily_costs.vision_usd` (auto-add column)

Cost-Profile:
- Sonnet 4.6: ~$0.04/Video (8 frames + transcript-excerpt input + summary output)
- Gemini Flash: ~$0.0004/Video (100x guenstiger, niedrigere Quality)

Env:
- VISION_FORCE=sonnet|gemini  — Default sonnet
- VISION_MAX=N                — max Videos pro Run (Default 5)
- ANTHROPIC_API_KEY or PRIVATE_OPS_ANTHROPIC_API_KEY  — for Sonnet
- GEMINI_API_KEY              — for Gemini fallback
    )annotationsN)datetimetimezone)Pathz/root/aria/lib)trace_llm_callz%/root/aria/brain/youtube/.config.yamlz4/root/.claude/skills/claude-watch/scripts/extract.shzclaude-sonnet-4-6zgemini-2.5-flashuy  Du bist Aria im Visual-Insights-Modus. Du siehst {n_frames} Frames aus einem AI-Engineering Video und einen Transcript-Auszug.

Ziel: Finde NEUE Erkenntnisse die VISUELL gezeigt werden aber im Transcript nicht (oder nur vage) erklaert sind.

Konkret achten auf:
- Code-Snippets in Slides (zeige sie woertlich)
- Architektur-Diagramme (skizziere die Komponenten + Pfeile)
- Charts/Numbers (gib die Daten wieder)
- API-Endpoints, Filenames, Config-Keys auf Slides
- UI-Screenshots mit relevanten Elementen
- Hand-Schrift / Whiteboard / Sketches

NICHT zu reportieren:
- Speaker-Face Closeups ohne Visual-Inhalt
- Generic Logos / Title-Slides / Outro
- Verallgemeinerungen die der Transcript schon liefert

Liefere AUSSCHLIESSLICH JSON, keine Markdown-Fences. MAX 5 Bullets visual_insights, MAX 3 slide_code_snippets, MAX 3 diagrams_described — keep responses tight:

{{
  "visual_insights": [
    "Bullet 1 — konkretes Visual-Detail (5-15 Woerter, max 5)"
  ],
  "slide_code_snippets": [
    "exakter Code aus Slide (max 3, max 200 Zeichen je)"
  ],
  "diagrams_described": [
    "Architektur: Component A -> B -> C (1-Satz, max 3)"
  ],
  "frame_count_useful": 0,
  "transcript_complement": "1-2 Saetze: was Vision ergaenzt das Transcript nicht hat"
}}

Wenn keine substantiellen Visual-Insights: alle Arrays leer, `transcript_complement: "Video ist transcript-vollstaendig, kein Visual-Mehrwert."`c                     dd l } t        j                         5 }| j                  |      cd d d        S # 1 sw Y   y xY w)Nr   )yamlCONFIG_PATHopen	safe_load)r	   fs     %/root/aria/scripts/aria-akp-vision.pyload_configr   S   s4    				 !q~~a ! ! !s   4=c                Z    t        j                  |       }t         j                  |_        |S )N)sqlite3connectRowrow_factory)db_pathcons     r   db_connr   Y   s     
//'
"CkkCOJ    c                z    	 | j                  d       | j                          y # t        j                  $ r Y y w xY w)Nz<ALTER TABLE daily_costs ADD COLUMN vision_usd REAL DEFAULT 0)executecommitr   OperationalError)r   s    r   ensure_cost_columnr   _   s5    RS

## s   !$ ::c                    t        j                  t        j                        j	                         j                         }| j                  d|||||f       | j                          y )NzINSERT INTO daily_costs (date, triage_usd, deep_usd, vision_usd, total_usd) VALUES (?,0,0,?,?) ON CONFLICT(date) DO UPDATE SET vision_usd = COALESCE(vision_usd,0) + ?, total_usd = total_usd + ?)r   nowr   utcdate	isoformatr   r   )r   deltatodays      r   add_vision_costr%   g   sQ    LL&++-779EKK	m	ueU+
 JJLr   c                @    t        | j                  d|dz  f            S )Na  SELECT d.video_id, d.brain_note_path, d.classification, d.priority, d.confidence,
                   i.url, i.title, i.channel, i.duration_seconds
            FROM deep_processed d
            JOIN ingested i ON i.video_id = d.video_id
            WHERE d.classification IN ('Issue','Skill','Spike')
              AND d.brain_note_path IS NOT NULL
              AND i.url IS NOT NULL
              AND i.source = 'youtube'
            ORDER BY d.processed_at DESC
            LIMIT ?   )listr   )r   limits     r   get_pendingr*   q   s*    		 
  r   c                h    | j                         sy	 d| j                         v S # t        $ r Y yw xY w)NT## Visual Insights)exists	read_textOSError)
brain_notes    r   has_visual_insightsr1      s<    #z';';'=== s   % 	11c           
        	 t        j                  dt        | gdddi t        j                  ddd      }|j                  d	k7  r1t        j                  j                  d
|j                  dd  d       y|j                  j                         r+|j                  j                         j                         d   nd}|rt        |      j                         syt        |      S # t         j
                  $ r Y yw xY w)z0Call claude-watch/extract.sh, return frames dir.bashTX  8768)FRAMESFRAME_WIDTH)capture_outputtexttimeoutenvNr   z[vision] extract.sh fail: i8
)
subprocessrun
EXTRACT_SHosenvironTimeoutExpired
returncodesysstderrwritestdoutstrip
splitlinesr   is_dir)url	work_rootrwork_dirs       r   run_extractrQ      s    NNZ%dCC2::CUC
 	||q

5ahhtuo5FbIJ45HHNN4Dqxx~~**,R0$H4>002> $$ s   6C& &C<;C<c                   t        j                         }t        j                  t	        |             }d|d d  }| d| dt	        |        d}d|dd|dg}| D ]L  }t        j                  |j                               j                  d	      }	|j                  d
dd|	dd       N t        dd|dgd}
t        j                  j                  dt        j                  |
      j!                         |dddd      }	 t        j                  j#                  |d      5 }t        j$                  |      }d d d        dj/                  d  j1                  d!g       D              }|j1                  d"i       }t)        dt        |||j1                  d#      |j1                  d$      d%t+        t        j                         |z
  dz        &       |j1                  d#d'      d(z  d)z  |j1                  d$d'      d(z  d*z  z   }	 |j3                  d+      }|j5                  d,      }t        j6                  |||d-z          }||j1                  d#      |j1                  d$      t=        |d.      |d d/ d0fS # 1 sw Y   >xY w# t&        $ rW}t)        dt        |t+        t        j                         |z
  dz        t-        |             d dt-        |      ifcY d }~S d }~ww xY w# t        j8                  t:        f$ r d }Y w xY w)1Nn_frames)Transcript-Excerpt (truncated to 6 KB):

p  



[+ frames]r:   )typer:   asciiimagebase64
image/jpeg)rZ   
media_typedata)rZ   sourcei	  user)rolecontent)model
max_tokensmessagesz%https://api.anthropic.com/v1/messagesz
2023-06-01application/json)z	x-api-keyzanthropic-versionzcontent-typePOSTr`   headersmethodx   r;   aria-akp-vision  scriptre   prompt
latency_mserrorru    c              3  @   K   | ]  }|j                  d d        ywr:   rv   Nget).0bs     r   	<genexpr>zcall_sonnet.<locals>.<genexpr>   s     C155$C   rd   usageinput_tokensoutput_tokensr   r   rr   re   rs   response_textr   rt   r       .Ag      @g      .@{}      r4   r   r   cost_usdraw)time	monotonicVISION_PROMPTformatlenr]   	b64encode
read_bytesdecodeappendSONNET_MODELurllibrequestRequestjsondumpsencodeurlopenload	Exceptionr   intstrjoinrz   findrfindloadsJSONDecodeError
ValueErrorround)framestranscript_excerptapi_keyt0prompt_statictranscript_blocktrace_promptrd   r   b64bodyreqrespder:   r   costfirstlastparseds                        r   call_sonnetr      s'   		B!((#f+(>MDEWX]Y]E^D_`#_D)9(:&VXVL/!12G  q||~.55g>'|SQ
 	 $9:D
 ..
 
 /ZZ$$& !-.

  ! 	C'^^##C#5 	 		$A	  77CaeeIr.BCCDEE'2E+<!%*/))N*CV[V_V_`oVpq!4>>#3b#8D"@AC IIna(3.#5?TU9VY\9\`d8ddD		#zz#Dtax01 EIIn$=PUPYPYZiPjx}  C  EF  yG  PT  UY  VY  PZ  [  [  ['	  	  '/|L"%t~~'7"'<&D"ESQRV	Ugs1v&&&'   *- sI   !J #I59J =K% 5I?:J 	K"AKK"K"%LLc                b   t        j                         }t        j                  t	        |             }d|d d  }| d| dt	        |        d}d|id|ig}| D ]J  }t        j                  |j                               j                  d      }	|j                  d	d
|	di       L d|igddid}
dt         d| }t        j                  j                  |t        j                  |
      j!                         ddid      }	 t        j                  j#                  |d      5 }t        j$                  |      }d d d        j/                  d      xs i gd   }dj1                  d |j/                  d       xs i j/                  dg       D              }|j/                  d!i       }t)        dt        |||j/                  d"      |j/                  d#      d$t+        t        j                         |z
  dz        %       |j/                  d"d      d&z  |j/                  d#d      d'z  z   d(z  }	 |j3                  d)      }|j5                  d*      }t        j6                  |||d+z          }||j/                  d"      |j/                  d#      t=        |d,      |d d- d.fS # 1 sw Y   gxY w# t&        $ rW}t)        dt        |t+        t        j                         |z
  dz        t-        |             d dt-        |      ifcY d }~S d }~ww xY w# t        j8                  t:        f$ r d }Y w xY w)/NrS   rU   rV   rW   rX   rY   r:   r[   inline_datar^   )	mime_typer`   partsmaxOutputTokensi  )contentsgenerationConfigz8https://generativelanguage.googleapis.com/v1beta/models/z:generateContent?key=zContent-Typerh   ri   rj   rm   rn   ro   rp   rq   ru   
candidatesr   rv   c              3  @   K   | ]  }|j                  d d        ywrx   ry   )r{   ps     r   r}   zcall_gemini.<locals>.<genexpr>   s     `155$`r~   rd   usageMetadatapromptTokenCountcandidatesTokenCountr   r   g?g?r   r   r   r      r4   r   )r   r   r   r   r   r]   r   r   r   r   GEMINI_MODELr   r   r   r   r   r   r   r   r   r   r   r   rz   r   r   r   r   r   r   r   )r   r   r   r   r   r   r   r   r   r   r   rM   r   r   r   r   	candidater:   r   r   r   r   r   s                          r   call_geminir      sa   		B!((#f+(>MDEWX]Y]E^D_`#_D)9(:&VXVL		!"E  Pq||~.55g>m<%MNOP u%&.5D E\NRghogp
qC
..
 
 4::d+;+B+B+D~_qNr  |B
   CC'^^##C#5 	 		$A	  |$,a0I77`y}}Y/G/M2.R.RSZ\^._``DEE/2&E+<!%*/))4F*GZ_ZcZcdzZ{|!4>>#3b#8D"@AC II(!,t3eii@VXY6Z]a6aaehhD		#zz#Dtax01 EII.@$ATYT]T]^tTu  DI  JN  PQ  DR  [_  `d  ad  [e  f  f  f)	  	  '/|L"%t~~'7"'<&D"ESQRV	Ugs1v&&&'    *- sI   !J- %J ;J- /=L  J*%J- -	L6ALLLL.-L.c           
        | j                         }g d}|j                  d      xs g }|r|D ]  }|j                  d|         n|j                  d       |j                  d      xs g }|r>|j                  d       |j                  d       |D ]  }	|j                  d|	 d	        |j                  d
      xs g }
|
r=|j                  d       |j                  d       |
D ]  }|j                  d|         |j                  d      }|r%|j                  d       |j                  d|        |j                  d       |j                  d| d|ddt        j                  t
        j                        j                          d       |j                  d       d|v r%|j                  ddj                  |      dz         }n%|j                         dz   dj                  |      z   }| j                  |       y )N)rv   r,   rv   visual_insightsz- uJ   - (keine substantiellen Visual-Insights — Video transcript-vollstaendig)slide_code_snippetsrv   z**Slide-Code:**z```
z
```diagrams_describedz**Diagramme:**transcript_complementz**Transcript-Ergaenzung:** z'*Auto-generated by aria-akp-vision.py (z, cost $.4fz, z)*z'---
*Auto-generated by aria-akp-deep.pyr=   z(
---
*Auto-generated by aria-akp-deep.pyrW   )r.   rz   r   r   r   r   r    r"   replacer   rstrip
write_text)r0   insightsre   r   r:   sectionbulletsr|   codecdiagramsr   
complements                r   append_visual_insightsr      s   !D,Gll,-3G 	%ANNRs8$	% 	cd<<-.4"Dr() 	-ANNU1#U+,	-||017RHr'( 	%ANNRs8$	%56Jr4ZLABNN2NN<UG8DQT:UWX`XdXdemeqeqXrX|X|X~W  @B  C  DNN21T9||F IIg.1]]_ {{}v%		'(::$r   c                H   | j                  d|f      j                         }|r|d   syt        |d         }|j                         sy	 t	        j
                  |j                               }|j                  d      xs dd d S # t        j                  t        f$ r Y yw xY w)Nz0SELECT raw_path FROM ingested WHERE video_id = ?raw_pathrv   
transcripti@  )
r   fetchoner   r-   r   r   r.   rz   r   r/   )r   video_idrowr   r`   s        r   get_transcript_excerptr   "  s    
++H8+
V
_
_
aCc*oS_A88:zz!++-(&,"et44  '* s   
:B B! B!c                   t         j                  j                  |       }|r|S dD ]  }t        |      j	                         st        |      j                         j                         D ][  }|j                  |  d      s|j                  dd      d   j                         j                  d      j                  d      c c S   y )N)z/root/aria/.env=r   "'
rB   rC   rz   r   r-   r.   rK   
startswithsplitrJ   )namevalpathlines       r   get_api_keyr   0  s    
**..
C

$ O:T
,,.99; O??dV1:.::c1-a0668>>sCII#NNOO
 r   c                 2    t        d      xs t        d      S )NANTHROPIC_API_KEYPRIVATE_OPS_ANTHROPIC_API_KEY)r    r   r   get_anthropic_keyr   =  s    *+[{;Z/[[r   c                 n   t         j                  j                  d      } | r| S t        d      }|j	                         rw|j                         j                         D ]V  }|j                  d      s|j                  dd      d   j                         j                  d      j                  d      c S  y )NGEMINI_API_KEYz/root/.aria-secrets/gemini.envzGEMINI_API_KEY=r   r   r   r   r   )r   secr   s      r   get_gemini_keyr   A  s    
**..)
*C


/
0C
zz|MMO..0 	KD01zz#q)!,224::3?EEcJJ	K r   c                 n   t        j                         } | j                  dt        t        t        j
                  j                  dd                   | j                  dd       | j                  dd	
       | j                  dddgt        j
                  j                  dd             | j                         }t               }t        |d   d         }t        |       |j                  r't        |j                  d|j                  f            }nt        ||j                        }g }|D ][  }t!        |d   xs d      }|j#                         s&t%        |      s|j'                  |       t)        |      |j                  k\  s[ n t+        dt)        |       d|j,                          |j.                  r9|d d D ]0  }t+        d|d   d d  d|d   dd|d    d|d   xs dd d          2 y!t1               }	t3               }
|j,                  dk(  r$|	s"t+        d"t4        j6                  #       d|_        |j,                  dk(  r|
st+        d$t4        j6                  #       y%d!x}x}}t9        |d&      D ]  \  }}|d   }|d'   }t!        |d         }t+        d(| d)t)        |       d*| d+|d   xs dd d,  d-	       t;        |      }|st+        d.       |d&z  }ht=        |j?                  d/            d d0 }|st+        d1       |d&z  }tA        ||      }|j,                  dk(  rtC        |||	      \  }}tD        }ntG        |||
      \  }}tH        }|st+        d2|        |d&z  }|j                  d3      xs d4}tK        ||       tM        ||||       t)        |j                  d5      xs g       }t+        d6| d7|d8d9|j                  d:       d)|j                  d;       d<	       |d&z  }tO        jP                  d=        t+        d>| d?| d@| d|j,                          y!)ANz--max
VISION_MAX5)rZ   defaultz
--video-idz%Run for one specific video_id (debug))helpz	--dry-run
store_true)actionz--force-modelsonnetgeminiVISION_FORCE)choicesr   pathsstate_dbzSELECT d.video_id, d.brain_note_path, d.classification, d.priority, d.confidence,
                       i.url, i.title, i.channel
                FROM deep_processed d JOIN ingested i ON i.video_id = d.video_id
                WHERE d.video_id = ?brain_note_pathrv   z[akp-vision] candidates: z model=r   z  DRY  r      z  classification5sprioritytitle<   r   z9[akp-vision] no ANTHROPIC_API_KEY, falling back to gemini)filez([akp-vision] ERROR: no API key available   r   rM   [/z]  2   z...z  SKIP no frameszframes/frame_*.jpg   z  SKIP no frame filesz  FAIL parse: r   g        r   z  OK z insights, $r   z (r   r   )g      ?z[akp-vision] processed=z	 skipped=z failed=))argparseArgumentParseradd_argumentr   rB   rC   rz   
parse_argsr   r   r   r   r(   r   r*   maxr   r-   r1   r   r   printforce_modeldry_runr   r   rF   rG   	enumeraterQ   sortedglobr   r   r   r   r   r%   r   r   sleep)apargscfgr   rowsr   r   bnrO   api_key_sonnetapi_key_gemini	processedskippedfailedividrM   r0   rP   r   r   r   meta
model_namer   n_inss                             r   mainr0  M  sK   		 	 	"BOOG#s2::>>,PS3T/UOVOOL'NOOOOKO5OOOh-AJJNN>8D  F==?D
-C
#g,z*
+Cs}}CKK( +/--)9	
  3) J #'(.B/yy{"2&c"z?dhh& 
%c*o%6gd>N>N=O
PQ||BQ 	xAGAjM#2./r!4D2Eb1IAjM?Z\^_`g^h^nlnpsqs]t\uvw	x&(N#%N8#NIPSPZPZ[#8#N8szzJ#$$I$&J* %3*o%j#/01
!Ac*o&bQG0BCR/H.IMNs#$&qLG&:;<Ra@)+qLG+C5
x'(^LNHd%J(^LNHd%JN4&)*aKFxx
#*sT"z8ZFHLL!239r:eWLc
"TXXn5M4NaPTPXPXYhPiOjjklmQ	

3K%N 
#I;iyPWX\XhXhWi
jkr   __main__)returndict)r   r   r2  sqlite3.Connection)r   r4  r2  None)r   r4  r#   floatr2  r5  )r   r4  r)   r   r2  zlist[sqlite3.Row])r0   r   r2  bool)z/tmp)rM   r   rN   r   r2  zPath | None)r   z
list[Path]r   r   r   r   r2  ztuple[dict | None, dict])
r0   r   r   r3  re   r   r   r6  r2  r5  )r   r4  r   r   r2  r   )r   r   r2  
str | None)r2  r8  )r2  r   ),__doc__
__future__r   r  r]   r   rB   rer   r?   rF   r   urllib.requestr   r   r   pathlibr   r   insertaria_llm_tracer   r
   rA   r   r   r   r   r   r   r%   r*   r1   rQ   r   r   r   r   r   r   r   r0  __name__exitr   r   r   <module>rB     s   , #    	 	   
   '  # $ ):;C
"!!TH! &3[l'fT" J
\	Zz zCHHTV r   