ó
    }¬j?  ã            $       óÖ  • S r SSKrSSKrSSKrSSKrSSKJrJr  SSKJ	r	  \R                  R                  \R                  R                  \R                  R                  \5      5      5      r\R                  R                  \SS5      rSrSr\" S	S
9 " S S5      5       rS r\" SSS/SSS	S9\" SSSS/SSSSS	S	SS9
\" SSSS/SSSS	S	S S!9	\" S"S#SS$/S%S&S'S"S	S	S(S9
\" S)S*SS+/S,S-S'S)S	S	S.S9
\" S/S0SS1/S2S3S'S)S	S	S4S9
\" S5S6SS7/S8S9S:S	S;9\" S<S=S>S?/S@SAS'S)S	S	SBS9
\" SCSDSSE/SFSGS'S	S;9\" SHSISSJ/SKSLS'S)S	SM9\" SNSOSSP/SQSRS'S)S	SM9\" SSSTSSU/SVSWS'S	S;9\" SXSYSSZ/S[S\S]SXS	S	S^S9
\" S_S`S>/SaSb5      \" ScSdSe/SfSg5      \" ShSiSjSk/SlSmSnSo9\" SpSqSSr/SsStSuSo9\" SvSwSSx/SySzS	S{9\" S|S}SS~/SS€S�So9\" S‚SƒSS„/S…S†S‡S‚SˆS‰9\" SŠS‹/ SŒQS�SŽ5      \" S�S�SS‘/S’S“S‡S‚S”S‰9\" S•S–S>S—/S˜S™S'S)S	SšS›9	\" SœS�S>Sž/SŸS3S'S)S	SšS›9	\" S S¡S>S¢/S£S¤S¥So9\" S¦S§S>S¨/S©SªSšS«9\" S¬S­S®/S¯S°S	S¬S±94 V s0 s H  o R*                  U _M     sn rS²S³S´SµS¶S·.rS¸ rS¹ r " Sº S»\5      rSÃS½ jrS¼SS¾.rS¿ rSÄSÀ jrSÅSÁ jr SÂ r!gs  sn f )Æa^  
The one registry of everything BidBrain can run, and the one way a run
reports and finishes (Steven 2026-09-02: "check the way all of the runs are
set up... it all seems sporadic, sometimes runs work, sometimes they dont,
sometimes the icon shows things its not supposed to... the maintenance
checks dont always act like the auction house runs. it needs simplified and
work correctly, consistently and look the same across all screens").

Before this, the same 17 runs were described in five hand written maps in
serve.py (proc slots, stop keys, labels, sites, gates) that had drifted
apart, and finished through four separate helpers in four scripts. Now:

  RUNS         one entry per run: key (the db.run_status key, used
               everywhere, never renamed on the way), label, the argv
               that starts it, the connection it belongs to, the Settings
               toggle that gates it, whether it opens a browser window,
               and its scope (bulk, row, login).
  progress()   the shared progress file write (was daily_run._progress).
  finish()     the terminal write: progress AND the durable run record in
               one call (was daily_run._finish, purchases_run._finish_sync,
               lost_bids_run._finish, share_comps._record). A row scoped
               run writes progress only, by design: one car's problem
               lives on the purchases page's own bar, never the app wide
               light.
  guard()      wraps a script's entry point so it CANNOT end without a
               terminal write: a normal return, a RunSkipped (switched off
               in Settings, nothing to do), any exception, SystemExit and
               Ctrl-C all land in finish(). "skipped" is a real phase, grey,
               never red: off is not broken.

Pure data plus file and db writes, no browser, importable by every script
and by serve.py.
é    N)Ú	dataclassÚfield)ÚdbÚdatazrun_progress.json)ÚstockÚreadingÚvaluingÚphotosÚwritingzglass-loginzclickdealer-loginzdealerkit-login)ÚdoneÚerrorÚskippedÚstoppedT)Úfrozenc                   óª   • \ rS rSr% \\S'   \\S'   \\S'   \\S'   \\S'   Sr\\S'   Sr\\S	'   S
r	\
\S'   Sr\\S'   S
r\
\S'   Sr\\S'   S
r\
\S'   Srg)ÚRuné6   ÚkeyÚlabelÚargvÚbusyÚstartingNÚsiteÚgateFÚheadedÚbulkÚscopeÚwinÚ ÚconfirmÚquiet© )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__ÚstrÚ__annotations__Útupler   r   r   Úboolr   r   r    r!   Ú__static_attributes__r"   ó    Ú./Users/stevendouglas/BidBrain/bidbrain/runs.pyr   r   6   sa   ‡ à	ƒHØƒJØ
ƒKØ
ƒIØƒMØ€Dˆ#ÓØ€Dˆ#ÓØ€FˆDÓØ€Eˆ3ÓØ€CˆÓØ€GˆSÓØ€Eˆ4Ör,   r   c           	      ó2   • [        SX[        U5      X4S.UD6$ )N)r   r   r   r   r   r"   )r   r)   )r   r   r   r   r   Úkws         r-   Ú_rr0   F   s   € ÜÐZ�3¬%°«+¸DÑZÐWYÑZÐZr,   ÚrunzThe daily runzdaily_run.pyzA daily run is in progress.zStarting a daily run...)r   ÚglasszGlass's checksz--glasszA Glass's pass is in progress.zStarting a Glass's pass...z€Run Glass's checks now? A Glass's login window opens on the Mac. Log in there, then it adds Glass's to these cars and re prices.)r   r   r   r   r    ÚclickdealerzClickdealer stockz--clickdealerz(A Clickdealer stock pass is in progress.z$Starting a Clickdealer stock pass...zWRun the Clickdealer stock refresh now? A Clickdealer window opens on the Mac if needed.)r   r   r   r    Údealerkit_stockzDealerKit stockz--dealerkitz&A DealerKit stock pass is in progress.z"Starting a DealerKit stock pass...Ú	dealerkitzµRun the DealerKit stock refresh now? A DealerKit login window opens on the Mac if needed, then it refreshes the in stock and gap flags. Keeps the existing valuations, no re valuing.Údealerkit_purchaseszDealerKit purchases pushz--dealerkit-purchasesz*A DealerKit purchases push is in progress.z&Starting a DealerKit purchases push...a  Push new purchases into DealerKit now? A DealerKit login window opens on the Mac if needed, then every purchase not already there gets added as a Due In record with its photos, service history documents and purchase price. Retail price is left for you to set.Údealerkit_checkzThe DealerKit record checkz--dealerkit-checkz!A DealerKit check is in progress.zStarting a DealerKit check...zˆCheck every purchase against its real DealerKit record now? This opens a DealerKit window and reads them all, it can take a few minutes.Úmotorway_probezMotorway one off checkz--motorway-probez A Motorway check is in progress.z,Checking Motorway's export and a car page...Úmotorway)r   r!   Údealerkit_surveyzDealerKit costs screen surveyzpurchases_run.pyz--survey-dealerkitz/A DealerKit costs screen survey is in progress.z&Looking at DealerKit's costs screen...z‹Look at DealerKit's costs screen for one car and write every step down? A DealerKit window opens on the Mac. Nothing is saved on DealerKit.Údealerkit_probezDealerKit API probez--dealerkit-probez%A DealerKit API probe is in progress.zProbing DealerKit's APIs...Údealerkit_stageszDealerKit stage checkz--dealerkit-stagesz'A DealerKit stage check is in progress.zChecking DealerKit's stages...)r   r   r!   Údealerkit_sale_priceszDealerKit sale price readz--dealerkit-sale-pricesz+A DealerKit sale price read is in progress.z#Reading what a sold car went for...Úsales_backfillzSales history backfillz--sales-backfillz(A sales history backfill is in progress.z'Sending every past sale to Dealer OS...Úlecapital_fundingzLE Capital funding syncz--lecapital-fundingz*An LE Capital funding sync is in progress.z&Starting an LE Capital funding sync...Ú	lecapitalaN  Sync DealerKit funding with LE Capital now? A window opens on the Mac for LE Capital and, if needed, DealerKit logins, then it reads LE Capital's Current Stock and Stock History and adds or settles DealerKit funding records to match. A value disagreement on a car live on both sides is only ever reported, never changed automatically.Ú	purchaseszPurchases syncz A purchases sync is in progress.zStarting a purchases sync...Ú	lost_bidszDidn't win synczlost_bids_run.pyz!A Didn't win sync is in progress.zStarting a Didn't win sync...Ú	compmatchzCompMatch synczshare_comps.pyz--syncz A CompMatch sync is in progress.zStarting a CompMatch sync...a8  Sync CompMatch data with the other dealership on this repo? Pulls their latest comps, then commits and pushes your own (make/model, winning price, retail, mileage, age, transmission, engine size and grade only, never a registration, price breakdown or margin). Needs DEALER_NAME set in your own dealer_config.py.)r    Ú	watchlistzWatchlist syncz--watchlist-syncz A watchlist sync is in progress.zStarting a watchlist sync...zASync your starred cars to the auction houses' own watchlists now?Ú	deep_readzShortlist deep readz--deep-readz%A shortlist deep read is in progress.z*Reading the shortlisted cars' own pages...)r!   Úunder_offerzUnder offer checkz--under-offerz$An under offer check is in progress.z Starting an under offer check...z`Check Motorway and Carwow for cars under offer now? Reads both platforms, takes a minute or two.Údealer_os_pushzDealer OS pushz--dealer-os-pushz A Dealer OS push is in progress.zStarting a Dealer OS push...Ú	dealer_osz›Send the current list (shortlisted and held cars, with prices) into Dealer OS now? This happens automatically after every run, so only use this to re send.)r   r   r    Úinstall_schedulezTimed jobs setup)zsetup_schedule.pyz	--installrB   rF   z The timed jobs are being set up.z7Setting up the Didn't win and Under offer timed jobs...Údealer_os_pullzDealer OS syncz--dealer-os-pullz A Dealer OS sync is in progress.zStarting a Dealer OS sync...z«Collect stars, hides, views and run requests made in Dealer OS now? This happens on its own every few seconds while the cockpit server is up, so only use this to force it.Údealerkit_push_rowzDealerKit pushz--push-dealerkitz,A DealerKit push for one car is in progress.zStarting a DealerKit push...Úrow)r   r   r   r   Údealerkit_check_rowzDealerKit record checkz--check-dealerkitz-A DealerKit check for one car is in progress.Úretail_repricezRe price retail estimatesz--reprice-retailz#A retail re pricing is in progress.zRe pricing retail estimates...zãRe price every live car's Retail est. with this dealership's own margin and price ending from Settings? A figure typed in by hand is left alone. A Due in car on DealerKit has the new figure sent there by the next automatic run.Úpurchase_checkzPurchase re readz--check-purchasez"A purchase re read is in progress.zStarting a purchase re read...)r   ÚloginzLogin windowzlogin.pyzA login window is open.zOpening a login window...)r   r   ÚMotorwayÚCarwowÚAuction4CarszDealer AuctionÚ	DealerWay)r9   ÚcarwowÚauction4carsÚdealerauctionÚ	dealerwayc                 óf   • U [         ;   a  [         U    R                  $ [        R                  X 5      $ )z>What a run_status key is called on screen, platforms included.)ÚRUNSr   ÚPLATFORM_LABELSÚget©r   s    r-   Ú	label_forr^   ¬   s(   € à
Œdƒ{Ü�C‰y�‰ÐÜ×Ñ˜sÓ(Ð(r,   c                  óô   • [         R                  5        V Vs0 s HQ  u  pUR                  S:w  d  M  XR                  UR                  UR
                  UR                  UR                  S._MS     snn $ s  snn f )ztThe part of the registry a page needs: label, whether to confirm and
with what, and whether it is one car's own job.rP   )r   r    r   r   r   )rZ   Úitemsr   r   r    r   r   )ÚkÚrs     r-   Úclient_registryrc   ³   se   € ô
 Ÿ
™
œô<â$‘�¨¯©°7Ñ(:ó:ˆAŸ™¨Q¯Y©YÀ!Ç(Á(ØŸ™¨a¯j©jñ:ò :á$ò<ð <ùó <s
   ˜A4²>A4c                   ó   • \ rS rSrSrSrg)Ú
RunSkippedé»   zËRaised by a run that has nothing to do and should say so without
being a failure: switched off in Settings, not configured on this Mac,
no saved run to work on. Lands as phase "skipped", grey, never red.r"   N)r#   r$   r%   r&   Ú__doc__r+   r"   r,   r-   re   re   »   s   † ôKr,   re   Fc                 ó$  •  [        [        SSS9 n[        R                  " XUU[	        U5      [
        R
                  " 5       U=(       d    [        R                  S5      S.U5        SSS5        g! , (       d  f       g= f! [         a     gf = f)aŽ  Write the shared progress file every page's bar and light read. Best
effort: reporting must never break the run it reports on. `key` names
the pass the line belongs to (begin() sets it for the whole pass), so
Dealer OS can say "Purchases sync: reading..." rather than a bare line
and can tell which pass just finished (Steven 2026-09-08: "it needs to
be more clear what is happening at all times").Úwzutf-8)Úencodingr   )ÚphaseÚmessager   ÚtotalÚpartialÚtsr   N)	ÚopenÚPROGRESSÚjsonÚdumpr*   ÚtimeÚ_stater\   Ú	Exception)rk   rl   r   rm   rn   r   Úfs          r-   Úprogressrx   Á   sn   € ðÜ”(˜C¨'Ò2°aÜ�IŠI À4Ø %´$°w³-ÄtÇyÂyÃ{Ø!×6¤V§Z¡Z°Ó%6ñ8à9:ô<÷ 3×2Ö2ûô ó Ùðús/   ‚B ‘AA1Á(B Á1
A?Á;B Á?B Â
BÂB)Úfinishedr   c                 ó   • U [         S'   g)zKName the pass this process is, so every progress line it writes
carries it.r   N)ru   r]   s    r-   Úbeginr{   Ô   s   € ð „Fˆ5‚Mr,   c                 ó>  • U(       a  SOUc  SOSn[         R                  U 5      nU [        S'   Ub  UR                  (       d  [	        XRX45        S[        S'   Ub  UR
                  S:w  a  g [        R                  " XS	LUb  UOS
U 35        g! [         a     gf = f)a  The end of one run, both halves at once: the terminal progress write
the bar and light watch for, and the durable record the Run menu's
board shows. ok=True is done, False is error, None is skipped. A row
scoped run writes no record (its outcome belongs to its own page).r   Nr   r   r   Try   r   Fz	Skipped. )	rZ   r\   ru   r!   rx   r   r   Úrecord_run_statusrv   )r   Úokrl   r   rm   rk   r1   s          r-   Úfinishr   Ú   s—   € ö
 ‰F¨B©J™y¸G€EÜ
�(‰(�3‹-€CØ„Fˆ5�MØ‰O §	§	Ü� Ô-Ø„Fˆ:ÑØ
�˜3Ÿ9™9¨Ó.ØðÜ
×Ò˜S¨E /Ø(*©™W¸iÈÀyÐ<Qõ	Søäó Ùðús   Á-!B Â
BÂBc                 ó¦   • [        U 5      =(       d    SR                  5       R                  5       nU(       a  US   R                  5       S U $ SS U $ )Nr   r   )r'   ÚstripÚ
splitlines)ÚtextÚcapÚliness      r-   Ú
first_liner†   î   sG   € Ü�‹Y�_˜"×#Ñ#Ó%×0Ñ0Ó2€EÞ %ˆE�!‰H�N‰NÓ¨t°Ð4Ð4¨2¨t°Ð4Ð4r,   c                 óx  • S[         S'    U" 5         [         S   (       d  [        U SS5        gg! [         a)  n[        U S[        U5      =(       d    S5         SnAgSnAf[         a\  n[         S   (       dH  [        UR                  [        5      (       a  UR                  OSn[        XS:H  US:X  a  SOSU S	35        e SnAf[         a    [         S   (       d  [        U SS
5        e [         aF  n[         S   (       d2  [        U S[        U5      =(       d    [        U5      R                  5        e SnAff = f)zýRun a script's entry point so it cannot end without a terminal write.
A normal return that never called finish() records "Finished"; a
RunSkipped records skipped; anything else records the failure and re
raises so the exit code and the log still say so.Fry   NzNothing to do.é   r   ÚFinishedzStopped early (exit Ú)ÚInterruptedT)ru   re   r   r†   Ú
SystemExitÚ
isinstanceÚcodeÚintÚKeyboardInterruptrv   Útyper#   )r   ÚfnÚerŽ   s       r-   Úguardr”   ó   s  € ð
 „Fˆ:ÑðÙ
Œô" �*×Üˆs�D˜*Õ%ð øô! ó Üˆs�Dœ* Q›-×;Ð+;Ô<ÜûÜó Ü�j×!Ü'¨¯©´×4Ñ4�1—6’6¸!ˆDÜ�3 ™	°¸³¡:ÐBVÐW[ÐV\Ð\]Ð@^Ô_ØûÜó Ü�j×!Ü�3˜˜}Ô-ØÜó Ü�j×!Ü�3˜œz¨!›}×@´°Q³×0@Ñ0@ÔAØûðús.   ‹/ ¯
D9¹AÁD9Á*ACÃ2D9Ã3AD4Ä4D9)r   r   r   FN)r   r   )é    )"rg   rr   ÚosÚsysrt   Údataclassesr   r   Úbidbrainr   ÚpathÚdirnameÚabspathÚ__file__ÚHEREÚjoinrq   ÚACTIVE_PHASESÚTERMINAL_PHASESr   r0   r   rZ   r[   r^   rc   rv   re   rx   ru   r{   r   r†   r”   )rb   s   0r-   Ú<module>r¢      s  ðñ óD Û 	Û 
Û ß (å à	‡w�w‡��r—w‘w—‘ r§w¡w§¡°xÓ'@ÓAÓB€Ø�7‰7�<‰<˜˜fÐ&9Ó:€ð
H€à9€ñ �$Ñ÷ð ó ðò[ñ
 €uˆo Ð/Ø$Ð&?ÈñNá€wÐ  >°9Ð"=Ø'Ð)EØ˜'¨$°Dð RñSñ €}Ð)¨N¸OÐ+LØ1Ð3YØ $¨DØhñjñ ÐÐ+¨n¸mÐ-LØ/Ð1UØÐ/¸À$ð GñHñ ÐÐ8¸>ÐKbÐ:cØ3Ð5]ØÐ3¸DÀdð UñVñ ÐÐ6¸ÐI\Ð8]Ø*Ð,KØÐ3¸DÀdð Zñ[ñ ÐÐ1°NÐDVÐ3WØ)Ð+YØ˜dñ$ñ ÐÐ:Ð=OÐQeÐ<fØ8Ð:bØÐ3¸DÀdð ]ñ^ñ ÐÐ/°.ÐBUÐ1VØ.Ð0MØ˜tñ%ñ ÐÐ2°^ÐEYÐ4ZØ0Ð2RØÐ3¸4ñAñ ÐÐ ;¸nÐNgÐ=hØ4Ð6[ØÐ3¸4ñAñ ÐÐ1°NÐDVÐ3WØ1Ð3\Ø˜tñ%ñ ÐÐ5¸ÐH]Ð7^Ø3Ð5]ØÐ1¸$ÀDð `ñañ €{Ð$Ð'9Ð&:Ø)Ð+IóKá€{Ð%Ð(:Ð';Ø*Ð,KóMá€{Ð$Ð'7¸Ð&BØ)Ð+Ið JñKñ €{Ð$ ~Ð7IÐ&JØ)Ð+IØRñTñ €{Ð)¨N¸MÐ+JØ.Ð0\Øññ €}Ð)¨N¸OÐ+LØ-Ð/QØqñsñ ÐÐ)¨NÐ<NÐ+OØ)Ð+IØÐ.ð mñnñ ÐÐ-Ò/mØ)Ð+dófáÐÐ)¨NÐ<NÐ+OØ)Ð+IØÐ.ð }ñ~ñ ÐÐ-Ð0BÐDVÐ/WØ5Ð7UØÐ3¸DÈñOñ ÐÐ6Ð9KÐM`Ð8aØ6Ð8WØÐ3¸DÈñOñ ÐÐ4Ð7IÐK]Ð6^Ø,Ð.Nð uñvñ ÐÐ+Ð.@ÐBTÐ-UØ+Ð-MÐUZñ\á€w�  Ø Ð"=ÀdÐRYñ[ñs[ó [ò [�Q�‰ˆqŠñ [ñ [€ð|  *°XÈ~Ø$4À;ñP€ò)ò<ôK�ô Kôð   DÑ	)€òôô(5ó
&ùòS[s   ÈI&