
    q%jC3                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlmZm	Z	m
Z
 ddlmZ ddlZddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZ ddlmZ  ej6                  d      Z eej<                  j?                  dd            Z  e!ej<                  j?                  dd            Z"ej<                  j?                  dd      jG                         dk(  Z$ e%       Z&de'd<   dZ(ddZ)ddZ*d dZ+d!dZ,d"dZ-d!dZ.d#d$dZ/y)%u  階段 9-C:Cooper 「點頭」+「體悟整理」。

對應 Robert 的需求 ②③:
  ② 群組分享體悟 → 庫柏整理到排程系統 → 加深思考 → 引導大家
  ③ 庫柏總是在群組訊息時表示「有收到 / 已記錄」(不洗版)

設計:
  1. 每則訊息進來,跑一次 Haiku 判斷類別 + 重要性
     - 類別:assignment / acceptance / progress / question / 體悟 / decision / 閒聊
     - 重要性 0-1
  2. 重要性 < 0.5 或閒聊 → 完全不 ack(避免洗版)
  3. 重要性 >= 0.5 → ack 一則極短訊息(模板化,< 30 字)
     - 對體悟:「📝 已記《...》明天簡報撈出來討論」+ 寫 reflection note
     - 對求助:「👀 看到了,等下幫看」
     - 對決議:「✅ 記下:《...》」
     - 對進度:「📈 收到」
  4. 同 channel 5 分鐘冷卻,避免連 ack 多則洗版

成本評估(Haiku 4.5):
  - 輸入 ~250 tokens / 訊息 → $0.00025
  - 輸出 ~30 tokens / 訊息(只要 JSON 標籤)→ $0.00015
  - 平均 ~$0.0004/訊息
  - 8 人群組假設 200 訊息/天 → $0.08/天 ≈ $2.4/月
  - 加上 ack push 訊息(只對 50% 重要的)→ 模板字串,沒額外 LLM cost
  → 月支出預估 < $3,可控
    )annotationsN)datetime	timedeltatimezone)UUID)select)settings)AsyncSessionLocal)AgentActivity)ChannelMessage)Userzcoba.acknowledgeACK_COOLDOWN_MIN5ACK_IMPORTANCE_MINz0.5ACK_ENABLEDfalsetrueset_PENDINGu   你是「群組訊息分類員 + 重要性評估員」。讀一則團隊訊息,判定類別 + 重要性。

【類別(只能挑一個)】
  - assignment       派工(指派誰做什麼)
  - acceptance       認領 / 確認(收到、OK、好)
  - progress         進度回報、做完了、卡住
  - question         求助、問問題
  - reflection       體悟、心得、復盤、學到、卡住、突破、教訓、沒想到、提醒自己、警惕、反省、第一次、才發現、原來、應該要、下次要、不該、悟到、頓悟
  - decision         決議、定案、不要做了、改方向
  - announcement     公告、通知、行政
  - chitchat         閒聊、午餐、貼圖、emoji 反應、感謝詞

【重要性 importance(0.0–1.0)】
  - 0.9–1.0:重大決議 / 客戶相關 / 阻塞中
  - 0.6–0.8:派工、進度、求助、體悟
  - 0.3–0.5:認領、公告
  - 0.0–0.3:閒聊、貼圖、感謝詞

【若 category = reflection,額外給】
  - tag:技術 / 客戶 / 團隊 / 自我管理 / 流程 / 商業 / 學習 / 其他
  - actionable:這個體悟是否可以變成 todo(true / false)
    例:「下次要先 review 才上線」→ true
    例:「今天才發現原來自己以前都搞錯方向」→ false(只是 insight)

【輸出嚴格 JSON,沒其他字】
{
  "category": "...",
  "importance": 0.0,
  "summary": "12 字內(體悟、決議要抓重點;閒聊用『—』)",
  "tag": "...(只在 reflection 時必填)",
  "actionable": true/false (只在 reflection 時必填)
}
c           
     (  K   t         j                  sy	 t        j                  t         j                        }|j                  j                  t         j                  dt        dd| dd  ddg	       d{   }d
j                  d |j                  D              }t        j                  d|t        j                        }|syt        j                  |j                  d            S 7 s# t         $ r t"        j%                  d       Y yw xY ww)u$   跑 Haiku 分類。失敗回 None。N)api_keyx   useru
   訊息:「iX  u   」)rolecontent)model
max_tokenssystemmessages c              3  T   K   | ]   }|j                   d k(  s|j                   " yw)textN)typer#   ).0bs     A/volume1/homes/robertsu/coba/app/app/services/coba_acknowledge.py	<genexpr>zclassify.<locals>.<genexpr>l   s      Glaff6Faffls   ((z\{.*\})flagsr   u   classify 失敗)r	   ANTHROPIC_API_KEY	anthropicAsyncAnthropicr    createCLAUDE_MODEL_HAIKUCLASSIFY_PROMPTjoinr   researchDOTALLjsonloadsgroup	Exceptionlogger	exception)r   clientresprawms        r'   classifyr>   `   s     %%))(2L2LM__++--"%Jwt}oS2QRS	 , 
 
 ggGdllGGIIiBII6zz!''!*%%
  *+sH   DA#C. 8C,9AC. D#C. +D,C. .DDDDc                  K   t        j                  t        j                        t	        |      z
  }t               4 d {   }|j                  t        t              j                  t        j                  dk(  t        j                  |k\               d {   j                         j                         }d d d       d {    t        |       t        fdD              S 7 7 Q7 '# 1 d {  7  sw Y   7xY ww)N)minutesackc              3  h   K   | ])  }|j                   xs i j                  d       k(  s&d + yw)
channel_id   N)extraget)r%   rcids     r'   r(   z$_recent_ack_count.<locals>.<genexpr>   s,     K$Q177=b"5"5l"Cs"Jq$s   '22)r   nowr   utcr   r
   executer   r   whereactivity_type
created_atscalarsallstrsum)rC   r@   cutoffdbrowsrH   s        @r'   _recent_ack_countrV   v   s     \\(,,')G*DDF ""bjj=!''++u4((F2
 

 79SSU 	 #" j/CK$KKK #
 #"""sZ   ?DC*DAC0C, C0;DC.$D,C0.D0D6C97D>Dc                    |xs dj                         }|dv rd}|rd| dnd}| dk(  rd| dS | dk(  rd	| S | d
k(  rd| dS | dk(  rd| S | dk(  rd| S d| S )Nr!   )u   —-r!   u   《u   》
reflectionu   📝 已記u    等下幫你歸到知識庫decisionu   ✅ 決議記下questionu   👀 看到u    等等幫看progressu   📈 收到進度announcementu   📌 公告收到u   📥 收到)strip)categorysummarysnippets      r'   	_ack_textrb      s    }"##%G""$+G9C G<WI%ABB:!'++:WI]33:"7),,>!"7),,	""    c                r  K   t         syt               4 d{   }|j                  t        t              j                  t        j                  | k(               d{   j                         }||j                  	 ddd      d{    y|j                  dk7  r	 ddd      d{    y|j                  xs dj                         }|rt        |      dk  r	 ddd      d{    y|j                  t        t              j                  t        j                  |j                  k(               d{   j                         }||j                  s	 ddd      d{    yd|v s$d|j!                         v sd|j!                         v r	 ddd      d{    yddd      d{    t#        j                  t$               d{   dk\  ry	 d	d
lm} t               4 d{   } ||j                  |j                  |       d{   }ddd      d{    ry	 t-               d{   }|syt/        |j1                  d      xs d      }|j1                  d      xs d}	|j1                  d      xs d}
t3        d|	 d|dd|
dd  |j                  |j                  t5        |j                        t5        |       |	||
d       d{    |t6        k  s|	dk(  ry|	dk(  r|j1                  d      xs dj                         xs d}t9        |j1                  d            }g }	 d	dlm}  ||dd d       d{   }t?        j@                  tB        jD                        tG        d      z
  }|xs g D ]  }t5        |j1                  d            t5        |       k(  r*|j1                  d       }|r-	 t?        jH                  |jK                  d!d"            }||k  ri	 |j1                  d#      xs d	d$k\  s|jQ                  |j1                  d      tS        |j1                  d#d	      d%      |j1                  d&      xs ddd' d(        |dd) }t3        d+d,|
dd-  |j                  |j                  t5        |j                        t5        |       |dd. |
|||t        |      d/       d{    ytY        |	|
      }	 d	d0l-m.} |j_                  j                  |       d{    t3        d2|	 d3| |j                  |j                  t5        |j                        t5        |       |	||d4       d{    y7 K7 7 7 7 7 57 7 7 # 1 d{  7  sw Y   xY w7 7 7 7 r# 1 d{  7  sw Y   xY w# t*        $ r Y w xY w7 7 7 q# tL        tN        f$ r Y w xY w# t*        $ r tT        jW                  d*       Y w xY w7 87 # t*        $ r tT        jW                  d1       Y "w xY w7 ׭w)5u   背景任務:跑 ack 流程。Nr#   r!      u   庫柏cooperz@cobarD   r   )is_in_engagement_db
importanceg        r_   chitchatr`   r>   z (z.2fz):    )rC   
message_idr_   rh   r`   )user_idrC   rE   rY   tag
actionable)search_relevanti,     )querylimit)daysrk   rN   Zz+00:00scoregffffff?   r   P   )rk   ru   preview   u    reflection 相似度比對失敗reflection_noteu   體悟:<   i  )rC   rk   r<   r`   rm   rn   
similar_tosimilar_count)line_clientu   ack push 失敗rA   z: )rC   rk   r_   rh   ack_text)0r   r
   rK   r   r   rL   idscalar_one_or_nonerl   message_typer   r^   lenr   rC   line_source_idlowerrV   r   app.services.coba_chatrg   r7   r>   floatrF   _logrQ   r   boolapp.services.coba_memoryro   r   rI   r   rJ   r   fromisoformatreplace
ValueError	TypeErrorappendroundr8   r9   rb   app.servicesr~   	push_text)rk   rT   msgr   chrg   in_engresultrh   r_   r`   ref_tagrn   r|   ro   sim_results	cutoff_dtsts_strdtr#   r~   s                         r'   try_ackr      s     ""bZZw 5 5gjjJ6N OPPddf;#++- #"" v% #"" ;;$"++-#g,* #"" JJvg44WZZ3>>5QRSSggi:R.. #"" w(gmmo"=GMMOA[ #""""" s~~/?@@AE>$&&".s~~s{{BOOF '&  G$$Fvzz,/637Jzz*%3Hjj#)rG zhZr*S)9WSb\NK{{s~~!$S^^!4!$Z'!+%   &&(j*@ <::e$*113;t&**\23

	A@ /gdsm2 NNK X\\2YB5GGI!'R'quu\*+s:>|,%33FNN34QR	>$ * EE'N'aC/%%&'eeL&9!&quuWa'8!!<$%EE)$4$:CR#@'  ($ $BQJ $WSb\N+;;3>>%(%8%(_%dsm")%%/%/(+J	 	 	 	 Xw'D,,##B$5$5t<<< u2dV${{s~~!$S^^!4!$Z'!+#	 	 	_ #P #"" T #"""""" A 'O '&&&   %0 O '	2   	A?@	A	( 	= ,*+,	sP  X7U#X7AU>U&!U> X7U)X7U>!X7,U,-X72/U>!X7,U/-X72AU>=U2>!U>X7*U5+X70)U>X7$U8%X75U;6!X7VX7!V6 5V6V6 9"V VV  V6 +V,V6 2X7WB$X7'W	(AX7W% WA7W% *W>W% AW% 4AX7X
X7%%X 
XX AX7X5X7&U>)X7,X7/X72U>5X78X7;X7>VVV	X7V6 V V6  V3&V)'V3.V6 6	W?X7WX7	X7W% W"W% !W""W% %XX7XX7X X2.X71X22X7c           	     @  K   	 t               4 d {   }|j                  t        | |||             |j                          d {    d d d       d {    y 7 K7 7 	# 1 d {  7  sw Y   y xY w# t        $ r t
        j                  d|        Y y w xY ww)N)rM   rl   r`   rE   zlog %s failed)r
   addr   commitr7   r8   r9   )rM   r`   rl   rC   rE   rT   s         r'   r   r     s     
9$&&"FF=+	  ))+ '&&  '&&&  9-89s   BA9 AA9 2A$A 	A$A9 A"A9 BA9  A$"A9 $A6*A-+A62A9 5B6A9 9BBBBc                    	 t        j                         }|j                  t	        |             }t
        j                  |       |j                  t
        j                         y# t        $ r Y yw xY w)u   fire-and-forget。N)	asyncioget_running_loopRuntimeErrorcreate_taskr   r   r   add_done_callbackdiscard)rk   looptasks      r'   trigger_ackr   "  s`    '') GJ/0DLL8++,	  s   A% %	A10A1c                  K   t        j                  t        j                        t	        |       z
  }t               4 d{   }|j                  t        t              j                  t        j                  dk(  t        j                  |k\  t        j                  j                  d            j                  t        j                  j                                      d{   j!                         j#                         }t%        |D ch c]  }|j&                  s|j&                   c}      }i }|r|j                  t        t(              j                  t(        j*                  j-                  |                   d{   j!                         j#                         }|D ci c]  }|j*                  |j.                   }}ddd      d{    g }	D ]  }|j0                  xs i }
|	j3                  t5        |j*                        t5        |j*                        dd |j                  j7                         j9                  |j&                  d      |
j9                  d      xs d|
j9                  d      xs ddd	 |
j9                  d
      d        |	S 7 _7 c c}w 7 'c c}w 7 # 1 d{  7  sw Y   xY ww)u6   列過去 N 小時的 reflection_note(預設一週)。)hoursNrz      u   (未知)r`   r!   r<      rm   )r   id_shortrN   byr`   r<   rm   )r   rI   r   rJ   r   r
   rK   r   r   rL   rM   rN   
deleted_atis_order_bydescrO   rP   listrl   r   r   in_display_namerE   r   rQ   	isoformatrF   )r   rS   rT   rU   rG   uids	users_mapusuoutes              r'   list_recent_reflectionsr   2  s    \\(,,')%*@@F ""bjj=!''++/@@((F2((,,T2 h}//4467
 
 79SSU 	 :1		QYY:;	

6$<#5#5dggkk$6G#HIIRRTXXZB79:r!q~~-rI: #" CGGMr

add)ADD	"1,,002--		:6uuY'-2EE%L&B-55<
 	  J5 #
 ; J: #"""s   ?K
J K
BJ5"J##)J5J&
J&
,AJ5J+$J5)J.J5	K
J3CK
#J5&J5.J53K
5K;J><KK
)r   rQ   returnzdict | None)rC   r   r@   intr   r   )r_   rQ   r`   rQ   r   rQ   )rk   r   r   None)rM   rQ   r`   rQ   rE   dict)   )r   r   r   z
list[dict])0__doc__
__future__r   r   r4   loggingosr1   r   r   r   uuidr   r+   
sqlalchemyr   
app.configr	   app.databaser
   app.models.agent_activityr   app.models.channelr   r   app.models.userr   	getLoggerr8   r   environrF   r   r   r   r   r   r   r   __annotations__r/   r>   rV   rb   r   r   r   r    rc   r'   <module>r      s   4 #    	 	 2 2     * 3 /   
		-	. rzz~~&8#>? 2::>>*>FG jjnn]G4::<F #  F,
L#$|~9- rc   