o
    _i@                     @   s  d Z ddlZejjdd ddlZddlmZ ddlmZ ddlm	Z	m
Z
mZmZmZmZmZmZmZmZmZmZ dd	 Zd
d Zdd Zdd Zdd Zdd Zdd Zdd Zedkred ddddddd dd!d"d#dgZdddddd$d dd%d&d'dgZ eee d(Z!ed) ed*d+d, e!d- D   ed.d/d, e!d0 D   ed1d2d, e!d3 D   ed4ee!  dS dS )5u=   
變更偵測模組

比對新舊資料，偵測會員異動
    Nzutf-8)encoding)datetime)Path)get_connectionget_latest_snapshotsave_snapshotfind_appraiser_by_nameinsert_appraiserinsert_career_historyupdate_appraiser_last_seenset_appraiser_inactiveend_career_historylog_membership_event
log_changecalculate_checksumc                 C   s   | sdS d t|  } | S )u   標準化事務所名稱  joinstrsplitname r   ZC:\Users\User\Documents\GitHub\Research_zoo\projects\taiwan-appraiser-registry\detector.pynormalize_office_name   s   r   c                 C   s   | sdS d t|  } | S )u   
    標準化姓名

    處理：
    - 移除前後空白
    - 移除中間多餘空白（例如「張 三」→「張三」）
    - 移除全形空白
    r   r   r   r   r   r   normalize_name   s   	r   c                 C   s   t | dpd}| dp| dpd }t| dp#| dp#d}| dp5| dp5| d	p5d }| d
pC| dpCd }|||||| dS )u   標準化記錄欄位名稱   姓名r   u   會員編號u   會號	   事務所u   事務所名稱u   地址u   開業執照地址u   事務所地址u   聯絡電話   電話)r   	member_idofficeaddressphoneraw)r   getstripr   )recordr   r    r!   r"   r#   r   r   r   normalize_record+   sJ   




r(   c                 C   s&   | d r| d  d| d  S | d S )u   
    產生比對用的 key

    策略：優先用「會員編號 + 姓名」，若無會員編號則用「姓名」
    這樣可以處理同公會內同名同姓的情況
    r    _r   r   )
normalizedr   r   r   make_compare_keyX   s   r+   c              	      s  g g g g g d}i }| D ]}t |}|d rt|}|||< qi }|D ]}t |}|d r5t|}|||< q#| D ]\}}	||vrW|d |	d ||	d |	d |	d d q:| D ]\}}	||vrs|d |	d ||	d d	 q\|D ]s}||v r|| }
|| }|
d r|d r|
d |d kr|d
 |d ||
d |d d qv|
d |d ks|
d |d kr|d |d ||
d |d kr|
d |d dnd|
d |d kr|
d |d dnddd qvdd |d D }dd |d D }||@ rRD ]2 t fdd|d D d}t fdd|d D d}|r6|r6|d  |||dd qfdd|d D |d< fdd|d D |d< |S )u   
    比對兩次抓取的差異

    Args:
        old_data: 上次抓取的資料（list of dict）
        new_data: 本次抓取的資料（list of dict）
        association: 公會名稱

    Returns:
        dict: 變更摘要
    )newleftoffice_changedinfo_updatedsuspected_data_fixr   r,   r!   r"   r#   )r   associationr!   r"   r#   r-   )r   r1   r!   r.   )r   r1   
old_office
new_officer/   )oldr,   N)r"   r#   )r   r1   changesc                 S      h | ]}|d  qS r   r   .0itemr   r   r   	<setcomp>       z!detect_changes.<locals>.<setcomp>c                 S   r6   r   r   r7   r   r   r   r:      r;   c                 3        | ]}|d   kr|V  qdS r   Nr   r7   r   r   r   	<genexpr>       z!detect_changes.<locals>.<genexpr>c                 3   r<   r=   r   r7   r   r   r   r>      r?   r0   u`   同名同時出現在新加入與退出，可能是公會修正重複資料或會員編號變更)r   r1   Z
old_recordZ
new_recordreasonc                       g | ]
}|d   vr|qS r   r   r7   suspected_namesr   r   
<listcomp>       z"detect_changes.<locals>.<listcomp>c                    rA   r   r   r7   rB   r   r   rD      rE   )r(   r+   itemsappendnext)old_datanew_datar1   r5   Z
old_by_keyrr*   keyZ
new_by_keyr'   r4   r,   Z	new_namesZ
left_namesZnew_itemZ	left_itemr   )r   rC   r   detect_changesc   s   		 
 $$	rM   c              
   C   s8  t  d}ddddd}| d D ]f}|d }t|}|rCt|d  t|d d||d|d	|d
d t|d |d n!t|dd}t|d||d|d	|d
d t||d t	|d|d|d| |d  d7  < q| d D ]T}|d }t||}|rt
|d | t|d |d t }| }	|	d|d f |	 d }
|  |
dkrt|d  t	|d||dd| |d  d7  < q}| d D ]<}|d }t||}|rt
|d | t|d d||d d t|d  t	|d||d |d | |d  d7  < q| d D ]}|d }t||}|rt|d  t }| }	g }g }|d d	rN|d ||d d	 d  |d d
rf|d ||d d
 d  |r||d |g |	d d!| d"| |  |  t	|d|dd| |d  d7  < q|S )#u   
    將變更套用到資料庫

    Args:
        changes: detect_changes() 的回傳值
        association: 公會名稱

    Returns:
        dict: 套用結果摘要
    z%Y-%m-%dr   )	new_addedleft_markedoffice_updatedr/   r,   r   idr1   r!   r"   r#   )appraiser_idsourcer1   office_nameZoffice_addressr#   ZrejoinedZmedium)r   Zidentity_confidenceZjoinedNrN      r-   z
                SELECT COUNT(*) as cnt FROM career_history
                WHERE appraiser_id = ? AND is_current = 1
            ZcntrO   r.   r3   )rR   rS   r1   rT   r2   rP   r/   r5   zoffice_address = ?z	phone = ?zC
                    UPDATE career_history
                    SET z, zc
                    WHERE appraiser_id = ? AND association = ? AND is_current = 1
                )r   nowstrftimer   r   r
   r%   r   r	   r   r   r   cursorexecutefetchonecloser   rG   extendr   commit)r5   r1   todayZresultsr9   r   ZexistingrR   connrX   Zactive_countZupdatesvaluesr   r   r   apply_changes   s   
	




ra   c              	   C   sR  t | d}t|}|t|ddddd}|rD|d }t|}|dkrD||d k rDd| d	| d
| d||  d	}||d< td|  |r|d |krRd|d< |S |d }	t|	||}
t|
d t|
d  t|
d  t|
d  }|dkrd|d< |
|d< t|
|}||d< td| d| |S d|d< |S td| d| d|d< d|d< |S )u  
    處理抓取結果：比對變更並更新資料庫

    Args:
        association_code: 公會代碼（如 'taipei'）
        new_data: 新抓取的資料
        association_name: 公會名稱（如 '台北市'）

    Returns:
        dict: 處理結果
    Z
appraisersFN)r1   record_counthas_changesr5   apply_resultswarningrb   r   g      ?u   ⚠️ u    人數異常下降！上次 u    人 → 本次 u    人（減少 uC    人）可能是網站故障或抓取失敗，建議人工確認。re   z  Zchecksumrc   datar,   r-   r.   r/   Tr5   rd   r1   Zis_first_scrape)r   r   lenprintrM   ra   r   )Zassociation_coderJ   Zassociation_nameZlast_snapshotZnew_checksumresultZ	old_countZ	new_countZwarning_msgrI   r5   total_changesrd   r   r   r   process_scrape_resultl  sd   






rk   c                 C   s   | sdS g }| d r| dt| d  d | d r(| dt| d  d | d r9| dt| d  d | d	 rJ| d
t| d	  d | dr\| dt| d  d |rcd|S dS )u   產生變更摘要u	   無變更r,   u
   新加入 u    位r-   u   退出 r.   u   換事務所 r/   u   資訊更新 r0   u   疑似資料修正 u   、)rG   rg   r%   r   )r5   partsr   r   r   get_changes_summary  s   
rm   __main__u   變更偵測模組u   張三u
   A事務所z02-1234)r   r   r   u   李四u
   B事務所z02-5678u   王五u
   C事務所z02-9999u
   D事務所u   趙六u
   E事務所z02-0000u   測試公會u   
偵測結果:u   新加入: c                 C      g | ]}|d  qS r   r   r8   cr   r   r   rD     r;   rD   r,   u   退出: c                 C   ro   r   r   rp   r   r   r   rD     r;   r-   u   換事務所: c                 C   ro   r   r   rp   r   r   r   rD     r;   r.   u   摘要: )"__doc__sysstdoutreconfigurejsonr   Zpathlibr   Zdatabaser   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r(   r+   rM   ra   rk   rm   __name__rh   rI   rJ   r5   r   r   r   r   <module>   sB    8-k R





