
    `gj!!                        d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	m
Z
 ddZddZddZdZdd	Zdd
ZddZedk(  r ej&                   e              yy)a  Classify candidate items by urgency/importance and emit only the urgent ones.

The proactive-monitor pattern: a fetch step (a watcher script, an inbox dump, a
feed) produces a list of candidate items; this script scores each with a cheap
LLM and prints ONLY the items at or above a threshold. Below-threshold runs
print nothing, so a cron job wrapping this stays silent unless something
actually matters -- the classic urgency-monitor pattern (fetch -> classify
urgency -> surface only what's above the bar).

Design choices:
  * Uses Hermes' auxiliary client with task="monitor", so the classifier model
    is configured once in config.yaml (auxiliary.monitor.{provider,model}) and
    can be a cheap fast model independent of the main chat model.
  * Reads items as JSON (a list of objects) from stdin or --input-file.
  * One LLM call scores the whole batch (cheap, single round-trip) and returns
    structured scores; we filter locally.
  * Empty result -> empty stdout -> the cron job's [SILENT]/empty-stdout path
    suppresses delivery. No spam on quiet intervals.

Usage (standalone):
  cat items.json | python classify_items.py --threshold 7     --criteria "Urgent if it needs a reply today or is from my manager/family"

Usage (wired to a watcher via cron, agent mode):
  Ask the agent: "Every 10 minutes, run watch_http_json.py for my inbox feed,
  pipe its JSON into classify_items.py with my urgency criteria, and deliver
  whatever it prints. Stay silent if it prints nothing."

Item schema (flexible): each item is an object; the classifier sees the whole
object. A "title"/"subject"/"summary"/"text" field helps it judge. An "id"
field (any of id/guid/message_id/url) is echoed back so duplicates can be
deduped upstream.
    )annotationsN)AnyDictListOptionalc                 6    t        | dt        j                  i y )Nfile)printsysstderr)argss    N/root/.hermes/venv/lib/python3.12/site-packages/cron/scripts/classify_items.py_eprintr   ,   s    	4!cjj!    c                   d}| r't        | d      5 }|j                         }d d d        nt        j                  j                         }|j	                         }|sg S 	 t        j                  |      }t        t              r't        |j                  d      t              r|d   S |gS t        |t              r!|D cg c]  }t        |t              s| c}S t        d       t        j                  d       y # 1 sw Y   xY w# t
        j                  $ r-}t        d|        t        j                  d       Y d }~d }~ww xY wc c}w )N zutf-8)encodingz)classify_items: input is not valid JSON:    itemsz6classify_items: expected a JSON list or {items: [...]})openreadr   stdinstripjsonloadsJSONDecodeErrorr   exit
isinstancedictgetlist)
input_filerawfdataexs         r   _load_itemsr(   0   s   
C*w/ 	1&&(C	 	 iinn
))+C	zz# $dhhw'.= v$7a:a#677DEHHQK)	 	  ;A3?@ 8s/   C=D	 ?EE=D	E	#EE	c                Z    dD ]"  }| j                  |      }|st        |      c S  d| S )N)idguid
message_idurllinkzitem-)r    str)itemindexkeyvals       r   _item_idr4   J   s8    : hhsms8O 5'?r   a  You are an urgency classifier for a proactive assistant. You will be given a numbered list of items and the user's importance criteria. Score EACH item from 0 (ignore entirely) to 10 (interrupt the user now). Return ONLY a JSON array, one object per item, in the same order: [{"index": <int>, "score": <int 0-10>, "reason": "<short>"}]. No prose, no markdown fences. Be conservative: most items should score low. Only score high when the item clearly meets the user's criteria.c           
        d| ddg}t        |       D ]O  \  }}dD ci c]  }||v r|||    }}|s|}|j                  d| dt        j                  |d      d d	         Q |j                  d
       dj	                  |      S c c}w )NzUSER IMPORTANCE CRITERIA:

zITEMS:)titlesubjectsummarytextbodyfromsenderr-   [z] F)ensure_asciii  zG
Return the JSON array of scores now (one object per item, same order).)	enumerateappendr   dumpsjoin)r   criterialinesir0   kviews          r   _build_promptrI   ]   s    *8*B7BEU# 	M4 ^
Dy tAwJ
 

 Dq2djjEB5DIJKL	M 
LLR 99U
s   Bc                   | xs dj                         }|j                  d      r*|j                  d      }d|v r|j                  dd      d   }	 t        j                  |      }i }t        |t              rN|D ]I  }t        |t              s|j                  d      }t        |t              s6d|cxk  r|k  sBn E|||<   K |S # t        j
                  $ r |j                  d      }|j                  d      }|dk\  rJ||kD  rE	 t        j                  |||dz          }n7# t        j
                  $ r t        d	       i cY cY S w xY wt        d
       i cY S Y w xY w)Nr   z````r6      r>   ]r   z1classify_items: could not parse classifier outputz1classify_items: classifier returned no JSON arrayr1   )r   
startswithsplitr   r   r   findrfindr   r   r!   r   r    int)	contentn_itemsr:   arrstartendoutobjidxs	            r   _parse_scoresr[   o   sQ   Mr  "Duzz#4<::dA&q)Djj &(C#t 	Cc4('''"C#s#S(:7(:C	 J+  		#jjoA:#+jjecAg!67'' KL	 GHI s6   C ?ED$#E$ E	EE		EEc                    t        j                  d      } | j                  ddd       | j                  dt        dd	
       | j                  dd d       | j                  dddgdd       | j	                         }t        |j                        }|sy	 ddlm} t        ||j                        }	  |dd|dgdd      }|j                  d   j                  j                  }t!        |t"              s|rt#        |      nd}t%        |t'        |            }g }	t)        |      D ]n  \  }
}|j+                  |
      }t!        |t,              r|j+                  d      nd }t!        |t              sK||j.                  k\  s[|	j1                  |
||f       p |	sy|j2                  dk(  rg|	D 
cg c]6  \  }
}}t5        ||
      |j+                  d      |j+                  d d      |d!8 }}}
}t7        t9        j:                  |d"d#$             yg }|	D ]  \  }
}}|j+                  d%      xs2 |j+                  d&      xs |j+                  d'      xs t5        ||
      }|j+                  d(      xs |j+                  d)      xs d}|j+                  d d      }d*|j+                  d       d+| }|r|d,| z  }|r	|d-| d.z  }|j1                  |        t7        d/j=                  |             y# t        $ r}t        d|        Y d }~yd }~ww xY w# t        $ r}t        d|        Y d }~yd }~ww xY wc c}}}
w )0Nz1Classify items by urgency; emit only urgent ones.)descriptionz
--criteriaTz#Plain-language importance criteria.)requiredhelpz--threshold   z+Minimum score (0-10) to surface. Default 7.)typedefaultr_   z--input-filez0Read items JSON from this file instead of stdin.)rb   r_   z--formatr:   r   z!Output format for surfaced items.)choicesrb   r_   r   )call_llmz0classify_items: cannot import auxiliary client:    monitoruser)rolerS   i   )taskmessages
max_tokenstemperaturer   z(classify_items: classifier call failed:    scorereason)r*   rn   ro   r0   Fr   )r?   indentr7   r8   r9   r-   r.   z## [z/10] r6   z
__z

)argparseArgumentParseradd_argumentrR   
parse_argsr(   r"   agent.auxiliary_clientrd   	Exceptionr   rI   rD   rc   messagerS   r   r/   r[   lenr@   r    r   	thresholdrA   formatr4   r
   r   rB   rC   )parserr   r   rd   r&   promptresprS   scoressurfacedrF   r0   srn   rX   blocksr7   r-   ro   blocks                       r   mainr      sK   $$1deF
t:_`
CAno
;mn

VV,<fSvwD(E3
 5$--0F%&9:	
 ,,q/))11'3'&-c'l2G 7CJ/FHU# *4JJqM",Q"5g4eS!et~~&=OOQaL)	* {{f !)
 
 D! tQ'w%%"-	
 
 	djj5;<& # $ 	!LQa! %88I&%88I&% D!$	  ((5/;TXXf%5;CUU8R(F155>*%w7E2cU#3vha(MM% 	! 	fkk&!"}  B1#FG   	:1#>?	&
s7   K7 5AL 2;L?7	L LL	L<$L77L<__main__)r   r   returnNone)r"   zOptional[str]r   List[Dict[str, Any]])r0   zDict[str, Any]r1   rR   r   r/   )r   r   rD   r/   r   r/   )rS   r/   rT   rR   r   zDict[int, Dict[str, Any]])r   rR   )__doc__
__future__r   rr   r   r   typingr   r   r   r   r   r(   r4   _CLASSIFY_INSTRUCTIONSrI   r[   r   __name__r    r   r   <module>r      sj    D #   
 , ,"4G $BNb zCHHTV r   