
    iS8                        d Z ddlZddlZddlZddlZddlmZ ddlmZmZ ddl	m
Z
 ddlmZmZmZmZmZ ej"                  j%                  d e e
e      j+                         j,                  j,                              Z e e
e      dz  d	z        Z e e
e      dz  d
z  dz        Zdedeeeef      fdZdee   dee   fdZdefdZdeeeef      dededeeeef      fdZdeeeef      deeef   fdZ	 d'deeeef      dedeeeeef   ef      fdZ deeef   defdZ!deeef   deeeeef   ef      dedededefdZ"d ee   dedefd!Z#ded"eddfd#Z$dejJ                  fd$Z&d(d%Z'e(d&k(  r e'        yy))u   
weekly-report.py — task-timers.json에서 지난 N일간의 메트릭을 집계하여
마크다운 리포트를 자동 생성한다.
    N)defaultdict)datetime	timedelta)Path)AnyDictListOptionalTupleWORKSPACE_ROOTmemoryztask-timers.jsonreportsweeklypathreturnc                    t         j                  j                  |       s3t        d|  t        j
                         t	        j                  d       	 t        | dd      5 }t        j                  |      }ddd       d
vst        |d
   t              s0t        dt        j
                         t	        j                  d       t        |d
   j                               S # 1 sw Y   lxY w# t        j                  $ r=}t        d| t        j
                         t	        j                  d       Y d}~d}~wt        $ r=}t        d	| t        j
                         t	        j                  d       Y d}~d}~ww xY w)u9   task-timers.json을 읽어 작업 목록을 반환한다.u<   [ERROR] task-timers.json 파일을 찾을 수 없습니다: file   rutf-8encodingNu(   [ERROR] task-timers.json 파싱 실패: u(   [ERROR] task-timers.json 읽기 실패: tasksuZ   [ERROR] task-timers.json 구조가 올바르지 않습니다. 'tasks' 키가 없습니다.)osr   existsprintsysstderrexitopenjsonloadJSONDecodeErrorOSError
isinstancedictlistvalues)r   frawes       F/home/jay/workspace/.worktrees/task-2116-dev1/scripts/weekly-report.py
load_tasksr.      s   77>>$LTFSZ]ZdZde$g. 	!))A,C	 cCL$!?jqtq{q{|G##%&&	 	 8<3::N 8<3::Ns<   C/ "C#8C/ #C,(C/ /F3D::F3E>>Fvaluec                     | sy| j                  dd      j                  dd      } 	 t        j                  |       }|j                  d      S # t        $ r Y yw xY w)u  ISO 8601 문자열을 datetime으로 변환한다. 실패 시 None 반환.

    task-timers.json의 타임스탬프는 로컬 시간(KST) + timezone 정보 없음.
    timezone-naive 그대로 반환하여 로컬 시간 기반 비교를 일관되게 유지한다.
    NZ z+00:00)tzinfo)replacer   fromisoformat
ValueError)r/   dts     r-   parse_dtr8   6   s_     MM#r"**8R8E##E*zzz&& s   &A 	AAc                  *    t        j                         S )u6   로컬 시간 기준 현재 datetime (timezone-naive).)r   now     r-   	now_localr=   H   s    <<>r<   r   sinceuntilc                     g }| D ]p  }t        |j                  d            }t        |j                  d            }d}|r||cxk  r|k  rn nd}|r||cxk  r|k  rn nd}|s`|j                  |       r |S )uc   
    start_time 또는 end_time이 [since, until] 범위 내에 있는 작업만 반환한다.
    
start_timeend_timeFT)r8   getappend)r   r>   r?   resulttaskstetin_ranges           r-   filter_tasks_by_periodrJ   R   s     $&F 	 dhh|,-dhhz*+%2&&H%2&&HMM$	  Mr<   c                 4   t        |       }t        d | D              }t        d | D              }t        d | D              }dx}x}x}}| D ]  }	|	j                  d      }
|
	|
dk(  sd|	vr|dz  }%t        |
      j	                         d	k(  r|dz  }Gt        |
      j	                         d
k(  r|dz  }it        |
      j	                         dv r|dz  }|dz  } ||z   |z   }|dkD  rt        ||z  dz  d      nd}t        d       }g }| D ]  }	|	j                  d      xs d}||   }|dxx   dz  cc<   |	j                  dd      }|dk(  r|dxx   dz  cc<   n|dk(  r|dxx   dz  cc<   t        |	j                  d            xs |dk(  }|r|dxx   dz  cc<   |j                  |	       |	j                  d      }|	 |d   j                  t        |             |	j                  d      }
d|	vs|
|
dk(  r|dxx   dz  cc<   t        |
      j	                         d	k(  r|dxx   dz  cc<   )t        |
      j	                         d
k(  r|dxx   dz  cc<   Tt        |
      j	                         dv r|dxx   dz  cc<   ~|dxx   dz  cc<    i }|j                         D ];  \  }}|d   }|r*t        |      t        |      z  }t        |dz  d      ||<   7d||<   = |||||||||t        |      ||dS # t        t        f$ r Y 4w xY w)u+   집계 결과 딕셔너리를 반환한다.c              3   J   K   | ]  }|j                  d       dk(  sd  yw)status	completedr   NrC   .0ts     r-   	<genexpr>zaggregate.<locals>.<genexpr>q   s     G!h;(FAG   ##c              3   l   K   | ],  }|j                  d       s|j                  d      dk(  s)d . yw)stale_atrM   staler   NrO   rP   s     r-   rS   zaggregate.<locals>.<genexpr>r   s*     TaAEE*$5xG9STs   *44c              3   J   K   | ]  }|j                  d       dk(  sd  yw)rM   runningr   NrO   rP   s     r-   rS   zaggregate.<locals>.<genexpr>s   s     CaeeHo&B!CrT   r   	qc_resultNr2   r   PASSFAIL)WARNWARNINGd   c            
          ddddg ddddd	S )Nr   )	totalrN   rW   rY   duration_seconds_listqc_passqc_failqc_warnqc_nar;   r;   r<   r-   <lambda>zaggregate.<locals>.<lambda>   s$    %'

 r<   team_id	(unknown)ra   rM   rN   rY   rV   rW   duration_secondsrb   rf   rc   rd   re   <   )ra   rN   rW   rY   rc   rd   re   rf   qc_fail_pct
team_statsteam_avg_minutesstale_details)lensumrC   strupperroundr   boolrD   float	TypeErrorr6   itemsr'   )r   ra   rN   rW   rY   rc   rd   re   rf   rR   qcqc_total_ratedrl   rm   ro   teamsrM   is_staledurrn   lstavg_secs                          r-   	aggregater   m   sb    JEGuGGIT5TTECUCCG +,+G+g+% UU;:r[%9QJEW]]_&qLGW]]_&qLGW]]_ 33qLGQJE w&0N>Lq>P%.036:VZK -8
	
-J +-M !uuY.;t	'
a
x$[ kNaNy iLALj)*?f.?gJ!OJ  #ee&'?)*11%*= UU;a2:rgJ!OJW]]_&iLALW]]_&iLALW]]_ 33iLALgJ!OJC!H 46##% *a'(#hS)G%*7R<%;T"%)T"* ":&,& 3 z* s   LLL	all_tasksthreshold_hoursc                    t               }g }| D ]c  }|j                  d      dk7  rt        |j                  d            }|5||z
  j                         dz  }||k\  sQ|j	                  ||f       e |j                  d d       |S )uT   현재 running 상태이며 threshold_hours 이상 경과한 작업을 반환한다.rM   rY   rA   i  c                     | d   S )Nr   r;   )xs    r-   rg   z#find_long_running.<locals>.<lambda>   s
    ad r<   Tkeyreverse)r=   rC   r8   total_secondsrD   sort)r   r   r:   rE   rR   rG   elapsed_hourss          r-   find_long_runningr      s    
 +C13F .55?i'aeeL)*:r002T9O+MM1m,-. KKNDK1Mr<   rF   c                 h    | j                  d      xs | j                  d      xs d}t        |      S )Nstale_reasonrM   rW   )rC   rr   )rF   reasons     r-   format_stale_reasonr      s-    XXn%F();FwFv;r<   metricslong_runningreport_datec                   # |j                  d      }|j                  d      }|j                  d      }g }|j                  d|        |j                  d| d|        |j                  d       |j                  d       | d   }	| d   }
| d	   }| d
   }|j                  d|	 d|
 d| d| d	       | d   }| d   }| d   }| d   }| d   }|d}n| d}|j                  d| d| d| d| d| d       |j                  d       |j                  d       |j                  d       |j                  d       | d    #| d!   }t        #j                         #fd"d#$      }|D ]W  }#|   }|j	                  |      }|| d%nd}|j                  d&| d'|d    d'|d    d'|d	    d'| d'|d    d'|d    d(       Y |j                  d       |j                  d)       | d*   }|r|D ]  }|j	                  d+d      }|j	                  d,      xs d-}t        |j	                  d.            }|r|j                  d      nd}t        |      }|j                  d/| d0| d1| d1| d	        n|j                  d2       |j                  d       |j                  d3       |r|D ]  \  }} |j	                  d+d      }|j	                  d,      xs d-}t        |j	                  d.            }|r|j                  d4      nd}!t        | d5       d6}"|j                  d/| d7| d8|! d0|" d9	        n|j                  d2       |j                  d       d:j                  |      S );N%Y-%m-%du!   # 주간 메트릭 리포트 — u   > 집계 기간: z ~ r2   u	   ## 요약ra   rN   rW   rY   u   - 총 작업: u   건 (완료: u   건, stale: u   건, 진행중: u   건)rl   rc   rd   re   rf   zN/A%u   - QC FAIL 비율: z (PASS: z, FAIL: z, WARN: z, N/A: )u   ## 팀별 통계uM   | 팀 | 작업수 | 완료 | stale | 평균소요시간 | QC PASS | QC FAIL |z|---|---|---|---|---|---|---|rm   rn   c                     |    d   S )Nra   r;   )rR   rm   s    r-   rg   zbuild_report.<locals>.<lambda>(  s    :a=;Q r<   Tr   u   분z| z | z |u$   ## stale 작업 상세 (최근 7일)ro   task_idrh   ri   rA   z- z (z, u   - (해당 없음)u7   ## 장기 running 작업 경고 (현재 running 상태)z%Y-%m-%d %H:%Mr   u   시간z: u
   , 시작: u    경과)
)	strftimerD   sortedkeysrC   r8   r   rt   join)$r   r   r   r>   r?   date_str	since_str	until_strlinesra   rN   rW   rY   rl   rc   rd   re   rf   pct_strrn   sorted_teamsr{   r|   avgavg_strro   rR   r   rG   	date_partr   rF   	elapsed_h	start_strelapsed_strrm   s$                                      @r-   build_reportr      s    ##J/Hz*Iz*IE 
LL4XJ?@	LL$YKs9+>?	LL 
LLGE$IGEi G	LL>%i[UGScdkcllpqr-(Ki Gi Gi GGE M#	LL%gYhwixyPXY`Xaahinhoopqr	LL 
LL#$	LL`a	LL01&J12 *//+1Q[_`L 
t""4(!$SE+ec!G*S;(8AgJ< H	Qy\N#a	l^2?	
	
 
LL 
LL78*1/*BM 	IAeeIu-G55#2{D!%%-.B35J/5I(+FLL2gYbb2fXQGH	I 	()	LL 
LLJK+ 	]OD)hhy%0G88I&5+D$((<01B9;$45I"9a018KLL2gYbj2k]RZ[\	] 	()	LL99Ur<   overridec                     | r| S |j                  d      }t        j                  t        d       t        j                  j                  t        d| d      S )Nr   Texist_okzweekly-z.md)r   r   makedirsREPORTS_DIRr   r   )r   r   r   s      r-   resolve_output_pathr   Y  sF    ##J/HKKd+77<<wxj%<==r<   contentc                     t         j                  j                  |       }|rt        j                  |d       t	        | dd      5 }|j                  |       d d d        y # 1 sw Y   y xY w)NTr   wr   r   )r   r   dirnamer   r!   write)r   r   dirpathr*   s       r-   save_reportr   a  sT    ggood#G
Gd+	dC'	* a	  s   A""A+c                      t        j                  d      } | j                  dt        ddd       | j                  dt        d d	d
       | j                  dddd       | j                         S )NuE   task-timers.json에서 주간 메트릭 리포트를 생성합니다.)descriptionz--days   Nu   집계 기간 (기본: 7일))typedefaultmetavarhelpz--outputPATHu$   출력 파일 경로 오버라이드z--print
store_trueprint_outputu   콘솔에도 출력)actiondestr   )argparseArgumentParseradd_argumentintrr   
parse_args)parsers    r-   r   r   n  s    $$1xyF
+   3   "	   r<   c                  N   t               } t               }| j                  dk7  r|}|t        | j                        z
  }n|j	                         }|t        |j                               z
  }|t        d      z
  }|t        d      z
  }t        j                  |t        j                  j                               }t        j                  |t        j                  j                               }t        t              }t        |||      }	t        |	      }
t        |      }t               }t!        |
||||      }t#        | j$                  |      }t'        ||       | j(                  rt+        |       t+        d| t,        j.                         t-        j0                  d       y )Nr   )daysr   u   [OK] 리포트 저장 완료: r   r   )r   r=   r   r   dateweekdayr   combinemintimemaxr.   TASK_TIMERS_PATHrJ   r   r   r   r   outputr   r   r   r   r   r    )argsr:   r?   r>   todaythis_mondayprev_mondayprev_sundayr   period_tasksr   r   r   report_contentoutput_paths                  r-   mainr     sS   <D +CyyA~iTYY// 
iU]]_==!I1$55!I1$55  hll.?.?.AB  hll.?.?.AB +,I *)UEBL %G %Y/L +K!'<eUSN &dkk;?K^, n	*;-
8szzJHHQKr<   __main__)g      ?)r   N))__doc__r   r"   r   r   collectionsr   r   r   pathlibr   typingr   r   r	   r
   r   environrC   rr   __file__resolveparent_WORKSPACE_ROOTr   r   r.   r8   r=   rJ   r   rv   r   r   r   r   r   	Namespacer   r   __name__r;   r<   r-   <module>r      su  
   	 
 # (  3 3**..!13tH~7M7M7O7V7V7]7]3^_tO,x7:LLM $'(2Y>IJ'S 'T$sCx.1 '8HSM hx&8 $8 S#X  
$sCx.	6fT$sCx.) fd38n f` !DcN# 
%S#X%
&'2d38n  
X#s(^XuT#s(^U234X X 	X
 X 	X@>(3- >h >3 >c C D H&& 2,^ zF r<   