ó
    º�jìc  ã                   ó6  • S r SSKrSSKrSSKrSSKJr  SSKJr  \R                  " S5      r
\R                  " S5      r\R                  " S5      rS	r/ S
QrSrS rS rS rS rS&S jrS rS&S jrS'S jrS(S jrSrSr\R                  " S5      r\R                  " S5      rS rS rS rS r S)S jr!S*S jr"\R                  " S5      r#\R                  " S 5      r$\R                  " S!5      r%\R                  " S"5      r&\R                  " S#5      r'S$ r(S% r)g)+aÒ  
Dealer Auction reader (Auto Trader's trade auction platform, added 2026-08-20).

Reads the logged in "Dealer Auction" tab of the dealer sourcing portal at
dealerauction.co.uk. Unlike Motorway and Carwow, almost everything needed is
already on the list card itself, registration, grade, owners, VAT status,
retail value and margin, and a time remaining countdown, so there is very
little detail page dependency here.

Three things decided by Mark 2026-08-20:
- No reserve is ever disclosed anywhere on this platform (checked the list
  and a real detail page thoroughly, unlike Auction4Cars, which turned out to
  have one hidden). Retail minus Margin stands in for it instead, labelled
  "AT Trade" on the card (the platform's own two real figures, not a guess).
- Condition grade is the real NAMA 1 to 5 scale, shown directly on the card,
  so this source needs no grade gate exemption, unlike Auction4Cars.
- Distance is never exact miles, only a town plus county string. Mark chose
  a county level allow list (like Auction4Cars' depot list, but coarser,
  county rather than town, since town names would be too many to manage).
  See pricing.gate_failures and _dealerauction_county.

Nothing here bids. It reads the screen only.
é    Né   )Úbrowser)ÚCaru   Â£\s*([\d,]+)z^(19[89]\d|20[0-4]\d)$z*href="#/advert/([A-Za-z0-9]+)(?:\?[^"]*)?"z'button[analyticslabel="paginator-next"])z
LAND ROVERzMERCEDES-BENZzMERCEDES BENZz
ALFA ROMEOzASTON MARTINz
GREAT WALLa  https://www.dealerauction.co.uk/sourcing/#/search?network=trade_independent&network=trade_franchise&network=trade_fleet&network=retail_ready&network=c2b&network=c2b_generic&network=digital_wholesale&network=hertz&network=manheim_digital_first&salesMethod=bidc                 ó    • U c  g [        U 5      R                  SS5      R                  5       n U R                  5       (       a  [	        U 5      $ S $ )NÚ,Ú )ÚstrÚreplaceÚstripÚisdigitÚint©Úss    Ú?/Users/stevendouglas/BidBrain/bidbrain/readers/dealerauction.pyÚ_numr   <   s@   € Ø�yØÜˆA‹�‰�s˜BÓ×%Ñ%Ó'€AØ—Y‘Y—[‘[Œ3ˆq‹6Ð* dÐ*ó    c                 ó‚   • U (       d  g [         R                  U 5      nU(       a  [        UR                  S5      5      $ S $ )Né   )ÚMONEYÚsearchr   Úgroup)r   Úms     r   Ú_moneyr   C   s/   € ÞØÜ�‰�Q‹€AÞ Œ4�—‘˜“
ÓÐ* dÐ*r   c                 óN   • SR                  S U R                  5        5       5      $ )NÚ c              3   óŒ   #   • U  H:  oR                  5       (       a  [        U5      S ::  a  UOUR                  5       v •  M<     g7f)é   N)ÚisupperÚlenÚ
capitalize)Ú.0Úws     r   Ú	<genexpr>Ú_title_case.<locals>.<genexpr>K   s/   é € Ð^ÒT]ÈqŸ)™)Ÿ+™+¬#¨a«&°A«+‘A¸A¿L¹L»NÔJÒT]ùs   ‚AA)ÚjoinÚsplitr   s    r   Ú_title_caser'   J   s!   € Ø�8‰8Ñ^ÐTU×T[ÑT[ÔT]Ó^Ó^Ð^r   c                 óº  • U =(       d    SR                  5       n U R                  5       nU(       d  gU R                  5       nSn[         HQ  nUR                  5       nUR                  5       S[	        U5       U:X  d  M5  US[	        U5       nU[	        U5      S n  O   Uc
  USS nUSS n/ nW HI  n[
        R                  " SU5      (       d  [        S U 5       5      (       a    OUR                  U5        MK     U[	        U5      S n	[        SR                  U5      5      n
U(       a  [        SR                  U5      5      OSnSR                  U	5      nX«U4$ )a¹  MAKE and MODEL are both in shouty caps in the title text, the
derivative is not, for example "LAND ROVER RANGE ROVER SPORT 2.0 P400e
HSE Dynamic..." splits as make "Land Rover", model "Range Rover Sport",
derivative "2.0 P400e...". The boundary is the first token that either
contains a lowercase letter or looks like an engine size ("1.7", "2.0"),
everything before that is still MAKE plus MODEL. This is a good enough
split for display and filtering, not perfect on every trim naming
(matches the same accepted imprecision as auction4cars.py's own split,
it is never used for the hard gate itself, only banned make matching,
which only ever needs the MAKE half, always reliably identified here).r   )r   r   r   Nr   z^\d+\.\dc              3   ó@   #   • U  H  oR                  5       v •  M     g 7f©N)Úislower)r!   Úcs     r   r#   Ú$_split_make_model.<locals>.<genexpr>k   s   é € Ð*BÂ¸1¯9©9¯;¨;Âùs   ‚r   )r   r&   ÚupperÚ_MULTI_WORD_MAKESr   ÚreÚmatchÚanyÚappendr'   r%   )ÚtitleÚtokensÚupÚmake_tokensÚmkÚ	mk_tokensÚrestÚmodel_tokensÚtÚderivative_tokensÚmakeÚmodelÚ
derivatives                r   Ú_split_make_modelrA   N   s@  € ð �[�b×ÑÓ!€EØ�[‰[‹]€FÞØØ	�‰‹€BØ€KßˆØ—H‘H“Jˆ	Ø�8‰8‹:�o”s˜9“~Ð&¨)Õ3Ø  ¤# i£.Ð1ˆKØœ#˜i›.˜/Ð*ˆDÙñ  ð ÑØ˜R˜a�jˆØ�a�bˆzˆà€LÛˆÜ�8Š8�K ×#Ñ#¤sÑ*BÁÓ*B×'BÑ'BÙØ×Ñ˜AÖñ ð œS Ó.Ð/Ð0Ðä�s—x‘x Ó,Ó-€DÞ3?ŒK˜Ÿ™ Ó.Ô/ÀR€EØ—‘Ð+Ó,€JØ˜
Ð"Ð"r   c           	      ó,  • U (       d  g[         R                  " SU 5      n[         R                  " SU 5      n[         R                  " SU 5      nU(       d  U(       d  U(       d  gU=(       d    [        R                  R                  5       n[        R                  " U(       a  [        UR                  S5      5      OSU(       a  [        UR                  S5      5      OSU(       a  [        UR                  S5      5      OSS9nX-   R                  5       $ )a*  "10h 35m", "2d 3h", "45m" etc, added to now to give an absolute end
time for the cockpit's live countdown (same mechanism Auction4Cars uses,
car.auction_ends_at). Returns "" if the text does not parse, so a card
with a reading that cannot be trusted just shows no countdown rather than
a wrong one.r   z	(\d+)\s*dz	(\d+)\s*hz	(\d+)\s*mr   r   )ÚdaysÚhoursÚminutes)r0   r   ÚdatetimeÚnowÚ	timedeltar   r   Ú	isoformat)ÚtextrG   rC   rD   ÚminsÚdeltas         r   Ú_parse_time_remainingrM   v   s¼   € ö ØÜ�9Š9�\ 4Ó(€DÜ�IŠI�l DÓ)€EÜ�9Š9�\ 4Ó(€DÞ–EžTØØ
×
(”×"Ñ"×&Ñ&Ó(€CÜ×ÒÞ#'ŒS�—‘˜A“Ô¨QÞ%*Œc�%—+‘+˜a“.Ô!°Þ&*”�D—J‘J˜q“MÔ"°ñ€Eð
 ‰K×"Ñ"Ó$Ð$r   c                 ó*   • U R                  S5      SS $ )aX  Splits cleanly one block per card, verified against a real captured
page (37 parts for 36 real cards, the first part is the pre amble before
any card). The earlier attempt split on a CSS class that turns out to
repeat inside a card (a "similar listings" widget shares it), which
silently dropped and truncated cards, caught before this shipped.zhref="#/advert/r   N)r&   )Ú	page_htmls    r   Ú_card_blocksrP   Œ   s   € ð �?‰?Ð,Ó-¨a¨bÐ1Ð1r   c           
      óø	  • / n[        5       n[        U 5       GHÝ  n[        R                  " SU5      nU(       a  UR	                  S5      OSnU(       a  Xc;   a  MC  UR                  U5        [        R                  " SU5      nU(       a3  [        R                  " UR	                  S5      5      R                  5       OSn[        U5      u  pšn[        R                  " SU5      nSR                  U(       a  UR	                  S5      OSR                  5       5      R                  5       n[        R                  " SU5      nU(       a  [        UR	                  S5      5      OSn[        R                  " S	U5      nU(       a  UR	                  S5      OSn[        R                  " S
U5      nU(       a*  [        UR	                  S5      R                  SS5      5      OSn[        R                  " SU5      nU(       a  UR	                  S5      OSn[        R                  " SU5      nU(       a  UR	                  S5      OSn[        R                  " SU5      nU(       a  [        UR	                  S5      5      OSn[        R                  " SU5      n[!        U=(       a    UR	                  S5      S:H  5      n[        R                  " SU5      nU(       a  [        UR	                  S5      5      OSn[        R                  " SU5      nU(       a3  [        R                  " UR	                  S5      5      R                  5       OSn[        R                  " SU[        R"                  5      n U (       a  [        U R	                  S5      5      OSn![        R                  " SU[        R"                  5      n"U"(       a  [        U"R	                  S5      5      OSn#U!b  U#b  U!U#-
  OSn$[        R                  " SU[        R"                  5      n%[%        U%(       a  U%R	                  S5      OSUS9n&Sn'[        R&                  " SU5       H:  n(SU(;   d  M  [        R                  " SU(5      n)U)(       a  U)R	                  S5      n'  O   UU(       a  SU-   OS-   R                  5       n*UR)                  [+        S00 SU_SU	_SU
_SU_SU_S U_S!U_S"U_S#U*_S$U_S%U_S&U_S'U_S(U$_S)U_S*U'_S+U&_S,S-U 3_S.S/_65        GMà     U$ )1zåParse one page of the Dealer Auction list into Cars. Registration,
grade, owners, VAT status, retail value and margin are all already on the
card, unlike Motorway and Carwow, so no detail page visit is needed for
the gate itself.z([A-Za-z0-9]+)(?:\?[^"]*)?"r   r   z#title="([^"]+)"[^>]*>\s*([^<]*)</a>r   z.analyticslabel="copy-vrm"[^>]*>\s*([^<]+?)\s*<z<test-advert-combinedYearPlate"[^>]*>\s*(\d{4})\s*\(\d+\)\s*<Nz-test-advert-bodyType"[^>]*>\s*([A-Za-z]+)\s*<z([\d,]+)\s*milesr   z8>\s*(Manual|Automatic|Semi-automatic|Semi automatic)\s*<zK>\s*(Petrol|Diesel|Hybrid|Electric|Petrol Plug-in Hybrid|Petrol Hybrid)\s*<z(\d+)\s*previous owners?z,test-advert-vatStatus"[^>]*>\s*VAT (\w+)\s*<Ú
QualifyingzCondition grade (\d)z%test-location-info"\s+title="([^"]+)"u!   >\s*Retail\s*<.{0,200}?Â£([\d,]+)u!   >\s*Margin\s*<.{0,200}?Â£([\d,]+)z4Time remaining.{0,400}?<strong[^>]*>([^<]*)</strong>)rG   z<img\b[^>]*>zalt="Primary vehicle image"úsrc="([^"]+)"r   Úregr>   r?   r@   ÚyearÚmileageÚtransmissionÚfuelÚengineÚ	body_typeÚownersÚgradeÚvat_qualifyingÚreserveÚlocationÚ	photo_urlÚauction_ends_atÚlisting_urlú2https://www.dealerauction.co.uk/sourcing/#/advert/ÚsourceúDealer Auction© )ÚsetrP   r0   r1   r   Úaddr   ÚhtmllibÚunescaper   rA   r%   r&   r.   r   r
   ÚboolÚSrM   Úfindallr3   r   )+rO   rG   ÚcarsÚids_seenÚblockÚid_mÚ	advert_idr4   Ú
title_textr>   r?   r@   ÚvrmrT   Úyear_mrU   Úbody_mrZ   Ú	mileage_mrV   Útrans_mrW   Úfuel_mrX   Úowners_mr[   Úvat_mr]   Úgrade_mr\   Úloc_mr_   Úretail_mÚretailÚmargin_mÚmarginr^   Útime_mÚends_atr`   Úimg_tagÚsrc_mrY   s+                                              r   Úparse_listingr†   •   s7  € ð
 €DÜ‹u€HÜ˜i×(ˆô �xŠxÐ6¸Ó>ˆÞ%)�D—J‘J˜q”M¨rˆ	Þ˜IÓ1ÙØ�‰�YÔä—	’	Ð@À%ÓHˆÞAF”W×%Ò% e§k¡k°!£nÓ5×;Ñ;Ô=ÈBˆ
Ü"3°JÓ"?Ñˆ�Zä�iŠiÐIÈ5ÓQˆØ�g‰g¦s�s—y‘y ”|°×9Ñ9Ó;Ó<×BÑBÓDˆä—’ÐZÐ\aÓbˆÞ(.Œt�F—L‘L “OÔ$°Dˆä—’ÐKÈUÓSˆÞ'-�F—L‘L ”O°2ˆ	ä—I’IÐ1°5Ó9ˆ	Þ?H”$�y—‘ qÓ)×1Ñ1°#°rÓ:Ô;Èdˆä—)’)ÐWÐY^Ó_ˆÞ+2�w—}‘} QÔ'¸ˆä—’ÐiÐkpÓqˆÞ"(ˆv�|‰|˜AŒ¨bˆä—9’9Ð8¸%Ó@ˆÞ,4”�h—n‘n QÓ'Ô(¸$ˆä—	’	ÐIÈ5ÓQˆÜ˜e×F¨¯©°A«¸,Ñ(FÓGˆä—)’)Ð3°UÓ;ˆÞ*1”�W—]‘] 1Ó%Ô&°tˆô —	’	ÐBÀEÓJˆÞ?D”7×#Ò# E§K¡K°£NÓ3×9Ñ9Ô;È"ˆä—9’9ÐAÀ5Ì"Ï$É$ÓOˆÞ,4”�h—n‘n QÓ'Ô(¸$ˆÜ—9’9ÐAÀ5Ì"Ï$É$ÓOˆÞ,4”�h—n‘n QÓ'Ô(¸$ˆð
 )/Ñ(:¸vÑ?Q�6˜F’?ÐX\ˆä—’ÐRÐTYÔ[]×[_Ñ[_Ó`ˆÜ'¾6¨¯©°Q¬ÀrÈsÑSˆð ˆ	Ü—z’z /°5Ö9ˆGØ,°Õ7ÜŸ	š	Ð"2°GÓ<�ÞØ %§¡¨A£�IÙñ :ð& ®d  d¢
¸Ñ;×BÑBÓDˆà�‰”Cò 	
Ùð	
Ùð	
Ù&+ð	
Ù8Bð	
áð	
á&ð	
á5Að	
áHLð	
ñ ð	
ñ &/ð	
ñ 8>ð	
ñ FKð	
ñ *ð		
ñ 4;ð		
ñ
 ð	
ñ
 *3ð	
ñ $ð	
ð MÈYÈKÑXð	
ñ $ð	
÷ 		ñu )ðH €Kr   c                 ó¨  • / n[        5       n[        5       n[        U5       GH  nU R                  5       n[        [        R	                  U5      5      nX„-
  n	U	(       d
  U(       a    U$ XH-  n[        U5       Hc  n
U
R                  (       a  U
R                  U;   a  M&  U
R                  (       a  UR                  U
R                  5        UR                  U
5        Me     U(       a  U" US-   [        U5      5        U R                  [        5      nU(       d    U$  UR                  SS9  U R                  S5        GM     U$ ! [         a       U$ f = f)a,  Accumulate cars across every page of the already filtered (auction
only) list, from an already open, already logged in page. Stops when a
page repeats the same advert ids as the one before, or after max_pages as
a safety cap. A "Recommended Stock" carousel on the page reuses some of
the same cars across different page loads, so each page's cars are kept
only if their registration has not already been added, not just added in
bulk (a real duplicate car bug caught live, about a third of a naive
accumulation turned out to be repeats from that carousel).r   é¸  ©Útimeouté  )rg   ÚrangeÚcontentÚADVERT_ID_RErm   r†   rT   rh   r3   r   Úquery_selectorÚNEXT_SELÚclickÚ	ExceptionÚwait_for_timeout)ÚpageÚ	max_pagesÚon_pagern   Úseen_idsÚ	seen_regsÚiÚhtmlÚpage_idsÚnew_idsr,   Únxts               r   Úread_all_pagesrž     s$  € ð €DÜ‹u€HÜ“€IÜ�9×ˆØ�|‰|‹~ˆÜ”|×+Ñ+¨DÓ1Ó2ˆØÑ%ˆÞž8Øð& €Kð% 	ÑˆÜ˜tÖ$ˆAØ�u�u˜Ÿ™ )Ó+ÙØ�u�uØ—‘˜aŸe™eÔ$Ø�K‰K˜ŽNñ %ö Ù�A˜‘Eœ3˜t›9Ô%à×!Ñ!¤(Ó+ˆÞØð €Kð	Ø�I‰I˜dˆIÑ#ð 	×Ñ˜d×#ñ/ ð0 €Køô ó 	Ùà€Kð	ús   ÄEÅ
EÅEc                 óÐ  • [         R                  " U SUS9n UR                  (       a  UR                  S   OUR                  5       nUR	                  [
        SSS9   UR                  SSS	9  UR                  S5        [        XBS9nU(       d  [        S5      eUUR                  5         $ ! [         a    [        S
UR                   S35      ef = f! UR                  5         f = f)z‰Standalone convenience wrapper: opens its own context, reads the whole
auction only list, closes. Fails loudly if the list never renders.Údealerauction©Úheadlessr   Únetworkidleé0u  ©Ú
wait_untilrŠ   za[analyticslabel="view-advert"]i N  r‰   zDDealer Auction list did not render any vehicles (no advert links at zf). The page may have changed or the login may have expired. Run: python3 login.py dealerauction manuali   ©r•   zbDealer Auction list rendered but no vehicles were read. The layout may have changed, not guessing.)r   Úopen_reader_contextÚpagesÚnew_pageÚgotoÚ	STOCK_URLÚwait_for_selectorr’   ÚRuntimeErrorÚurlr“   rž   Úclose)Ú
playwrightr¢   r•   Úctxr”   rn   s         r   Ú	read_liver³   *  sä   € ô ×
%Ò
% j°/ÈHÑ
U€CðØ"ŸyŸyˆs�y‰y˜Š|¨c¯l©l«nˆØ�	‰	”)¨¸uˆ	ÑEð	Ø×"Ñ"Ð#DÈeÐ"ÑTð 	×Ñ˜cÔ"Ü˜dÑ8ˆÞÜð=óð ð à�	‰	�øô ó 	Üð'Ø'+§x¡x jð 1\ð\óð ð	ûð 	�	‰	�ús$   ˜AC ÁB, Á.-C Â,$CÃC ÃC%z:https://www.dealerauction.co.uk/sourcing/#/invoice-historyz2button[analyticslabel="invoice-history-purchases"]z^([A-Z]{2}\d{2}\s?[A-Z]{3})\bz5^(\d{2})/(\d{2})/(\d{2}),?\s+(\d{2}):(\d{2}):(\d{2})$c                 óv  • [         R                  U =(       d    SR                  5       5      nU(       d  g[        UR	                  S5      5      [        UR	                  S5      5      [        UR	                  S5      5      pCn [
        R                  " SU-   X25      R                  5       $ ! [         a     gf = f)zÕ"29/07/26, 14:44:39" (the invoice history page's own real format, a
2 digit year) turned into a plain YYYY-MM-DD string. Returns "" rather
than guessing if the text does not match, never seen any other shape
live.r   r   r   r   iÐ  )	Ú_INVOICE_DATE_REr1   r   r   r   rF   ÚdaterI   Ú
ValueError)rJ   r   ÚdayÚmonthÚyear2s        r   Ú_parse_invoice_dater»   Y  s‹   € ô
 	×Ñ §
¨×1Ñ1Ó3Ó4€AÞØÜ˜AŸG™G A›J›¬¨Q¯W©W°Q«Z«¼#¸a¿g¹gÀa»j»/�€CðÜ�}Š}˜T E™\¨5Ó6×@Ñ@ÓBÐBøÜó Ùðús   Â'B+ Â+
B8Â7B8c                 óL  • / nU R                  S5      SS  GHˆ  n[        R                  " SU5      nU(       a3  [        R                  " UR                  S5      5      R                  5       OSnU(       d  M`  [        R                   " SU5      n[        U5      S:  a  Mˆ  [        R                  " SUS   5      nU(       a3  [        R                  " UR                  S5      5      R                  5       OSnUS	   n[        R                  U5      nU(       a  UR                  S5      OSn	[        R                  " [        R                  " S
SU5      5      R                  5       n
[        R                  " SSU
5      n
[        R                  U
5      nU(       a.  SR                  UR                  S5      R                  5       5      OSnU(       a   X«R                  5       S R                  5       OU
n[        R                  " SSU5      R                  5       n[        US   R                  5       5      n[        US   5      n[        US   5      n[        US   5      n[        US   5      nU(       a  Uc  GM`  UR!                  SXLXÖUUUUUUU	(       a  SU	 3OSS.5        GM‹     U$ )aT  Parse one page of the Invoice history table into purchase dicts. Each
real data row carries a class="c-recent-transactions__charge-id" cell
(the header row does not, so it is skipped for free by requiring that
marker). Once split on that marker, each row's own remaining columns
(Status, Description, Date/Time, Vehicle price, Fee, Tax, Total, Type,
Payment Method) are still in a fixed, real order, but the same generic
"<td>...(?:(?!</td>).)*...</td>" trick used elsewhere in this project
cannot isolate the Description column here: this table's own Status
column ALSO carries a "<div class=...>Complete</div>" plus several
Angular comment placeholders that make it look like real content to a
naive first-td match (found live, the description regex kept matching
the Status cell instead). Splitting on every real "</td><td>" boundary
instead gives one clean chunk per column, addressed by its own fixed
position, immune to that confusion. The registration sits at the very
start of the Description column's own text ("DN18NCD Hyundai Santa
Fe...", confirmed on every real row seen live), the rest up to " Buyer
Fee" is the vehicle name, and the advert id is the real "View advert"
link inside that same column (reusing ADVERT_ID_RE, the exact same
pattern the stock list's own parse_listing already matches). Vehicle
price, Fee and Tax are kept under the same generic winning_bid/
motorway_fee/vat_total column names Carwow's own purchases already
reuse (Mark's original platform specific naming, kept for db.py's own
_PURCHASE_PAYMENT_FIELDS shape, no transport_fee or protect_fee
equivalent shown on this platform).z(class="c-recent-transactions__charge-id"r   Nz<span>([^<]*)</span>r   z</td>\s*<td[^>]*>é	   zc-pill[^"]*">([^<]*)</div>r   z<[^>]+>r   z\s+z\s*Buyer Fee\s*View advert\s*$r   é   é   é   é   re   rc   )ÚplatformÚvehicle_refrT   ÚnameÚstatusÚpriceÚtotal_priceÚbought_dateÚwinning_bidÚmotorway_feeÚ	vat_totalrb   )r&   r0   r   ri   rj   r   r   r   rŽ   ÚsubÚ_REG_LEAD_REr1   r%   Úendr»   Ú
_col_moneyr3   )rO   Úoutrp   ÚrefÚ	referenceÚcolsrÅ   Ú	desc_htmlÚaidrr   Ú	desc_textÚreg_mrT   rÄ   rÈ   Úvehicle_priceÚfeeÚtaxÚtotals                      r   Úparse_invoice_historyrÜ   h  s%  € ð2 €CØ—‘Ð!KÓLÈQÈRÔPˆÜ�iŠiÐ/°Ó7ˆÞ>A”G×$Ò$ S§Y¡Y¨q£\Ó2×8Ñ8Ô:Àrˆ	ÞÙÜ�xŠxÐ,¨eÓ4ˆô ˆt‹9�q‹=ÙÜ—’Ð8¸$¸q¹'ÓBˆÞ>D”×!Ò! &§,¡,¨q£/Ó2×8Ñ8Ô:È"ˆØ˜‘Gˆ	Ü×!Ñ! )Ó,ˆÞ$'�C—I‘I˜a”L¨Rˆ	Ü×$Ò$¤R§V¢V¨J¸¸YÓ%GÓH×NÑNÓPˆ	Ü—F’F˜6 3¨	Ó2ˆ	Ü×"Ñ" 9Ó-ˆÞ16ˆb�g‰g�e—k‘k !“n×*Ñ*Ó,Ô-¸BˆÞ27ˆyŸ™›˜Ð&×,Ñ,Ô.¸YˆÜ�vŠvÐ7¸¸TÓB×HÑHÓJˆÜ)¨$¨q©'¯-©-«/Ó:ˆÜ" 4¨¡7Ó+ˆÜ˜˜a™Ó!ˆÜ˜˜a™Ó!ˆÜ˜4 ™7Ó#ˆÞ�mÑ+ÚØ�
‰
Ø(¸Ø°]Ø °Ø(¸#ÈCæ )ð QÐQZÐP[Ñ\Ø/1ñ
÷ 	ñ9 QðH €Jr   c                 óf   •  [        U R                  SS5      5      $ ! [        [        4 a     gf = f)u>  A bare "212.50" (already extracted, the Â£ sign stripped by the
caller's own regex) to a plain float, real pence kept, unlike the
stock list's own _money/_num above, which only ever need whole
pounds; a real invoice total genuinely carries them (Â£212.50). Named
distinctly from _money above so it can never shadow it.r   r   N)Úfloatr
   r·   ÚAttributeError)Údigitss    r   Ú_pence_moneyrá   ©  s4   € ðÜ�V—^‘^ C¨Ó,Ó-Ð-øÜœÐ'ó Ùðús   ‚ �0¯0c                 óv   • [         R                  " SU 5      nU(       a  [        UR                  S5      5      $ S$ )u‡   The Â£ figure inside one already isolated invoice history column
(a <strong>Â£N.NN</strong> cell), or None if that column carries none.u   Â£([\d,.]+)r   N)r0   r   rá   r   )Úcol_htmlr   s     r   rÏ   rÏ   µ  s.   € ô 	�	Š	�. (Ó+€AÞ'(Œ<˜Ÿ™ ›
Ó#Ð2¨dÐ2r   c                 óÚ  • / n[        5       n[        U5       H³  nU R                  5       n[        U5      nU Vs/ s H  owS   U;  d  M  UPM     nnU(       d
  U(       a    U$ X6 Vs1 s H  owS   iM	     sn-  nUR	                  U5        U R                  [        5      n	U	(       d    U$  U	R                  SS9  U R                  S5        Mµ     U$ s  snf s  snf ! [         a       U$ f = f)a®  Accumulate purchase rows across the Invoice history table's own real
pagination (the same paginator-next button and shape read_all_pages
already drives for the stock list), newest first, confirmed live, so
max_pages defaults to a small daily top up rather than the whole
history (a real account showed 12 pages of 10 rows each, 2026-08-27).
Stops early once a page repeats an already seen reference, or there is
no next page left.rÃ   rˆ   r‰   r‹   )
rg   rŒ   r�   rÜ   Úextendr�   r�   r‘   r’   r“   )
r”   r•   ÚrowsÚ	seen_refsr™   rš   ÚfoundÚrÚnewr�   s
             r   Úread_all_invoice_pagesrë   ¼  sö   € ð €DÜ“€IÜ�9ÖˆØ�|‰|‹~ˆÜ% dÓ+ˆÙÓEš%�Q ]Ñ#3¸9Ñ#D�q™%ˆÐEÞ–yØð €Kð 	°Ó6²¨1˜Ô&±Ñ6Ñ6ˆ	Ø�‰�CÔØ×!Ñ!¤(Ó+ˆÞØð €Kð	Ø�I‰I˜dˆIÑ#ð 	×Ñ˜dÖ#ñ ð  €Kùò Fùò 7øô ó 	Ùà€Kð	ús#   ºCÁ
CÁ'CÂ+CÃ
C*Ã)C*c                 ó¾  • [         R                  " U SUS9n UR                  (       a  UR                  S   OUR                  5       nUR	                  [
        SSS9   UR                  [        SS9  UR                  [        5        UR                  S
5        [        XBS9UR                  5         $ ! [         a    [        S	5      ef = f! UR                  5         f = f)zöRead the account's own real Invoice history, Purchases only, newest
first. Fails loudly if the screen itself does not render; a purely
empty result after that (a genuinely quiet stretch with nothing bought)
is not an error. Read only, never acts.r    r¡   r   r£   éÈ¯  r¥   r¤   r‰   z™Dealer Auction invoice history screen did not render. The page may have changed or the login may have expired. Run: python3 login.py dealerauction manualiÜ  r§   )r   r¨   r©   rª   r«   ÚINVOICE_HISTORY_URLr­   ÚPURCHASES_TAB_SELr’   r®   r‘   r“   rë   r°   )r±   r¢   r•   r²   r”   s        r   Úread_purchase_invoicesrð   Ù  sÁ   € ô
 ×
%Ò
% j°/ÈHÑ
U€CðØ"ŸyŸyˆs�y‰y˜Š|¨c¯l©l«nˆØ�	‰	Ô%°-Èˆ	ÑOð	>Ø×"Ñ"Ô#4¸eÐ"ÑDð 	�
‰
Ô$Ô%Ø×Ñ˜dÔ#Ü% dÑ@à�	‰	�øô ó 	>Üð=ó>ð >ð	>ûð 	�	‰	�ús$   ˜AC
 ÁB1 Á2.C
 Â1CÃC
 Ã
Cz0<img[^>]*class="c-fpa-image-viewer__image"[^>]*>zalt="Main image (\d+)"rS   zdata-full="([^"]+)"z/advert/([A-Za-z0-9]+)c                 óÆ  • 0 n[         R                  U 5       H¤  n[        R                  U5      nU(       d  M!  [	        UR                  S5      5      n[        R                  U5      n[        R                  U5      nU(       a  UR                  S5      OU(       a  UR                  S5      OSnU(       d  M   XqU'   M¦     [        U5       Vs/ s H  o�U   PM	     sn$ s  snf )z¶Every real photo on a won car's own advert page, ordered by the
image's own position index (never scrape order), deduped by index. []
on a page with no gallery at all, never guessed.r   r   )	Ú_ADVERT_IMG_RErm   Ú_ADVERT_IMG_IDX_REr   r   r   Ú_ADVERT_IMG_FULL_REÚ_ADVERT_IMG_SRC_REÚsorted)	rO   ÚseenÚtagÚidx_mÚidxÚfull_mr…   r¯   r™   s	            r   Ú_parse_advert_galleryrü     s¯   € ð €DÜ×%Ñ% iÖ0ˆÜ"×)Ñ)¨#Ó.ˆÞÙÜ�%—+‘+˜a“.Ó!ˆÜ$×+Ñ+¨CÓ0ˆÜ"×)Ñ)¨#Ó.ˆÞ!'ˆf�l‰l˜1ŒoÆ¨e¯k©k¸!¬nÈ2ˆßˆ3Ø�‹Iñ 1ô $ Dœ\Ó*š\˜�ŒG™\Ñ*Ð*ùÒ*s   ÃCc                 ó¨   • U R                  SU 3SSS9  U R                  S5         U R                  5       n[	        U5      $ ! [         a    / s $ f = f)aÕ  The full photo gallery from a won car's own advert page, the only
place Dealer Auction ever shows real photos for a purchase (the
Invoice history list itself carries none). The page is a client side
route and the gallery only finishes rendering a moment after
navigation, so this waits the same real 4s the live reconnaissance for
this needed. Returns [] rather than guessing if the page never renders
a gallery at all (a listing that has since come down, for example).rc   Údomcontentloadedrí   r¥   i   )r«   r“   r�   r’   rü   )r”   rr   rš   s      r   Úread_purchase_galleryrÿ     se   € ð 	‡I�IÐBÀ9À+ÐNØ+°Uð ñ <à×Ñ˜$ÔðØ�|‰|‹~ˆô ! Ó&Ð&øô ó ØŠ	ðús   §A ÁAÁAr*   )é<   N)Tr   )r   )Tr   )*Ú__doc__r0   rF   rš   ri   r   r   Úpricingr   Úcompiler   ÚYEARrŽ   r�   r/   r¬   r   r   r'   rA   rM   rP   r†   rž   r³   rî   rï   rÍ   rµ   r»   rÜ   rá   rÏ   rë   rð   rò   ró   rõ   rô   ÚADVERT_URL_RErü   rÿ   rf   r   r   Ú<module>r     s<  ðñó0 
Û Û Ý Ý à
�
Š
Ð#Ó$€Ø	‡z‚zÐ+Ó,€ð �zŠzÐGÓH€Ø4€òÐ ðð 
ò+ò+ò_ò%#ôP%ò,2ôkô\$ôNðR SÐ ØHÐ Ø�zŠzÐ:Ó;€Ø—:’:ÐVÓWÐ òò>òB	ò3ôô:ðB —’ÐOÓP€Ø—Z’ZÐ 9Ó:Ð Ø—Z’ZÐ 0Ó1Ð Ø—j’jÐ!7Ó8Ð Ø—
’
Ð4Ó5€ò+ó$'r   