
    6NjW.                    8   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mZ  ed      Zej                  j                  dd      j!                  d      Zej                  j                  d	d      Zej                  j                  d
d      ZdZdZdZdZesdD ]  Zej2                  j5                  e      r} ee      j7                         j9                         D ]V  Zej=                  d      sej?                  dd      d   jA                         jA                  d      jA                  d      Z n es n ere dndZ!d"dZ"eefd#dZ#d$d%dZ$d&dZ%ef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j\                   e,              yy)*u{  aria-brain-embed — One-shot + incremental Embedding für brain_chunks (KAR-120).

Schreibt Brain-Files in chunks → embedded sie via OpenAI text-embedding-3-large
(3072 dim) → upsertet sie in `brain_chunks` (Supabase aria-DB pgvector).

Embedding-Modell-Wahl (Sektion 12.3 der Gibson-Analyse):
- text-embedding-3-large: 3072 dim, ~$0.13 per 1M Token, general-purpose
- One-shot full-brain (200 Files, 30 MB): geschätzt ~$0.13

Voraussetzungen:
- OPENAI_API_KEY in env oder /root/.aria-secrets/openai.env
- pgvector + brain_chunks Table vorhanden (siehe /tmp/aria-pgvector-schema.sql, KAR-120-Setup)

CLI:
    python3 aria-brain-embed.py status      # zeige welche Files embedded sind
    python3 aria-brain-embed.py backfill    # full re-embed alles
    python3 aria-brain-embed.py incremental # nur neu/geändert (default)
    python3 aria-brain-embed.py file <path> # einzelnes File
    )annotationsN)Pathz/root/aria/brainSUPABASE_URL /SUPABASE_SERVICE_ROLE_KEYOPENAI_API_KEYz$https://api.openai.com/v1/embeddingsztext-embedding-3-largei     )z/root/.aria-secrets/openai.envz/root/aria/.envzOPENAI_API_KEY==   "'z5/rest/v1/brain_chunks?on_conflict=file_path,chunk_idxc                 &    t         dt          dddS )NBearer application/jsonzresolution=merge-duplicates)apikeyAuthorizationContent-TypePrefer)r        &/root/aria/scripts/aria-brain-embed.py_supabase_headersr   <   s!    +"#<"=>*/	 r   c                   | j                         } t        |       |k  r| r| gS g S g }d}|t        |       k  rt        ||z   t        |             }|t        |       k  r | j                  d||      }|||dz  z   kD  r|}|j	                  | || j                                |t        |       k\  rn!t        ||z
  |dz         }|t        |       k  r|D cg c]  }|s|	 c}S c c}w )zZSimple character-window chunking. Brain-Files sind meist <30KB,
    1-15 chunks pro Datei.r   
   r   )striplenminrfindappendmax)texttargetoverlapchunksposendnlcs           r   
chunk_textr+   E   s     ::<D
4yFv%2%F
C
D	/#,D	*T?D#s+BC&A+%%d3sm))+,#d)#-q) D	/ #!A###s   C#C#c                   t        |       dkD  r@g }t        dt        |       d      D ]#  }|j                  t        | ||dz    |             % |S t        st        d      t        j                  t        | d      j                         }d}d}t        |dz         D ]  }	 t        j                  j                  t        |dt         d	d
d      }t        j                  j                  |d      5 }	t        j                  |	j!                               }
ddd       
d   D cg c]  }|d   	 c}c S  |xs t5        d      # 1 sw Y   1xY wc c}w # t        j"                  j$                  $ rs}|}|j&                  dk(  r\||k  rWt(        j*                  j-                  d|dz    d| d|dd       t/        j0                  |       t3        |dz  d      }Y d}~E d}~ww xY w)u   OpenAI text-embedding-3-large, batch. Exponential-backoff bei 429.

    Sub-Batching (KAR-876, 08.07.2026): grosse Files (600+ Chunks) sprengen
    das Request-Limit (HTTP 400) — intern in 100er-Batches aufteilen.d   r   zlERROR: OPENAI_API_KEY missing. Place in /root/.aria-secrets/openai.env as OPENAI_API_KEY=sk-... (chmod 600).)modelinputg      ?Nr   r   r   )r   r   POSTdataheadersmethod<   timeoutr2   	embeddingi  z  [429] retry r   z in z.1fzs
r      zembed_batch exhausted retries)r   rangeextendembed_batchr	   
SystemExitjsondumpsEMBED_MODELencodeurllibrequestRequestOPENAI_EMBED_URLurlopenloadsreaderror	HTTPErrorcodesysstderrwritetimesleepr   RuntimeError)textsretriesoutipayloaddelaylast_errattemptreqrr2   itemexcs                r   r<   r<   [   s   
 5zC!#q#e*c* 	?AJJ{51s7#3W=>	?
4
 	
 jj;?@GGIGEH1% 	..(( w'.~.>%?$6 ! ) C ''R'8 ,Azz!&&(+,26v,?$D%??( 
Cl#BCC, ,?||%% 	Hxx37W#4

  >'A+ayUSVKWZ![\

5!EAIr*	sD    AE2$EE%E1EE	EG'4A'G"!G""G'c                   t         st        d      | |||d}t        j                  j	                  t         t        j                  |      j                         i t               ddid      }t        j                  j                  |d      5 }d	|j                  cxk  xr d
k  nc cddd       S # 1 sw Y   yxY w)uL   Upsert via Supabase REST. Unique(file_path, chunk_idx) — resolution=merge.zERROR: SUPABASE_URL missing)	file_path	chunk_idxcontentr8   r   z*resolution=merge-duplicates,return=minimalr0   r1   r9   r6   r
   i,  N)REST_BRAIN_CHUNKSr=   rB   rC   rD   r>   r?   rA   r   rF   status)r_   r`   ra   r8   rowrZ   r[   s          r   upsert_chunkre      s    677	C ..
 
 

3 6 6 8_$&_2^_ ! C
 
		R		0 %Aahh$$% % %s   B44B=c                    g }| j                  d      D ]C  }t        d |j                  t              j                  D              r3|j                  |       E t        |      S )z:Liste alle .md-Files unter BRAIN_ROOT, ohne .git/.archive.z*.mdc              3  p   K   | ].  }|j                  d       xs |dv xs |j                  d       0 yw).)__pycache__Archivz.archiveN)
startswithendswith).0parts     r   	<genexpr>zbrain_files.<locals>.<genexpr>   s=       Tjnts#et/H'HeDMMZdLee  Ts   46)rglobanyrelative_to
BRAIN_ROOTpartsr!   sorted)
only_underrT   mds      r   brain_filesrx      s}    
Cv&   Trt  sA  sA  BL  sM  sS  sS  T  T

2 #;r   c            	     &   t         si S i } d\  }}	 t         d}t        j                  j	                  |i t               d| d||z   dz
   dd      }	 t        j                  j                  |d	
      5 }t        j                  |j                               }ddd       D ]
  }|| |d   <    t        |      |k  r| S ||z  }# 1 sw Y   .xY w# t        $ r2}t        j                  j                  d| d| d       | cY d}~S d}~ww xY w)ud  Was ist in brain_chunks_files (welche Files schon embedded).

    Paginiert (KAR-876, 08.07.2026): PostgREST/Supabase kappt Antworten bei
    max-rows=1000 — limit=10000 half nicht. Ohne Pagination sah der
    Incremental-Lauf nur 1000 von ~2960 registrierten Files und hat ~1900
    Files faelschlich als 'missing' re-embedded (Kosten + inflierte Logs).)r   i  z8/rest/v1/brain_chunks_files?select=*&order=file_path.ascitems-r   )z
Range-UnitRangeGET)r3   r4      r6   Nz&[embed] status query failed at offset : r   r_   )rb   r   rB   rC   rD   r   rF   r>   rG   rH   	ExceptionrL   rM   rN   r   )	rT   offsetpageurlrZ   r[   rowsr]   rd   s	            r   db_files_statusr      s6    	CLFD
VWnn$$?(* ?'!'&4-!*;)<=?	 % 
	''R'8 ,Azz!&&(+,
  	(C$'CK !	(t9tJ$% , , 	JJEfXRPSuTVWXJ	s6   !C 8$C	C 	CC 	D'DDDc                h   t               }t               }t        dt        |       d       t        dt        |       d       t        dt        |      t        |      z
   d       | j                  r>|D ]9  }t        |j                  t                    }||v rdnd}t        d	| d
|        ; y)NzBrain-Files: z totalzDB-Indexed:  z fileszMissing:     z files not embedded yetu   ✓u   —   r   )rx   r   printr   verbosestrrr   rs   )argsfilesdb_idxfrelrc   s         r   
cmd_statusr      s    MEF	M#e*V
,-	M#f+f
-.	M#e*s6{233J
KL|| 	'AammJ/0C!VmUFBvhau%&	' r   c                h   t        | j                        }|j                         s	t        |z  }|j	                         s t        d| dt        j                         yt        |j                  t                    }|j                  d      }t        |      }|st        | d       yt        | d	t        |       d
t        j                         t        |      }t        t        ||            D ];  \  }\  }}t!        ||||      }	t        d| d	|	rdnd t        j                         = y)NzERROR: z
 not foundfiler   replaceerrorsz: empty, skippedr   r    chunksz  chunk OKFAIL)r   pathis_absoluters   existsr   rL   rM   r   rr   	read_textr+   r   r<   	enumeratezipre   )
r   r   r   ra   r&   
embeddingsrU   r#   emboks
             r   cmd_filer      s
   TYYA==?N88:s*%CJJ7
ammJ'
(Ckkk+G F%&'	SECK=
(szz:V$J#C
$;< I;D##q$,2bdf56SZZHI r   c                   t               }t        dt        |       dt         dt        j
                         d}d}|D ]  }t        |j                  t                    }|j                  d      }t        |      }|sA	 t        |      }t        t        ||            D ]  \  }
\  }}	 t        ||
||        |t        |      z  }t!        d |D              dz  }||dz  dz  z  }t        d	| dt        |       dt        j
                         t#        j$                  d        t        d| dt        |       d|dt        j
                         y# t        $ r,}	t        d	| d
|	 t        j
                         Y d }	~	Kd }	~	ww xY w# t        $ r/}	t        d	| d|
 d|	 t        j
                         Y d }	~	$d }	~	ww xY w)Nz
Embedding z brain-files via z...r   r   g        r   r   r    EMBED-FAIL: #z UPSERT-FAIL: c              3  2   K   | ]  }t        |        y w)N)r   )rm   r*   s     r   ro   zcmd_backfill.<locals>.<genexpr>   s     3qCF3s      gp=
ף?i@B r   r   皙?z
Done. z chunks embedded across z files. Estimated cost: $z.4f)rx   r   r   r@   rL   rM   r   rr   rs   r   r+   r<   r   r   r   re   sumrO   rP   )r   r   total_chunkstotal_cost_estr   r   ra   r&   r   r]   rU   r#   r   approx_tokenss                 r   cmd_backfillr      s   ME	Js5zl"3K=
D3::VLN !--
+,++Y+/G$	$V,J (FJ(?@ 	JNA{cJS!T3/	J
 	F#3F33a7-$.::3%r#f+g.SZZ@

3+, 
H\N":3u:, G,S139<E!  	Bse=.SZZ@	  J3%q>#7cjjIIJs0   E,F	F$!FF	G$GGc                   t               }t               }|D cg c]%  }t        |j                  t                    |vs$|' }}t        dt        |       dt        |       dt        j                         |sy|D ]  }t        |j                  t                    }|j                  d      }t        |      }|sAt        d| d	t        |       d
t        j                         	 t        |      }d}
t        t        ||            D ]  \  }\  }}	 t        ||||        t        d| dt        |      |
z
   dt        |       dt        j                         t!        j"                  d        yc c}w # t        $ r,}	t        d| d|	 t        j                         Y d }	~	0d }	~	ww xY w# t        $ r3}	|
dz  }
t        d| d| d|	 t        j                         Y d }	~	d }	~	ww xY w)NzIncremental: z new/missing files (of z total)r   r   r   r   r   z: embedding z chunks ...r   r   [z] UPSERT-FAIL: r   r   z
 chunks okr   )rx   r   r   rr   rs   r   r   rL   rM   r   r+   r<   r   r   r   re   rO   rP   )r   r   r   r   pendingr   ra   r&   r   r]   failedrU   r#   r   s                 r   cmd_incrementalr     s   MEFPQ3q}}Z'@#A#OqPGP	M#g,'>s5zl'
RY\YcYcd !--
+,++Y+/G$3%|CK=<3::N	$V,J 'FJ(?@ 	KNA{cKS!T3/	K 	3%r#f+./qVZHszzZ

3)* 3 Q  	Bse=.SZZ@	  K!3%q?3%8szzJJKs:   %E9E9+E>F6>	F3!F..F36	G2?)G--G2c                 ,   t        j                  d      } | j                  dd      }|j                  dd      }|j	                  d	d
d       |j                  t               |j                  dd      }|j                  t               |j                  dd      }|j                  t               |j                  dd      }|j	                  dd       |j                  t               | j                         }|j                  |      S )NzAria Brain Embedding (KAR-120))descriptioncmdT)destrequiredrc   zshow indexed vs missing files)helpz-vz	--verbose
store_true)action)funcbackfillzfull re-embed all brain filesincrementalzembed only new/changed filesr   zembed a single filer   zabsolute or BRAIN_ROOT-relative)argparseArgumentParseradd_subparsers
add_parseradd_argumentset_defaultsr   r   r   r   
parse_argsr   )parsersubp_stp_bfp_incp_fr   s          r   mainr   %  s    $$1QRF


UT

:C>>()H>IDdK=:&>>*+J>KD<(NN=/MNNE	O,
..&;.
<CV"CD(#D99T?r   __main__)returndict)r#   r   r$   intr%   r   r   	list[str])   )rR   r   rS   r   r   zlist[list[float]])
r_   r   r`   r   ra   r   r8   zlist[float]r   bool)rv   r   r   z
list[Path])r   zdict[str, dict])r   r   )/__doc__
__future__r   r   hashlibr>   osrL   rO   urllib.requestrB   pathlibr   rs   environgetrstripr   r   r	   rE   r@   CHUNK_TARGET_CHARSCHUNK_OVERLAPenv_filer   r   r   
splitlineslinerk   splitr   rb   r   r+   r<   re   rx   r   r   r   r   r   r   __name__exitr   r   r   <module>r      s  & #    	 
    $%
zz~~nb188=JJNN+FK  0"59 & I 77>>(#X002==? ??#45%)ZZQ%7%:%@%@%B%H%H%M%S%STW%XN   nIJ  );= $,&DR%& $. D*@>, zCHHTV r   