ó
    á³jæ5  ã                  óN  • S r SSKJr  SSKrSSKrSSKJr  SSKJrJ	r	  SSK
JrJrJrJrJrJrJrJr  SSKJr  SSKJr  S	r1 S
krSrSr\\/\\   4   r\\\/\\\\4      4   rSr " S S\5      rSS jr  " S S\5      r! " S S\5      r"\ " S S5      5       r# " S S5      r$g)u‰  Batch answering: many questions asked of one document as one server-side job.

Each question is an ordinary answer â€” retrieval runs for it, the answer step
fills the same `Answer` a single question produces â€” so a batch introduces no
second answer format. What this module adds is the run around them: a job that
continues if the page is closed, a bounded number of questions in flight, a
retry with backoff for a provider that returns 529 under load, and one event
per question completing so progress is a count filling in rather than a log
being tailed.

The two steps are two stages with their own bounds. Retrieval for one question
does not wait for another question's answer: up to `concurrency` retrievals
run while up to `concurrency` answers run, and at most `concurrency` retrieved
contexts wait between them. The run then moves at the pace of the slower stage
rather than the sum of both. Retrieval alone took about as long as the answer
on the Jev ranker, so serial slots spent half their life on work that held no
answer-model call.

The runner holds no reference to the app. The caller hands `start` two
coroutines, one that retrieves for a question and one that answers it from
what was retrieved; everything here is scheduling, accounting and delivery,
which is what makes it testable without a model.

Runs live in the server's memory, capped at the twenty most recent so a
long-lived process does not accumulate them. Given a store, the runner also
records each run and every finished row in the database, and answers for a
run it does not hold in memory from there: a run started by another task,
or by a process since replaced. A run known to neither reaches the page as
an id it kept and a run nobody can answer for. That is never silence â€” the
route 404s and the page reports the run as gone, because a batch of forty
figures quietly becoming thirty-nine is the failure this feature exists to
prevent.
é    )ÚannotationsN)ÚOrderedDict)Ú	dataclassÚfield)ÚAnyÚAsyncIteratorÚ	AwaitableÚCallableÚDictÚListÚOptionalÚProtocol)Ú	BaseModel)Úget_settingsé   >   é  é­  éô  éö  é÷  é   g       @c                  ób   • \ rS rSrSrS
S jr            SS jrSS jrSS jrSS jr	Sr
g	)ÚRunStoreéB   zyThe durable side of a run: what the runner writes as it goes and what
it reads back for a run it does not hold in memory.c                ó   • g ©N© )ÚselfÚjob_idÚdoc_idÚwantÚ	questionss        Ú4/home/mande/repo/quber/src/quber/playground/batch.pyÚsave_runÚRunStore.save_runF   s   € Ð[^ó    c                ó   • g r   r   )r   r   ÚindexÚquestionÚerrorÚresponses         r#   Úsave_rowÚRunStore.save_rowG   s   € àr&   c                ó   • g r   r   ©r   r   s     r#   Ú
finish_runÚRunStore.finish_runJ   s   € ¨sr&   c                ó   • g r   r   r/   s     r#   Úload_runÚRunStore.load_runK   s   € Àr&   c                ó   • g r   r   ©r   r    s     r#   Úrunning_forÚRunStore.running_forL   s   € °r&   r   N)
r   Ústrr    Úintr!   r9   r"   ú	List[str]ÚreturnÚNone)r   r9   r(   r:   r)   r9   r*   úOptional[str]r+   úOptional[Dict[str, Any]]r<   r=   )r   r9   r<   r=   ©r   r9   r<   r?   ©r    r:   r<   Úbool)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r$   r,   r0   r3   r7   Ú__static_attributes__r   r&   r#   r   r   B   sM   † ñ;ô _ðØðØ"%ðØ14ðØ=JðØVnðà	ôô 3ÜDß3r&   r   c                ó–   • U R                  5        Vs/ s H)  oR                  5       (       d  M  UR                  5       PM+     sn$ s  snf )u  One question per line, as the batch screen's prompt window states.

Lines are trimmed and blank lines dropped. A repeated line is kept â€” a
person who pasted a question twice gets two rows, because a question that
silently vanishes from a run is exactly what a batch must never do.
)Ú
splitlinesÚstrip)ÚtextÚlines     r#   Úparse_questionsrN   O   s3   € ð &*§_¡_Ô%6ÓGÒ%6˜T¿*¹*¿,‹LˆD�J‰JŽLÑ%6ÑGÐGùÒGs
   “A¯Ac                  óJ   • \ rS rSr% SrS\S'   S\S'   SrS\S	'   SrS
\S'   Srg)ÚBatchRowéY   zjOne finished question. Either `response` (the /api/chat contract,
verbatim) or `error` is set, never both.r:   r(   r9   r)   Nr>   r*   r?   r+   r   )	rC   rD   rE   rF   rG   Ú__annotations__r*   r+   rH   r   r&   r#   rP   rP   Y   s(   ‡ ñ0ð ƒJØƒMØ€Eˆ=ÓØ)-€HÐ&Ö-r&   rP   c                  ój   • \ rS rSr% SrS\S'   S\S'   S\S'   S\S'   S\S	'   S\S
'   S\S'   S\S'   Srg)ÚRunStateéc   zeWhat `GET /api/batch/{id}` returns: the run's counts and the rows
finished so far, in question order.r9   r   r:   r    r!   ÚtotalÚdoneÚfailedrB   ÚrunningzList[BatchRow]Úrowsr   N)rC   rD   rE   rF   rG   rR   rH   r   r&   r#   rT   rT   c   s2   ‡ ñ+ð ƒKØƒKØ
ƒIØƒJØ
ƒIØƒKØƒMØ
Ör&   rT   c                  ó¸   • \ rS rSr% S\S'   S\S'   S\S'   S\S'   \" \S	9rS
\S'   SrS\S'   Sr	S\S'   \" \
S	9rS\S'   \SS j5       rSS jrSS jrSS jrSrg)Ú_Runéq   r9   r   r:   r    r!   r;   r"   )Údefault_factoryzDict[int, BatchRow]rZ   r   rX   NzOptional[asyncio.Task[None]]Útaskz-List[asyncio.Queue[Optional[Dict[str, Any]]]]Úsubscribersc                ój   • U R                   S L=(       a    U R                   R                  5       (       + $ r   )r_   rW   )r   s    r#   rY   Ú_Run.running|   s$   € à�y‰y Ð$×=¨T¯Y©Y¯^©^Ó-=Ô)=Ð=r&   c                ó<  • [        U R                  U R                  U R                  [	        U R
                  5      [	        U R                  5      U R                  U R                  [        U R                  5       Vs/ s H  oR                  U   PM     snS9$ s  snf )N©r   r    r!   rV   rW   rX   rY   rZ   )
rT   r   r    r!   Úlenr"   rZ   rX   rY   Úsorted)r   Úis     r#   ÚstateÚ
_Run.state€   sq   € ÜØ—;‘;Ø—;‘;Ø—‘Ü�d—n‘nÓ%Ü�T—Y‘Y“Ø—;‘;Ø—L‘LÜ(.¨t¯y©yÔ(9Ó:Ò(9 1—)‘)˜A”,Ñ(9Ñ:ñ	
ð 		
ùò ;s   Á<Bc                óL   • U R                    H  nUR                  U5        M     g r   ©r`   Ú
put_nowait)r   ÚeventÚqs      r#   ÚpublishÚ_Run.publishŒ   s   € Ø×!Ô!ˆAØ�L‰L˜Öò "r&   c                óL   • U R                    H  nUR                  S 5        M     g r   rk   )r   rn   s     r#   Úclose_subscribersÚ_Run.close_subscribers�   s   € Ø×!Ô!ˆAØ�L‰L˜Öò "r&   r   )r<   rB   )r<   rT   )rm   úDict[str, Any]r<   r=   )r<   r=   )rC   rD   rE   rF   rR   r   ÚdictrZ   rX   r_   Úlistr`   ÚpropertyrY   rh   ro   rr   rH   r   r&   r#   r\   r\   q   sm   ‡ àƒKØƒKØ
ƒIØÓÙ %°dÑ ;€DÐ
Ó;Ø€FˆCƒOØ)-€DÐ
&Ó-ÙAFÐW[ÑA\€KÐ>Ó\àó>ó ð>ô

ô ÷r&   r\   c                  óÊ   • \ rS rSrSrS\S4       SS jjrSS jr            SS jrSS jr	SS jr
\SS	 j5       rSS
 jrSS jrSS jrSS jrSS jrSS jrSrg)ÚBatchRunneré•   z)The run registry. One per server process.Nc                óŠ   • Ub  UO[        5       R                  R                  U l        X l        [        5       U l        X0l        g r   )r   Ú
playgroundÚquestion_concurrencyÚ_concurrencyÚ	_max_runsr   Ú_runsÚ_store)r   ÚconcurrencyÚmax_runsÚstores       r#   Ú__init__ÚBatchRunner.__init__˜   s9   € ð 'Ñ2‰K¼»×8QÑ8Q×8fÑ8fð 	Ôð "ŒÜ-8«]ˆŒ
Ø�r&   c                óš   • U R                   R                  5        Vs/ s H!  oR                  (       d  M  UR                  PM#     sn$ s  snf )z*The runs this process is still working on.)r€   ÚvaluesrY   r   ©r   Úruns     r#   Úrunning_idsÚBatchRunner.running_ids¨   s3   € à&*§j¡j×&7Ñ&7Ô&9ÓIÒ&9˜s¿[½[“
�—
”
Ñ&9ÑIÐIùÒIs
   �AµAc                óÌ  • [         R                  " 5       R                  SS n[        XaX2S9nU R                  b  U R                  R                  XaX25        XpR                  U'   [        U R                  5      U R                  :”  a–  U R                  R                  SS9u  p‰U	R                  b9  U	R                  R                  5       (       d  U	R                  R                  5         U	R                  5         [        U R                  5      U R                  :”  a  M–  [        R                  " 5       R!                  U R#                  XtU5      5      Ul
        UR%                  5       $ )z9Register a run and schedule it on the running event loop.Né   )r   r    r!   r"   F)Úlast)ÚuuidÚuuid4Úhexr\   r�   r$   r€   re   r   Úpopitemr_   rW   Úcancelrr   ÚasyncioÚget_running_loopÚcreate_taskÚ_runrh   )
r   r    r"   r!   Úretrieve_oneÚ
answer_oner   rŠ   Ú_Úevicteds
             r#   ÚstartÚBatchRunner.start¬   s  € ô —’“×!Ñ! # 2Ð&ˆÜ˜&°dÑPˆØ�;‰;Ñ"Ø�K‰K× Ñ  °ÔAØ �
‰
�6ÑÜ�$—*‘*‹o §¡Ó.ØŸ™×+Ñ+°Ð+Ð7‰JˆAØ�|‰|Ñ'°·±×0AÑ0A×0CÑ0CØ—‘×#Ñ#Ô%Ø×%Ñ%Ô'ô	 �$—*‘*‹o §¡Õ.ô
 ×+Ò+Ó-×9Ñ9¸$¿)¹)ÀCÐWaÓ:bÓcˆŒØ�y‰y‹{Ðr&   c                ó´   • U R                   R                  U5      nUb  UR                  5       $ U R                  U5      nU(       a  U R	                  U5      $ S $ r   )r€   Úgetrh   Ú_storedÚ_state_from_stored)r   r   rŠ   Ústoreds       r#   r    ÚBatchRunner.get½   sK   € Ø�j‰j�n‰n˜VÓ$ˆØ‰?Ø—9‘9“;ÐØ—‘˜fÓ%ˆÞ28ˆt×&Ñ& vÓ.ÐB¸dÐBr&   c                óV   • U R                   b  U R                   R                  U5      $ S $ r   )r�   r3   r/   s     r#   r¡   ÚBatchRunner._storedÄ   s%   € Ø/3¯{©{Ñ/Fˆt�{‰{×#Ñ# FÓ+ÐPÈDÐPr&   c                ó°   • U S    Vs/ s H  n[        S	0 UD6PM     nn[        U S   U S   U S   [        U S   5      [        U5      U S   U S   US9$ s  snf )
NrZ   r   r    r!   r"   rX   rY   rd   r   )rP   rT   re   )r£   ÚrrZ   s      r#   r¢   ÚBatchRunner._state_from_storedÇ   sp   € à'-¨f¢~Ó6¢~ !”‘˜1”¡~ˆÐ6ÜØ˜(Ñ#Ø˜(Ñ#Ø˜‘Ü�f˜[Ñ)Ó*Ü�T“Ø˜(Ñ#Ø˜9Ñ%Øñ	
ð 		
ùò 7s   ˆAc                óÊ   ^• [        U4S jU R                  R                  5        5       5      (       a  gU R                  SL=(       a    U R                  R	                  T5      $ )uª   Whether any run against this document is still going â€” what lets
a document removal or replacement refuse instead of pulling the
corpus out from under a run mid-flight.c              3  óf   >#   • U  H&  oR                   T:H  =(       a    UR                  v •  M(     g 7fr   )r    rY   )Ú.0rŠ   r    s     €r#   Ú	<genexpr>Ú*BatchRunner.running_for.<locals>.<genexpr>Ù   s&   øé € ÐSÒ?R¸�z‰z˜VÑ#×3¨¯©Ô3Ò?Rùs   ƒ.1TN)Úanyr€   rˆ   r�   r7   r6   s    `r#   r7   ÚBatchRunner.running_forÕ   sI   ø€ ô ÔS¸t¿z¹z×?PÑ?PÔ?RÓS×SÑSØØ�{‰{ $Ð&×J¨4¯;©;×+BÑ+BÀ6Ó+JÐJr&   c               ó.  #   • U R                   R                  U5      nUc!  U R                  U5        Sh  v•N nU7v •  M  UR                  (       d  U R	                  U5      7v •  g[
        R                  " 5       nUR                  R                  U5          UR                  5       I Sh  v•N nUc  OU7v •  UR                  S5      S:X  a  OM9  UR                  R                  U5        g N¸
 g NE! UR                  R                  U5        f = f7f)zÃYield one event per question completing, then a final `done` event.

Subscribing to a finished run yields the `done` event immediately, so
a page that attaches late still learns the run is over.
NÚtyperW   )
r€   r    Ú_stored_eventsrY   Ú_done_eventr•   ÚQueuer`   ÚappendÚremove)r   r   rŠ   rm   Úqueues        r#   ÚeventsÚBatchRunner.eventsÝ   så   é € ð �j‰j�n‰n˜VÓ$ˆØ‰;Ø#×2Ñ2°6Ô:÷ �eØ•à�{�{Ø×"Ñ" 3Ó'Ó'ØÜ9@¿º»ˆØ�‰×Ñ˜uÔ%ð		*ØØ#Ÿi™i›k×)�Ø‘=ØØ“Ø—9‘9˜VÓ$¨Ó.Øñ ð �O‰O×"Ñ" 5Õ)ñ#Ð:àñ *øð �O‰O×"Ñ" 5Õ)üsP   ‚0D²C1¶C/·C1ºADÂC5 Â-C3Â.%C5 ÃDÃ/C1Ã1DÃ3C5 Ã5DÄDc               ó¾  #   • [        5       n U R                  U5      nUc  g[        US   5      nUS    HY  nUS   U;   a  M  UR                  US   5        SUS   U[        U5      US   US   SL [	        S0 UD6R                  SS	9S
.7v •  M[     US   (       d  SU[        US   5      US   S.7v •  g[        R                  " [        5      I Sh  v•N   MÍ   N7f)zEvents for a run another process is working on, read from the
store: each row as it lands there, then done once the run closes.Nr"   rZ   r(   ÚrowrX   r*   Újson©Úmode©r²   r(   rV   rW   rX   Úokr¼   rY   rW   ©r²   rV   rW   rX   r   )	Úsetr¡   re   ÚaddrP   Ú
model_dumpr•   ÚsleepÚSTORE_POLL_SECONDS)r   r   Úseenr£   rV   r¼   s         r#   r³   ÚBatchRunner._stored_eventsø   sü   é € ô ›ˆØØ—\‘\ &Ó)ˆFØ‰~ØÜ˜˜{Ñ+Ó,ˆEØ˜f”~�Ø�w‘< 4Ó'ÙØ—‘˜˜W™Ô&à!Ø  ™\Ø"Ü ›IØ$ XÑ.Ø˜g™,¨$Ð.Ü#™? c™?×5Ñ5¸6Ð5ÐBñõ ñ	 &ð ˜)×$à"Ø"Ü  v¡Ó/Ø$ XÑ.ñ	ó ð Ü—-’-Ô 2Ó3×3Ð3ñ5 ñ4 4ùs   ‚CCÃCÃCc                óp   • S[        UR                  5      [        UR                  5      UR                  S.$ )NrW   rÂ   )re   r"   rZ   rX   r‰   s     r#   r´   ÚBatchRunner._done_event  s-   € àÜ˜Ÿ™Ó'Ü˜Ÿ™“MØ—j‘jñ	
ð 	
r&   c              ƒ  óâ  ^ ^^^^^^^#   • T R                   n[        R                  " SU-  5      m[        R                  " U5      m[        R                  " U5      mSUUUUUUU 4S jjm [        R                  " U4S j[	        TR
                  5       5       6 I S h  v•N   T R                  b%  T R                  R                  TR                  5        TR                  T R                  T5      5        TR                  5         g  Ng! T R                  b%  T R                  R                  TR                  5        TR                  T R                  T5      5        TR                  5         f = f7f)Né   c              “  óò  >#   • T IS h  v•N   [        XS9n T	 IS h  v•N   TR                  TU5      I S h  v•N nS S S 5      IS h  v•N   T IS h  v•N   TR                  TUW5      I S h  v•N Ul        S S S 5      IS h  v•N   UT
R                  U '   TR                  b<  TR                  R                  T
R                  XUR
                  UR                  5        T
R                  SU [        T
R                  5      [        T
R                  5      T
R                  UR
                  S L UR                  SS9S.5        S S S 5      IS h  v•N   g  GNJ GN8 GN! GN! , IS h  v•N  (       d  f       GN*= f GN' GN Nü! , IS h  v•N  (       d  f       GN= f! [         a0  n[	        U5      Ul        T
=R                  S-  sl         S nAGNJS nAff = f NŒ! , IS h  v•N  (       d  f       g = f7f)N)r(   r)   é   r¼   r½   r¾   rÀ   )rP   Ú_with_retryr+   Ú	Exceptionr9   r*   rX   rZ   r�   r,   r   ro   re   r"   rÅ   )r(   r)   r¼   ÚcontextÚexcrš   Ú	answeringÚ	in_flightr™   Ú
retrievingrŠ   r   s        €€€€€€€r#   ÚoneÚBatchRunner._run.<locals>.one)  sW  øé € ß ’yÜ UÑ>�ð	$ß)šzØ(,×(8Ñ(8¸ÀxÓ(P×"P˜÷  *Ÿzç(šyØ-1×-=Ñ-=¸jÈ(ÐT[Ó-\×'\˜œ÷  )Ÿyð #&�—‘˜‘Ø—;‘;Ñ*Ø—K‘K×(Ñ(¨¯©°UÀcÇiÁiÐQT×Q]ÑQ]Ô^ð —‘à %Ø!&Ü!$ S§]¡]Ó!3Ü # C§H¡H£Ø"%§*¡*Ø!Ÿi™i¨4Ð/Ø"Ÿ~™~°6˜~Ð:ñô
÷# !—y“yò  *Ú"P÷  *ŸzŸzšzúâ(Ú'\÷  )ŸyŸy™yûä ó $ô !$ C£�C”IØ—J’J !‘O—J“Jûð	$ú÷ !—y—y�yüs"  ƒG7ŠE‹G7Ž
G™FŸE F£E ¹EºE ¾FÁ	EÁ

FÁE;ÁFÁFÁ/E>Á0	FÁ9FÂFÂFÂ	B:GÅG7ÅGÅG7ÅFÅE ÅFÅ E8	Å&E)Å'E8	Å3	FÅ>FÆFÆF	Æ	FÆ
F	ÆFÆGÆFÆ
GÆ(%GÇGÇGÇGÇG7ÇG4Ç#G&Ç$G4Ç0G7c              3  ó8   >#   • U  H  u  pT" X5      v •  M     g 7fr   r   )r¬   rg   rn   r×   s      €r#   r­   Ú#BatchRunner._run.<locals>.<genexpr>H  s   øé € Ð"RÒ9Q±°¡3 q§9 9Ò9Qùs   ƒ)r(   r:   r)   r9   r<   r=   )r~   r•   Ú	SemaphoreÚgatherÚ	enumerater"   r�   r0   r   ro   r´   rr   )	r   rŠ   r™   rš   ÚnrÔ   rÕ   r×   rÖ   s	   ```` @@@@r#   r˜   ÚBatchRunner._run   s  ÿé € Ø×Ñˆô ×%Ò% a¨!¡eÓ,ˆ	Ü×&Ò& qÓ)ˆ
Ü×%Ò% aÓ(ˆ	÷	õ 	ð<	$Ü—.’.Ô"R¼À3Ç=Á=Ô9QÓ"RÐS×SÐSà�{‰{Ñ&Ø—‘×&Ñ& s§z¡zÔ2Ø�K‰K˜×(Ñ(¨Ó-Ô.Ø×!Ñ!Õ#ñ Tøà�{‰{Ñ&Ø—‘×&Ñ& s§z¡zÔ2Ø�K‰K˜×(Ñ(¨Ó-Ô.Ø×!Ñ!Õ#üs2   ŠA E/Á+3D ÂDÂD Â#A#E/ÄD ÄA$E,Å,E/c              ‡  ó:  #   • [         n[        [        5       H  n U" U6 I S h  v•N s  $    [        S5      e N! [         aT  n[	        USS 5      nU[
        ;  d  U[        S-
  :X  a  e [        R                  " U5      I S h  v•N    US-  n S nAM{  S nAff = f7f)NÚstatus_coderÏ   rÍ   Úunreachable)	ÚFIRST_RETRY_DELAYÚrangeÚMAX_ATTEMPTSrÑ   ÚgetattrÚRETRY_STATUSr•   rÆ   ÚRuntimeError)r   ÚstageÚargsÚdelayÚattemptrÓ   Ústatuss          r#   rÐ   ÚBatchRunner._with_retryO  sŒ   é € Ü!ˆÜœ\Ö*ˆGðÙ" D˜\×)Ò)ñ +ô ˜=Ó)Ð)ñ *øÜó Ü   m°TÓ:�Ø¤Ó-°¼LÈ1Ñ<LÓ1LØÜ—m’m EÓ*×*Ñ*Ø˜‘
–ûðüsC   ‚Bœ:¤8¥:¨B¸:º
BÁ>BÂBÂ
BÂBÂBÂB)r~   r   r€   r�   )r‚   zOptional[int]rƒ   r:   r„   zOptional[RunStore]r<   r=   )r<   r;   )r    r:   r"   r;   r!   r9   r™   ÚRetrieveOnerš   Ú	AnswerOner<   rT   )r   r9   r<   zOptional[RunState]r@   )r£   rt   r<   rT   rA   )r   r9   r<   zAsyncIterator[Dict[str, Any]])rŠ   r\   r<   rt   )rŠ   r\   r™   rï   rš   rð   r<   r=   )ré   zCallable[..., Awaitable[Any]]rê   r   r<   r   )rC   rD   rE   rF   rG   ÚMAX_RUNSr…   r‹   r�   r    r¡   Ústaticmethodr¢   r7   r¹   r³   r´   r˜   rÐ   rH   r   r&   r#   ry   ry   •   s·   † Ù3ð &*Ø Ø$(ð	à"ðð ðð "ð	ð
 
õô JðØðØ&/ðØ7:ðØJUðØclðà	ôô"CôQð ó
ó ð
ôKô*ô64ô@
ô-$÷^*r&   ry   )rL   r9   r<   r;   )%rG   Ú
__future__r   r•   r�   Úcollectionsr   Údataclassesr   r   Útypingr   r   r	   r
   r   r   r   r   Úpydanticr   Úquber.settingsr   rñ   rç   rå   rã   r9   rï   rð   rÇ   r   rN   rP   rT   r\   ry   r   r&   r#   Ú<module>rù      sÓ   ðñ õD #ã Û Ý #ß (ß Z× ZÓ Zå å 'ð €ò )€Ø€ØÐ ð
 ˜�u˜i¨™nÐ,Ñ-€Ø�c˜3�Z ¨4°°S°©>Ñ!:Ð:Ñ;€	ð Ð ô
4ˆxô 
4ôHô.ˆyô .ôˆyô ð ÷ ð  ó ð ÷FE*ò E*r&   