+
    ê°ëi€  ã                  ó‚   € R t ^ RIHt ^ RIt^ RIHt ^ RIt^ RIHt R t	R R lt
R R	 ltR
 t]R8X  d
   ]! 4        R# R# )aœ  
Repair stale parts_inventory.part_images.file_path values by matching files on disk.

Why:
- Legacy migrated rows may point to missing files even when the actual file exists
  elsewhere under the uploads tree.
- New uploads work because they write correct file_path values.

What it does:
1) Reads part_images rows with file_path (and optional original_filename).
2) If file_path does not exist on disk, searches for candidates under:
     <upload_root>/parts/<part_id>/
   by stem (filename without extension) and original_filename.
3) If exactly one candidate is found, updates file_path to that relative path.
4) Optional cleanup: delete rows still unresolved and file-missing.

Examples:
  # Dry run
  python scripts/repair_part_image_paths.py --host 127.0.0.1 --user bill --password 5074367 --dry-run

  # Apply fixes
  python scripts/repair_part_image_paths.py --host 127.0.0.1 --user bill --password 5074367 --apply
)ÚannotationsN)ÚPath)Ú
DictCursorc                 óÀ  € \         P                  ! R R7      p V P                  RRR7       V P                  R\        RR7       V P                  RR	R
7       V P                  RR	R
7       V P                  RRR7       V P                  RRR7       V P                  RRRR7       V P                  RRRR7       V P                  R\        ^ RR7       V P	                  4       # )zGRepair parts_inventory.part_images.file_path values from on-disk files.)Údescriptionz--hostz	127.0.0.1)Údefaultz--portiê  )Útyper   z--userT)Úrequiredz
--passwordz
--databaseÚparts_inventoryz--upload-rootz+/var/www/html/Server/partsinventory_uploadsz--applyÚ
store_truez$Persist changes. Default is dry-run.)ÚactionÚhelpz--delete-unresolved-missingz)Delete rows still unresolved and missing.z--limitz%Optional max rows to inspect (0=all).)r   r   r   )ÚargparseÚArgumentParserÚadd_argumentÚintÚ
parse_args)Úps    Ú@c:\Users\Bill\Software\Server\scripts\repair_part_image_paths.pyr   r   !   s¿   € Ü×ÒÐ,uÔv€AØ‡N�N�8 [€NÔ1Ø‡N�N�8¤#¨t€NÔ4Ø‡N�N�8 d€NÔ+Ø‡N�N�<¨$€NÔ/Ø‡N�N�<Ð):€NÔ;Ø‡N�N�?Ð,Y€NÔZØ‡N�N�9 \Ð8^€NÔ_Ø‡N�NÐ0¸ÐLw€NÔxØ‡N�N�9¤3°Ð8_€NÔ`Ø�<‰<‹>Ðó    c               ó    € V ^8„  d   QhRRRR/# )é   Únamez
str | NoneÚreturnÚstr© )Úformats   "r   Ú__annotate__r   /   s   € ÷ +ñ +�zð + cñ +r   c                óx   € V '       g   R # \        V 4      P                  P                  4       P                  4       # )Ú )r   ÚstemÚstripÚlower)r   s   &r   Ústem_of_namer#   /   s(   € ßÙÜ�‹:�?‰?× Ñ Ó"×(Ñ(Ó*Ð*r   c               ó    € V ^8„  d   QhRRRR/# )r   Úpart_dirr   r   z3tuple[dict[str, list[Path]], dict[str, list[Path]]]r   )r   s   "r   r   r   5   s   € ÷ 
ñ 
 $ð 
Ð+^ñ 
r   c                ó†  € / p/ pV P                  4       '       g   W3# V P                  R 4       F�  pVP                  4       '       g   K  VP                  VP                  P                  4       . 4      P                  V4       VP                  VP                  P                  4       . 4      P                  V4       K�  	  W3# )Ú*)ÚexistsÚrglobÚis_fileÚ
setdefaultr    r"   Úappendr   )r%   Úby_stemÚby_nameÚfps   &   r   Úbuild_candidate_mapr0   5   s˜   € Ø%'€GØ%'€GØ�?‰?×ÒØÐÐØ�n‰n˜SÖ!ˆØ�z‰z�|Š|ÙØ×Ñ˜2Ÿ7™7Ÿ=™=›?¨BÓ/×6Ñ6°rÔ:Ø×Ñ˜2Ÿ7™7Ÿ=™=›?¨BÓ/×6Ñ6°rÖ:ñ	 "ð
 ÐÐr   c                 ó
  € \        4       p \        V P                  4      p\        P                  ! V P
                  V P                  V P                  V P                  V P                  \        R R7      p^ p^ p^ p^ p^ p VP                  4       ;_uu_ 4       pRp	V P                  ^ 8”  d'   V	R,          p	VP                  W�P                  34       MVP                  V	4       VP                  4       p
V
 EF   pV^,          pVR,          pVR,          pVP                  R4      ;'       g    RP!                  4       pVP                  R4      ;'       g    RP!                  4       pV'       d   V^,          pK„  V'       g   V^,          pK—  W,          pVP#                  4       '       d   V^,          pKÀ  VR	,          \%        V4      ,          p\'        V4      w  pp. p\        V4      P(                  P+                  4       pVV9   d   VP-                  VV,          4       \/        V4      pVV9   d   VP-                  VV,          4       VP                  R
4      ;'       g    RP!                  4       P+                  4       pV'       d    VV9   d   VP-                  VV,          4       \/        V4      pV'       d    VV9   d   VP-                  VV,          4       . p\1        4       pV FG  p\%        VP3                  4       4      pVV9   d   K%  VP5                  V4       VP7                  V4       KI  	  \9        V4      ^8X  dp   V^ ,          P;                  V4      P=                  4       p\?        RV RV RV RV 24       V P@                  '       d   VP                  RVV34       V^,          pEK½  V^,          pV PB                  '       g   EKÛ  V P@                  '       g   EKð  \?        RV RV RV 24       VP                  RV34       V^,          pEK#  	  V P@                  '       d#   VP                  R4       VP                  R4       RRR4       V P@                  '       d   VPE                  4        MVPG                  4        \?        RRV 2RV 2RV 2RV 2RV 2RV P@                   24        TPK                  4        R#   + '       g   i     Lƒ; i  \H         d    TPG                  4        h i ; i  TPK                  4        i ; i)F)ÚhostÚportÚuserÚpasswordÚdatabaseÚcursorclassÚ
autocommitzœ
                SELECT id, part_id, file_path, original_filename, url, is_primary
                FROM part_images
                ORDER BY id
            z	 LIMIT %sÚidÚpart_idÚ	file_pathr   ÚurlÚpartsÚoriginal_filenamezFIX image_id=z	 part_id=z: z -> z3UPDATE part_images SET file_path = %s WHERE id = %szDELETE image_id=z: unresolved missing path=z%DELETE FROM part_images WHERE id = %sz%UPDATE part_images SET is_primary = 0a6  
                    UPDATE part_images pi
                    JOIN (
                      SELECT part_id, MIN(id) AS min_id
                      FROM part_images
                      GROUP BY part_id
                    ) x ON x.min_id = pi.id
                    SET pi.is_primary = 1
                    NzRepair summary:z
inspected=zalready_ok=zfixed=zunresolved=zdeleted=zapplied=)&r   r   Úupload_rootÚpymysqlÚconnectr2   r3   r4   r5   r6   r   ÚcursorÚlimitÚexecuteÚfetchallÚgetr!   r(   r   r0   r   r"   Úextendr#   ÚsetÚresolveÚaddr,   ÚlenÚrelative_toÚas_posixÚprintÚapplyÚdelete_unresolved_missingÚcommitÚrollbackÚ	ExceptionÚclose)Úargsr?   ÚconnÚ	inspectedÚ
already_okÚfixedÚ
unresolvedÚdeletedÚcurÚsqlÚrowsÚrowÚimage_idr:   r;   r<   Ú	full_pathr%   r-   r.   Ú
candidatesÚfp_nameÚfp_stemÚ	orig_nameÚ	orig_stemÚuniqÚseenÚcÚkeyÚrels                                 r   Úmainrl   B   s?  € Ü‹<€DÜ�t×'Ñ'Ó(€Kä�?Š?Ø�Y‰YØ�Y‰YØ�Y‰YØ—‘Ø—‘ÜØô€Dð €IØ€JØ€EØ€JØ€GðkØ�[‰[�]Œ]˜cðˆCð
 �z‰z˜AŒ~Ø�{Õ"�Ø—‘˜C§*¡* Õ/à—‘˜CÔ Ø—<‘<“>ˆDä�Ø˜Q•�	Ø˜t�9�Ø˜i�.�Ø ŸW™W [Ó1×7Ð7°R×>Ñ>Ó@�	Ø—w‘w˜u“~×+Ð+¨×2Ñ2Ó4�ßØ !•O�JÙß Ø !•O�JÙà'Õ3�	Ø×#Ñ#×%Ò%Ø !•O�JÙà&¨Õ0´3°w³<Õ?�Ü#6°xÓ#@Ñ �˜à)+�
Ü˜y›/×.Ñ.×4Ñ4Ó6�Ø˜gÔ%Ø×%Ñ% g¨gÕ&6Ô7ä& yÓ1�Ø˜gÔ%Ø×%Ñ% g¨gÕ&6Ô7à ŸW™WÐ%8Ó9×?Ð?¸R×FÑFÓH×NÑNÓP�	ß ¨gÔ!5Ø×%Ñ% g¨iÕ&8Ô9Ü(¨Ó3�	ß ¨gÔ!5Ø×%Ñ% g¨iÕ&8Ô9ð �Ü“u�Û#�AÜ˜aŸi™i›kÓ*�CØ˜d”{Ù Ø—H‘H˜S”MØ—K‘K –Nñ $ô �t“9 ”>Ø˜q�'×-Ñ-¨kÓ:×CÑCÓE�CÜ˜M¨(¨°9¸W¸IÀRÈ	À{ÐRVÐWZÐV[Ð\Ô]Ø—z—z�zØŸ™Ð$YÐ\_ÐaiÐ[jÔkØ˜Q•J“Eà !•O�JØ×5×5Ó5¸$¿*¿*»*ÜÐ 0°°
¸)ÀGÀ9ÐLfÐgpÐfqÐrÔsØŸ™Ð$KÈhÈ[ÔYØ 1�›ñu ðz �z�zˆzØ—‘ÐCÔDØ—‘ðô
÷Y ðp �:�:ˆ:Ø�K‰K�Mà�M‰MŒOäØØ˜˜Ð$Ø˜*˜Ð&Ø�U�GÐØ˜*˜Ð&Ø�w�iÐ Ø�t—z‘z�lÐ#õ	
ð 	�
‰
Ž÷U �]ûôL ô Ø�‰ŒØðûð 	�
‰
�úsq   Á>S ÂBSÄ*(SÅSÅ,SÅ?C SÉ &SÊESÏSÏ#ASÐ*"SÑS Ñ&AS ÓS	ÓS ÓS7Ó7S: Ó:TÚ__main__)Ú__doc__Ú
__future__r   r   Úpathlibr   r@   Úpymysql.cursorsr   r   r#   r0   rl   Ú__name__r   r   r   Ú<module>rs      sE   ðñõ. #ã Ý ã Ý &òõ+õ
òðD ˆzÔÙ†Fñ r   