
    :j                    */   d Z ddlZddlZddlZddlmZ ddlmZ  ed      Z ee      j                  dz  Z
 ee      j                  dz  Z ee      j                  dz  Zg Zej                         rP	  ej                  ej!                               Zer+ eed   e      r eeD  ch c]  } d	| v s| d	    c}       ZneZ ej,                  e      Zg Z eed
      5 Z ej6                  e      ZeD ]  Z ee      dk  red   j=                         Z ee      dkD  red   j=                         ndZ  ee      dkD  r$ed   j=                         jC                         dk(  ndZ" ee      dkD  r$ed   j=                         jC                         dk(  ndZ# ee      dkD  red   j=                         ndZ$ejK                         dv se dk(  re jM                  d      sese ZejO                  ee jK                         e"e#e$d        	 ddd        ee      j                  dz  Z( e)       Z*e(j                         rUe(j!                         jW                         D ]4  Z,e,j=                         jK                         Z-e-s$e*j]                  e-       6 i Z/ ee
d
      5 Z ej`                  e      ZeD ]  Zed	   j=                         jK                         Z-e-e*v r)ejc                  dd      ejc                  dd      ed   ed   ed   ed    eed         dkD  red   dd d z   ned    eed!         dkD  red!   dd d z   ned!   ed"   ejc                  d#d      ejc                  d$d%      ejc                  d&d      d'e/e-<    	 ddd       d(e2d)efd*Z3d+e2d)efd,Z4h d-Z5i Z6eD ]  Z7e7d	   jK                         j=                         Z8e7jc                  d.d      xs dj=                         Z9d/e9v rd0ne9re9jM                  d      sd1nd ee9      z   Z:e8e6vse:e6e8   d   kD  s~e7e:fe6e8<    e6jw                         D cg c]  }|d   	 c}Zg Z< e)       Z=eD ]  Z>e>d	   jK                         j=                         Z-e-e5v r)e/jc                  e>d	         Z?e?s@ e4e?d         Z@ e3e>d(         ZA eBe@eA      ZCe<jO                  i e>e?eCe@d2       e=j]                  e-        e/j                         D ]a  \  Z-Z?e-e=v se-e5v re?jc                  d      xs dj=                         xs e-Z9e9e-ddddZE e4e?d         Z@e<jO                  i eEe?e@e@d2       c d3 ZFe<j                  d4 d56        ej                  d7ej                        ZJd8e2d)eKfd9ZLd: ZMd;d<d=d>d?d@ZNdAdBdCdDdEd@ZOdFed)e2fdGZPddlZQ ee      j                  dHz  ZRi ZSeRj                         r!	  eQj                  eRj!                               ZSddIlTmUZU ddJlmVZW  ee      j                  dz  ZX eU       ZYi ZZeXj                         r	  eQj                  eXj!                               Z[dK Z\ e]e[e\L      Z^i Z_e^D ]  Z`e`jc                  d	d      j=                         jK                         Z8e8s4e`jc                  dM      dNk7  rI e\e`      Zaeaej                  k(  rae8e_v reae_e8   z
  j                         dOk  reae_e8<   eYe8xx   dz  cc<   e8eZvs	eaeZe8   kD  seaeZe8<    	 g g dPZd ee      j                  dQz  Zeeej                         r!	  eQj                  eej!                               Zdedjc                  dRg       D ci c]  }|dS   |
 c}ZfdTg iZg ee      j                  dUz  Zhehj                         r!	  eQj                  ehj!                               ZgdVe2fdWZidVe2fdXZjdVe2fdYZkdVe2d)elfdZZmdZnd*d	e2d[e2de2dVe2d)e2f
d\Zod]e2d)e2fd^Zpd+dVe2d_eld)e2fd`ZqeND ci c]  }|d c}ZrdddddddddddddddaZsdZte<D ],  Z> eMe>      ZueOeu   ZveNeu   Zwe>db   Z? eoe>d	   e?re?jc                  dd      nde?re?jc                  dd      nde?re?jc                  d"d      nd      ZxdZyexdck(  rMeZjc                  e>d	   jK                         j=                               Zzezrez eWend      z   j                  de      ZydZ|ereuxx   dz  cc<   exdfk(  resdfxx   dz  cc<   nexdck(  resdcxx   dz  cc<   nexdgk(  resdgxx   dz  cc<   nexdhk(  resdhxx   dz  cc<   nexdik(  resdixx   dz  cc<   nexdjk(  resdjxx   dz  cc<   nwexdkk(  resdkxx   dz  cc<   ndeudlk(  resdmxx   dz  cc<   nQeudNk(  rJ eme?re?jc                  d"d      nd      Z| eb eBe|d      d      Z|esdnxx   dz  cc<   esdoe| xx   dz  cc<   ndZ|eudNk(  re|dkD  rdpdqdrdsZ}dBdtdAdsZ~e}e|   Zwe~e|   Zve>d	   j                  d      Zdue dvZe?rdwe?d    neZ ePe>dx         Ze?re?d   ne>d(   xs dyZe?r(e?d   dzk(  rd{Z epe?d!         Zd|Znd}Z epe?d         Zd~ZndZdZdZdZe?r0e?jc                  d#      re?d#   jM                  d      s epe?d#         Ze?re?jc                  d$      r ele?d$         ndZe?re?jc                  d&d      ndZdev r#ej                  d      d   j=                         neZdev r#ej                  d      d   j=                         ndZdAdBdEdDdddZejc                  ed      Zde dedk\  rdAnedk\  rdBnd de dZerde d epe       de dndZe>d   rdndZe>d   rdndZe>d(   rd epe>d(          dndZe>d	   j                  d      jK                         Z ej,                  dde      Zg Ze?r%e?jc                  d      rejO                  e?d          e>jc                  d.      rejO                  e>d.          dZeD ]  Z ej                  dej=                               D ]_  Z ej,                  dde      Z ee      dk\  s$ejK                         ek7  s8ed   jC                         edd jK                         z   Z n es n erde ndZe dZe dZe dZe dZe?re?jc                  dd      ndZer"e?r e?jc                  d      dzk(  rd epe       dndZe?r5e?jc                  d      dzk(  r!e?jc                  d"      rd eqe?d"   d1       dndZexdck(  rdey dZn2exdfk(  rdZn*exdgk(  rdZn"exdhk(  rdZnexdik(  rdZnexdjk(  rdZn
exdkk(  rdZndZerde d epe?d#          de de de dndZde d epe       de de d epe       de de d epe       de de d epe       de dZe?rde e e e e dndZerde de de dndZetdjU                  g deu deu deudlk(  rdneudk(  rdnd dex dey dejK                          dÑe dđe| dő epe>d.   jK                                dƑ epe>d	          dǑev dew dȑe dɑe dʑe dˑ epe>d.          d̑e d͑ epe>d	          dΑe?rFdez   dz   e?r0e?jc                  d#      re?d#   jM                  d      s epe?d#         ndz   dz   ez   dz   nd dɑe?re?jc                  d      dzk(  rdnd dӑe dԑedyk7  redd0 ndy dՑe dɑe d֑e dɑe dɑe dב      z  Zt/ egjc                  dTg       D ch c]0  }|jc                  d	d      jK                         j=                         2 c}Zedjc                  dg       D ]  Zejc                  d	d      Z ejc                  d.e j                  d            Ze s:e jK                         j=                         Zeev r]ee5v rbeSjc                  ei       jc                  d٫      dv resdxx   dz  cc<   efjc                  ejc                  dd      i       Zejc                  ddޫ      Zejc                  dd      Zejc                  dd      Zejc                  dd      Ze j                  d      Zdue dvZejc                  dd      Zerdwe neZej=                         Z ej                  de      ZdZeD ]m  Z ej,                  dde      Z ee      dk\  s$ejK                         ejK                         k7  sFed   jC                         edd jK                         z   Z n erde ndZe de dZdZedk(  r#ejc                  d      rd eped   dd        dZetdjU                  g d epejK                                dƑ epe        de dˑ epe       d̑e d͑ epe        d̑e dБ epe       de derdnd de dԑ epedd0        d epe       d/erde dnd e de d epe       de d epe       de d      z  Zt egjc                  dTg       D ]t  Zejc                  d	d      Z ejc                  d.e j                  d            Ze s:e jK                         j=                         Zee5v r]eSjc                  ei       jc                  d٫      dv resdxx   dz  cc<   e j                  d      Zdue dvZejc                  dd      Zerdwe neZejc                  dd      Zej=                         Z ej                  de      ZdZeD ]m  Z ej,                  dde      Z ee      dk\  s$ejK                         ejK                         k7  sFed   jC                         edd jK                         z   Z n erde ndZe dZe d ed      z   dz   Ze d ed      z   dz   ZetdjU                  g d epejK                                dƑ epe        de dˑ epe       d̑e d͑ epe        d̑e dБ epe       de derdnd d  epedd0        de d epe       de d epe       de de d epe       de de d epe       de d      z  Ztw djU                  d eND              Zed e2 ee<            z   dz   z  Zedz  ZeZ eQj,                  eS      ZdjU                  g d	 ej                         j                  d
       d ej                         j                  d
       desdm    desdm    desdn    desdf    desdc    desd    desd    desdi    desdj    desdk    desdg   esdh   z    desdn    desd    desd    desd    de d ee<       d et d!eÛ d"      Zej                  ed
        eȐd#e        ddlZ ej                         d$z  Z eɐj                  ee˫        eȐd%ej>                           eȐd& ee<       d'       erj                         D ]  \  ZZes	 eȐd(eNe    d)eΛ         yc c} w # e$ r Y mw xY w# 1 sw Y   $xY w# 1 sw Y   xY wc c}w # e$ r i ZSY Ww xY w# e$ r Y Qw xY w# e$ r Y w xY wc c}w # e$ r Y w xY wc c}w c c}w (,  z9Build Network Audit HTML from Google Sheet + DM scan CSV.    N)datetime)Pathz/tmp/keith_network.csvzfollow_up_FULL.csvznetwork_audit.htmlzactivity_log.jsonhandlezutf-8)encoding       TRUEF      )zcontact infozmarc.paffrathzContact Info@)namer   reached_outfollowed_upnotezskip_list.txtdisplay_name
focus_hintfollow_up_typelast_msg_datelast_sender	thread_idkeith_last_msg   u   …other_last_msgconversation_summarysuggested_reply
lead_score0intent)r   r   r   r   r   r   r   r   r   r   r   r   r   returnc                    | j                         } | st        j                  S t        j                  d|       }|d   j                         }dD ]  }	 t        j
                  ||      c S  t        j                  S # t        $ r Y 8w xY w)Nz[+,])z%d%b%yz%d%b%Yz%d %b %y%d %b %Y)stripr   minresplitstrptime	Exception)r   partslastfmts       -/Users/daichi/keith-dm-scanner/build_audit.pyparse_note_dater.   Y   s    ::<D||HHWd#E9??D; 	$$T3//
 <<  		s   A;;	BBdc                     | st         j                  S 	 t        j                  | d      S # t        $ r t         j                  cY S w xY w)N%Y-%m-%d %H:%M)r   r%   r(   r)   )r/   s    r-   parse_dm_dater2   f   sA    ||  $455 ||s   * AA>	   @aliseabass@allymacpole@drpeterrohde@finnwheatley@alexislumpkin@cristallelove@nature_in_lex@aprilvaccaroart@the.ecosystem.movementr    
      )dm	best_datedm_datec                 R    | d   }|r|j                  d      rt        |d         S dS )Nr?   r   r   )getint)rr?   s     r-   r   r      s-    	
4B$&266,+?3r, FQF    c                     | d   S )Nr@    )xs    r-   <lambda>rJ      s
    !K. rF   T)keyreverseu,   ^[\U00010000-\U0010ffff☀-⟿ἰ0-ᾯF\s]+$textc                     | j                         }|syt        j                  d|t        j                        ryt        j                  dd|      }t        |      dk(  xr t        |      dkD  S )zZTrue if the message is just an emoji reaction (heart, thumbs up, etc.) with no real words.Fz^reacted .+ to (your )?messageTu)   [\U00010000-\U0010ffff☀-⟿ἰ0-ᾯF\s]r	   r   )r$   r&   match
IGNORECASEsublen)rM   tstrippeds      r-   _is_reaction_onlyrU      sZ    

A	xx11bmmDvvBBJHx=A,#a&1*,rF   c                    | d   }|rl|d   dk(  rc|j                  dd      }|rNt        |      }t        |      }|r|r||kD  ry|j                  dd      j                         }|rt	        |      ryyy| d	   ry
| d   ryy)Nr?   r   NEEDS_REPLYr   r	   bump_upr   needs_replyr   doner   not_contacted)rC   _latest_keith_date_latest_other_dater$   rU   )rowr?   summarykeith_latestother_latest
other_lasts         r-   statusrc      s    	TB	=0ff3R8G1':1':9T$VV$4b9??A
"3J"?$ 
=
=rF   u   ⚡ NEEDS REPLYu   ↑ BUMP UPu   ✓ DONEu   → REACHED OUTu
   · NOT YET)rY   rX   rZ   r   r[   z#ff3b30z#ff9500z#30d158z#0a84ffz#636366dtc                     | t         j                  k(  ryt        j                         | z
  }|j                  }|dk(  ry|dk(  ry|dk  r| dS |dk  r|dz   d	S |d
k  r|dz   dS |d
z   dS )N   —r   todayr   	yesterday   zd ago   zw agoim  zmo agozy ago)r   r%   nowdays)rd   deltar/   s      r-   days_agorn      s    	X\\LLNRE

AAvAv1u1#U#2v1a4&&3w1b5'((fXUrF   zbuckets.json)Counter)	timedeltac                 r    t        j                  | d   d      S # t        $ r t         j                  cY S w xY w)Ntsr1   )r   r(   r)   r%   )_es    r-   	_parse_tsrt      s.     ))"T(4DEE1X\\11s    66)rK   typerX   i,  )postsengagerszengagers.jsonrv   id	followersznew_followers.jsonr_   c                    t               }| s|S | j                  d      D ]  }t        j                  d|      }|s|j	                  d      j                         j                         }|dk7  rO|j	                  d      j                         }dD ]7  }	 |j                  t        j                  ||      j                                   |S # t        $ r Y Iw xY w)zLSet of dates (date objects) where keith sent a message in the convo summary. | \[([^\]]+)\]\s*([^:]+):r   keithr   z%d.%m.%Yr1   z%d.%m.%y)setr'   r&   rO   groupr$   loweraddr   r(   dater)   )r_   outentrymsenderdate_strr,   s          r-   _keith_message_datesr   "  s    
%C3Ju% HH/7(!!#))+Wh771:##%= 	C))(C8==?@	 J  s   3C	CCc                    | syd}| j                  d      D ]  }t        j                  d|      }|s|j                  d      j	                         }|j                  d      j	                         j                         }|dk7  rndD ]#  }	 t        j                  ||      }|||kD  r|}   |S # t        $ r Y 5w xY w)zGParse conversation_summary, return most recent KEITH message timestamp.Nr{   r|   r   r   r}   r~   	r'   r&   rO   r   r$   r   r   r(   r)   r_   latestr   r   r   r   r,   rr   s           r-   r\   r\   4      4Fu% HH/7(771:##%!!#))+Wh= 	C&&x5>R&[F	 M     B11	B=<B=c                    | syd}| j                  d      D ]  }t        j                  d|      }|s|j                  d      j	                         }|j                  d      j	                         j                         }|dk(  rndD ]#  }	 t        j                  ||      }|||kD  r|}   |S # t        $ r Y 5w xY w)zNParse conversation_summary, return most recent OTHER (non-keith) message date.Nr{   r|   r   r   r}   r~   r   r   s           r-   r]   r]   H  r   r   c                 j   | sy| j                  d      D cg c]#  }|j                         s|j                         % }}d}t        |      D ]S  }t        j                  d|      }|s n9|j                  d      j                         j                         dk(  r|dz  }S n t        |d      S c c}w )zTCount consecutive Keith messages at the END of the conversation (unanswered streak).r   r{   r|   r   r}   r   )r'   r$   reversedr&   rO   r   r   max)r_   pr*   countr   s        r-   count_keith_tailr   \  s     'e 4B1	QWWYBEBEe_ HH/3771:##%0QJE ua= Cs
   B0B0last_msg_date_strc                    | j                         j                         }t        j                  |      }|r|j                  d      dv r|d   S t	        |      }t
        j                  |d      }t        j                  |      }|dk(  s|r|j                         |vry|}	|	dk  ry|}
t        |      }|r"|j                         |
j                         k\  ry|	dk\  ryt        j                         |
t        t              z   k  ry	y)
zoDetermine bucket: LEAD (default), EXCLUDED, PARTNER, IN_SKOOL, SM_TEACHERS, BOOKED_CALL, LONG_PAUSE, EXHAUSTED.bucketEXCLUDEDPARTNERIN_SKOOLSM_TEACHERSBOOKED_CALLr   LEADr   r   	EXHAUSTEDrl   
LONG_PAUSE)r   r$   USER_BUCKETSrC   r   _bump_count_last_bump_dater   r]   r   rk   
_timedelta
PAUSE_DAYS)r   r   r   r_   huserkeith_dates	raw_bumpslast_bump_rawbumps	last_bumplatest_others               r-   compute_bucketr   n  s    AAD"&ggH~ 'w/K1%I#''*MA~]m.@.@.B+.U EqyI &g.L))+y~~/??z||~	JJ$???rF   sc                     | j                  dd      j                  dd      j                  dd      j                  dd      S )	N&z&amp;<z&lt;>z&gt;"z&quot;)replace)r   s    r-   escr     s<    99S'"**37??VLTTUXZbccrF   nc                    | sy| j                  d      D cg c]#  }|j                         s|j                         % }}|| d }g }|D ]  }t        j                  d|t        j                        }|s|j                  dt        |       d       I|j                  d      |j                  d      j                         |j                  d	      j                         }}}|j                         d
k(  rdnd}	|j                         d
k(  rd
n
t        |      }
|j                  d|	 dt        |       d|
 dt        |       d	        dj                  |      S c c}w )zEParse "[DATE] sender: text | ..." and render last n messages as HTML.r	   r{   Nz\[([^\]]*)\]\s*([^:]+):\s*(.*)z<div class="convo-msg"></div>r   r   r   r}   zconvo-keithzconvo-otherz<div class="convo-msg z"><span class="convo-meta">u    · z </span><span class="convo-text">z</span></div>)
r'   r$   r&   rO   DOTALLappendr   r   r   join)r_   r   r   r*   r   r   r   r   rM   clssender_labels              r-   render_convo_tailr     sC    'e 4B1	QWWYBEB1"#JE
C 
HH6299EJJ0Q?@WWQZ)9)9);QWWQZ=M=M=Odf%||~8mm"(,,.G";wV

$SE *((+D	{$|n E((+D	{ 3	

 773<# Cs
   EE)
LEAD_REPLY	LEAD_BUMPr   r   r   r   ENGAGERSNEW_FOLLOWERSr   r   r   BUMP_1BUMP_2BUMP_3r?   r   r   z%Y-%m-%dr   r   r   r   r   r   rY   r   r   BUMP_u
   ↑ BUMP 1u   ↑↑ BUMP 2u   ↑↑↑ BUMP 3)r   r   r   z#e05800zhttps://www.instagram.com//z#https://www.instagram.com/direct/t/r@   rf   rW   z
they said:zpreview-otherzkeith said:zpreview-keith[|z#bf5af2z#3a3a3c)HOTWARMCOLDSUPPORTCOLLABSPAMz#555z<<div class="score-bar"><div class="score-fill" style="width:z%;background:F   (   z&"></div></div><span class="score-num">z</span>z-<span class="intent-badge" style="background:z	" title="z">r   z+<span class="flag green">reached out</span>z*<span class="flag gray">not reached</span>r   z+<span class="flag green">followed up</span>z<span class="flag note">z[^a-z]z
[\s,._\-]+z	[^A-Za-z]zHey z	Hey thereuV   , just bumping this up. I know how easy it is for messages to get buried. 🫶🙏🪷u   , just circling back, how are you doing? Did you have a chance to try out shaking yet? If you got any questions, feel free to message me anytime 🫶🙏🪷uf   , just circling back, how are you doing? Curious what drew you to follow Shaking Medicine 🫶🙏🪷u   , just circling back, how are you doing? Curious if anything from my content that I shared recently stood out to you 🫶🙏🪷u'   <div class="focus-hint">👁 focus on: r   ub   <details class="convo-expand"><summary>📜 show last 5 messages</summary><div class="convo-body">z</div></details>uU   <div class="pause-info">⏸ paused after 2 unanswered bumps. next bump available: <b>z
</b></div>u{   <div class="partner-info">🤝 partner / practitioner. write a custom collab message in IG, no auto-suggestions here.</div>uD   <div class="archive-info">🚫 excluded from regular outreach.</div>uE   <div class="archive-info">🗂 exhausted (3+ unanswered bumps).</div>u8   <div class="managed-info">📚 in Skool community.</div>u:   <div class="managed-info">🌿 SM teacher / trained.</div>u1   <div class="managed-info">📞 call booked.</div>z(<a class="sug-text sug-clickable" href="z@" target="_blank" onclick="return openDM(event,this)" data-sug="z" data-url="z%" title="click to copy + open IG DM">z6</a><button class="copy-btn" onclick="copyText(this, `z`)">copy</button>z.<div class="tpl-row"><a class="tpl-btn" href="uJ   " title="click to copy + open IG DM">↑ bump</a><a class="tpl-btn" href="uQ   " title="click to copy + open IG DM">↻ circle back</a><a class="tpl-btn" href="uI   " title="click to copy + open IG DM">🌿 SM</a><a class="tpl-btn" href="u;   " title="click to copy + open IG DM">📲 CONTENT</a></div>zG<div class="suggestion"><span class="sug-label">suggested reply:</span>z
      <div class="preview z$">
        <span class="prev-label">z</span> z
      </div>z
  <tr class="row row-z" data-status="z" data-tab-group="replyrZ   hiddenbumpz" data-bucket="z" data-next-bump="z" data-intent="z" data-score="z" data-bump-num="z" data-name="z" data-handle="u&  ">
    <td class="col-status">
      <button class="row-undo" onclick="return unsendDM(event,this)" title="undo sent">✕ undo</button>
      <div class="bucket-actions">
        <button class="bucket-btn b-exclude" onclick="return setBucket(event,this,'EXCLUDED')" title="exclude (ex/not interested)">🚫</button>
        <button class="bucket-btn b-partner" onclick="return setBucket(event,this,'PARTNER')" title="mark as partner/practitioner">🤝</button>
        <button class="bucket-btn b-inskool" onclick="return setBucket(event,this,'IN_SKOOL')" title="in Skool community">📚</button>
        <button class="bucket-btn b-smteachers" onclick="return setBucket(event,this,'SM_TEACHERS')" title="SM teacher / trained">🌿</button>
        <button class="bucket-btn b-bookedcall" onclick="return setBucket(event,this,'BOOKED_CALL')" title="call booked">📞</button>
        <button class="bucket-btn b-restore" onclick="return setBucket(event,this,'LEAD')" title="restore to leads">↩</button>
      </div>
      <span class="badge" style="background:z</span>
      z
      z6
    </td>
    <td class="col-person">
      <a href="z&" target="_blank" class="person-name">z</a>
      <a href="z(" target="_blank" class="person-handle">z</a>
      z	<a href="zP" target="_blank" class="dm-link" onclick="return openDM(event,this)" data-sug="uz   ">open DM →</a><button class="undo-btn" onclick="return unsendDM(event,this)" title="mark as not sent">↶ undo</button>ut   <button class="reclassify-btn" onclick="return moveToBump(event,this)" title="move to BUMP UP tab">→ bump</button>zB
    </td>
    <td class="col-date">
      <span class="date-rel">z%</span>
      <span class="date-abs">z1</span>
    </td>
    <td class="col-msg">
      z,
    </td>
    <td class="col-flags">
      z
    </td>
  </tr>
rw   r   r   r   post_id
descriptorzyour recent posturlactionengagedrr   u*   ! 🤗 Thanks so much for the love on our u:   . 💙🙏 I'm wondering, what resonated for you the most?	commentedcommentz "d   r   z
  <tr class="row row-engager" data-status="engager" data-tab-group="engagers" data-bucket="LEAD" data-next-bump="" data-intent="" data-score="0" data-name="u.  ">
    <td class="col-status">
      <button class="row-undo" onclick="return unsendDM(event,this)" title="undo sent">✕ undo</button>
      <div class="bucket-actions">
        <button class="bucket-btn b-exclude" onclick="return setBucket(event,this,'EXCLUDED')" title="exclude">🚫</button>
        <button class="bucket-btn b-partner" onclick="return setBucket(event,this,'PARTNER')" title="mark as partner">🤝</button>
        <button class="bucket-btn b-inskool" onclick="return setBucket(event,this,'IN_SKOOL')" title="in Skool">📚</button>
        <button class="bucket-btn b-smteachers" onclick="return setBucket(event,this,'SM_TEACHERS')" title="SM teacher">🌿</button>
        <button class="bucket-btn b-bookedcall" onclick="return setBucket(event,this,'BOOKED_CALL')" title="call booked">📞</button>
        <button class="bucket-btn b-restore" onclick="return setBucket(event,this,'LEAD')" title="restore">↩</button>
      </div>
      <span class="badge engager-badge">🎯 NEW LEAD</span>
    </td>
    <td class="col-person">
      <a href="u   open DM →u   open profile →zF</a>
    </td>
    <td class="col-date">
      <span class="date-rel">uP   </span>
    </td>
    <td class="col-msg">
      <div class="engager-info">🎯 z2" target="_blank" style="color:#8ad">your post</a>z	your postz</div>
      <div class="suggestion">
        <span class="sug-label">prefab message:</span>
        <a class="sug-text sug-clickable" href="z*" title="click to copy + open IG profile">z?</a>
        <button class="copy-btn" onclick="copyText(this, `zQ`)">copy</button>
      </div>
    </td>
    <td class="col-flags"></td>
  </tr>
r   followed_atuY   ! 🤗 Thanks so much for following along. Curious, what brought you to Shaking Medicine?ut   ! 🤗 Thanks so much for following! I suppose you found me through that Shaking Medicine reel we made with Floki. I'   z+m curious, what resonated the most for you?u   ! 🌿 So glad youz;re here. Curious what drew you to explore shaking medicine?z
  <tr class="row row-new-follower" data-status="new_follower" data-tab-group="newfollowers" data-bucket="LEAD" data-next-bump="" data-intent="" data-score="0" data-name="u7  ">
    <td class="col-status">
      <button class="row-undo" onclick="return unsendDM(event,this)" title="undo sent">✕ undo</button>
      <div class="bucket-actions">
        <button class="bucket-btn b-exclude" onclick="return setBucket(event,this,'EXCLUDED')" title="exclude">🚫</button>
        <button class="bucket-btn b-partner" onclick="return setBucket(event,this,'PARTNER')" title="mark as partner">🤝</button>
        <button class="bucket-btn b-inskool" onclick="return setBucket(event,this,'IN_SKOOL')" title="in Skool">📚</button>
        <button class="bucket-btn b-smteachers" onclick="return setBucket(event,this,'SM_TEACHERS')" title="SM teacher">🌿</button>
        <button class="bucket-btn b-bookedcall" onclick="return setBucket(event,this,'BOOKED_CALL')" title="call booked">📞</button>
        <button class="bucket-btn b-restore" onclick="return setBucket(event,this,'LEAD')" title="restore">↩</button>
      </div>
      <span class="badge new-follower-badge">👋 NEW FOLLOWER</span>
    </td>
    <td class="col-person">
      <a href="zs</a>
    </td>
    <td class="col-date">
      <span class="date-rel">followed</span>
      <span class="date-abs">u  </span>
    </td>
    <td class="col-msg">
      <div class="new-follower-info">👋 just followed @shakingmedicine</div>
      <div class="suggestion">
        <span class="sug-label">welcome (default):</span>
        <a class="sug-text sug-clickable" href="zS`)">copy</button>
        <div class="tpl-row">
          <a class="tpl-btn" href="uQ   " title="floki collab welcome">🎬 Floki</a>
          <a class="tpl-btn" href="uw   " title="casual welcome">🌿 casual</a>
        </div>
      </div>
    </td>
    <td class="col-flags"></td>
  </tr>
c           
   #   |   K   | ]4  }t         |   d kD  r&d| dt        |    dt        |    dt         |    d	 6 yw)r   z"<button class="pill" data-filter="z" style="--c:z" onclick="filterStatus(this)">z <b></b></button>N)countsSTATUS_COLORSTATUS_LABEL).0ks     r-   	<genexpr>r     sQ       	
VAY] )=a8IIhAtF1I;m5s   :<zX<button class="pill pill-all active" data-filter="" onclick="filterStatus(this)">ALL <b>r   u   <button class="pill" data-filter="sent" style="--c:#8e8e93" onclick="filterStatus(this)">✉ SENT <b id="sentCount">0</b></button>u   <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Keith — Network Audit r#   u/+  </title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0c0c0e;color:#e0e0e0;font-size:13px;min-height:100vh}

/* header */
.header{background:#111113;border-bottom:1px solid #222;padding:16px 20px 12px;position:sticky;top:0;z-index:100}
.header h1{font-size:16px;font-weight:600;color:#fff;margin-bottom:10px}
.pills{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
.pill{background:#1c1c1e;border:1px solid #333;border-radius:20px;color:#aaa;cursor:pointer;font-size:11px;padding:4px 12px;transition:all .15s}
.pill:hover,.pill.active{background:var(--c,#555);border-color:var(--c,#555);color:#fff}
.pill-all{--c:#555}
.pill-all.active{background:#333;border-color:#555}
.search-row{display:flex;gap:8px}
.search-row input{background:#1c1c1e;border:1px solid #2c2c2e;border-radius:8px;color:#e0e0e0;font-size:13px;outline:none;padding:7px 12px;width:280px}
.search-row input:focus{border-color:#555}
.count-label{color:#555;font-size:12px;align-self:center;margin-left:4px}

/* table */
table{width:100%;border-collapse:collapse}
th{background:#111113;color:#555;font-size:10px;font-weight:600;letter-spacing:.06em;padding:8px 12px;text-align:left;text-transform:uppercase;border-bottom:1px solid #1e1e20}
td{padding:10px 12px;border-bottom:1px solid #161618;vertical-align:top}
tr.row:hover td{background:#111115}
.row-hidden{display:none}
.row-sent{opacity:.4}
.row-sent td{background:#0a0a0c}
.row-sent:hover{opacity:.7}

/* cols */
.col-status{width:120px;white-space:nowrap}
.col-person{width:180px}
.col-date{width:90px;white-space:nowrap}
.col-msg{max-width:380px}
.col-flags{width:160px}

/* badge */
.badge{border-radius:5px;color:#fff;display:inline-block;font-size:10px;font-weight:700;letter-spacing:.03em;padding:3px 8px;white-space:nowrap}

/* person */
.person-name{color:#fff;display:block;font-size:13px;font-weight:600;text-decoration:none}
.person-name:hover{color:#5bf}
.person-handle{color:#555;display:block;font-size:11px;text-decoration:none;margin-top:1px}
.person-handle:hover{color:#9cf}
.dm-link{color:#f90;display:inline-block;font-size:10px;margin-top:4px;text-decoration:none;opacity:.75}
.dm-link:hover{opacity:1}
.reclassify-btn{background:none;border:1px solid #3a3a3c;border-radius:4px;color:#8e8e93;cursor:pointer;font-size:9px;margin-left:6px;padding:2px 6px;vertical-align:middle}
.reclassify-btn:hover{border-color:#ff9500;color:#ff9500}
.tab-btn.tab-all.active{background:#1a1a2e;border-color:#5e5ce6;color:#5e5ce6}
.intent-badge{border-radius:4px;color:#fff;display:inline-block;font-size:9px;font-weight:700;letter-spacing:.04em;margin-top:4px;padding:2px 6px;cursor:default}
.score-bar{background:#1c1c1e;border-radius:3px;height:3px;margin-top:6px;overflow:hidden;width:70px;display:inline-block;vertical-align:middle}
.score-fill{height:100%;border-radius:3px}
.score-num{color:#444;font-size:10px;margin-left:4px;vertical-align:middle}
.presets{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px}
.preset{background:#1c1c1e;border:1px solid #2c2c2e;border-radius:6px;color:#777;cursor:pointer;font-size:11px;padding:4px 10px;transition:all .15s}
.preset:hover{background:#2c2c2e;color:#ccc}
.preset.active{background:#2a2a10;border-color:#f90;color:#f90}
th.sortable{cursor:pointer;user-select:none}
th.sortable:hover{color:#aaa}

/* date */
.date-rel{color:#ccc;display:block;font-size:13px}
.date-abs{color:#444;display:block;font-size:10px;margin-top:2px}

/* message preview */
.preview{border-left:2px solid #333;color:#999;font-size:11px;line-height:1.5;margin-bottom:6px;padding:4px 8px}
.preview-other{border-color:#30d158;color:#9ef5b0}
.preview-keith{border-color:#0a84ff;color:#9ecfff}
.prev-label{color:#555;font-size:10px;font-style:italic}

/* suggestion */
.suggestion{background:#1a1a0e;border:1px solid #3a3a10;border-radius:6px;padding:7px 9px;margin-top:4px}
.sug-label{color:#888;display:block;font-size:10px;margin-bottom:3px;text-transform:uppercase;letter-spacing:.05em}
.sug-text{color:#ffd60a;font-size:12px;line-height:1.5}
.copy-btn{background:#2a2a10;border:1px solid #4a4a20;border-radius:4px;color:#aaa;cursor:pointer;float:right;font-size:10px;margin-top:-2px;padding:2px 8px}
.copy-btn:hover{background:#3a3a18;color:#fff}
.copy-btn.copied{background:#1a3a10;color:#5f5}
.row-undo{background:#3a1010;border:1px solid #6a1818;border-radius:4px;color:#ff8080;cursor:pointer;display:none;font-size:11px;font-weight:600;margin-bottom:6px;padding:4px 10px;transition:all .15s;width:fit-content}
.row-undo:hover{background:#5a1818;border-color:#8a2828;color:#ffb0b0}
tr.row[data-sent="1"] .row-undo{display:inline-block}
/* bucket action buttons */
.bucket-actions{display:flex;gap:3px;margin-top:4px;flex-wrap:wrap}
.bucket-btn{background:#1c1c1e;border:1px solid #2c2c2e;border-radius:4px;color:#777;cursor:pointer;font-size:12px;padding:2px 6px;transition:all .15s;line-height:1}
.bucket-btn:hover{background:#2c2c2e;color:#fff;border-color:#444}
.b-exclude:hover{border-color:#ff453a;color:#ff453a}
.b-partner:hover{border-color:#0a84ff;color:#0a84ff}
.b-inskool:hover{border-color:#bf5af2;color:#bf5af2}
.b-smteachers:hover{border-color:#30d158;color:#30d158}
.b-bookedcall:hover{border-color:#ff9500;color:#ff9500}
.b-restore:hover{border-color:#30d158;color:#30d158}
/* restore button only visible when bucket != LEAD */
.b-restore{display:none}
tr.row[data-bucket="EXCLUDED"] .b-restore,
tr.row[data-bucket="PARTNER"] .b-restore,
tr.row[data-bucket="IN_SKOOL"] .b-restore,
tr.row[data-bucket="SM_TEACHERS"] .b-restore,
tr.row[data-bucket="BOOKED_CALL"] .b-restore{display:inline-block}
/* hide active bucket button */
tr.row[data-bucket="EXCLUDED"] .b-exclude{display:none}
tr.row[data-bucket="PARTNER"] .b-partner{display:none}
tr.row[data-bucket="IN_SKOOL"] .b-inskool{display:none}
tr.row[data-bucket="SM_TEACHERS"] .b-smteachers{display:none}
tr.row[data-bucket="BOOKED_CALL"] .b-bookedcall{display:none}
/* managed bucket info */
.managed-info{color:#bf9af2;font-size:11px;margin-bottom:6px;padding:4px 8px;background:#1a0e2a;border-left:3px solid #bf5af2;border-radius:3px}
.engager-badge{background:#1a3a2a;color:#5fffa0}
.tab-btn.tab-engagers.active{background:#5fffa0;border-color:#5fffa0;color:#000}
body[data-tab="engagers"] tr.row[data-sent="1"]{display:none!important}
.engager-info{color:#9ef5b0;font-size:11px;margin-bottom:6px;padding:4px 8px;background:#0a1a10;border-left:3px solid #30d158;border-radius:3px}
.new-follower-badge{background:#3a2a4a;color:#d8b0ff}
.tab-btn.tab-newfollowers.active{background:#bf5af2;border-color:#bf5af2;color:#fff}
.new-follower-info{color:#d8b0ff;font-size:11px;margin-bottom:6px;padding:4px 8px;background:#1a0e2a;border-left:3px solid #bf5af2;border-radius:3px}
.row-new-follower .col-status .badge{background:#3a2a4a;color:#d8b0ff}
.row-engager .col-status .badge{background:#1a3a2a;color:#5fffa0}
/* hide focus_hint, convo, suggestion's normal AI in engagers (it's the prefab template) */
body[data-tab="engagers"] tr.row[data-tab-group="engagers"]{display:table-row}

tr.row[data-bucket="PARTNER"] .b-partner{display:none}
/* archive tab: red tint */
.tab-btn.tab-archive.active{background:#3a1010;border-color:#3a1010;color:#ff8080}
/* in PARTNERS tab, hide all the AI tooling — partners need custom outreach */
body[data-tab="partners"] .focus-hint,
body[data-tab="partners"] .convo-expand,
body[data-tab="partners"] .sug-clickable,
body[data-tab="partners"] .copy-btn,
body[data-tab="partners"] .tpl-row,
body[data-tab="paused"] .focus-hint,
body[data-tab="paused"] .convo-expand,
body[data-tab="paused"] .sug-clickable,
body[data-tab="paused"] .copy-btn,
body[data-tab="paused"] .tpl-row{display:none}
/* paused-info badge in PAUSED tab */
.pause-info{background:#1a1a0a;border-left:3px solid #f90;border-radius:3px;color:#ffa040;font-size:11px;padding:5px 8px;margin-top:4px}
.partner-info{background:#0a1a2a;border-left:3px solid #0a84ff;border-radius:3px;color:#7ab8ff;font-size:11px;padding:5px 8px;margin-top:4px}
.archive-info{background:#1a0a0a;border-left:3px solid #ff453a;border-radius:3px;color:#ff8080;font-size:11px;padding:5px 8px;margin-top:4px}
body[data-tab="paused"] .pause-info,
body[data-tab="partners"] .partner-info,
body[data-tab="archive"] .archive-info{display:block}
.pause-info,.partner-info,.archive-info{display:none}
/* clickable AI suggestion + template chips */
.sug-clickable{color:#ffd60a;cursor:pointer;display:inline;text-decoration:none}
.sug-clickable:hover{color:#fff5a0;text-decoration:underline}
.tpl-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tpl-btn{background:#2a2018;border:1px solid #4a3520;border-radius:4px;color:#ffa040;cursor:pointer;font-size:11px;padding:3px 8px;text-decoration:none;transition:all .15s}
.tpl-btn:hover{background:#3a2a1c;color:#fff;border-color:#6a4a30}
.focus-hint{background:#0e1a2a;border-left:3px solid #0a84ff;border-radius:3px;color:#7ab8ff;font-size:11px;line-height:1.45;margin-bottom:6px;padding:5px 8px}
.convo-expand{background:#111;border:1px solid #222;border-radius:4px;font-size:11px;margin-bottom:6px}
.convo-expand summary{color:#777;cursor:pointer;font-size:10px;padding:5px 8px;user-select:none}
.convo-expand summary:hover{color:#aaa}
.convo-expand[open] summary{border-bottom:1px solid #1a1a1a;color:#aaa}
.convo-body{padding:6px 8px;display:flex;flex-direction:column;gap:5px;max-height:280px;overflow-y:auto}
.convo-msg{display:flex;flex-direction:column;gap:1px;line-height:1.4}
.convo-meta{color:#444;font-size:9px;text-transform:lowercase}
.convo-text{color:#ddd}
.convo-keith .convo-text{color:#9ecfff}
.convo-keith .convo-meta{color:#0a84ff}
.convo-other .convo-text{color:#9ef5b0}
.convo-other .convo-meta{color:#30d158}
/* tab nav */
.tab-nav{display:flex;gap:8px;margin:14px 0 6px}
.tab-btn{background:#1a1a1c;border:1px solid #2a2a2c;border-radius:8px;color:#888;cursor:pointer;font-size:13px;font-weight:600;padding:8px 16px;transition:all .15s}
.tab-btn:hover{border-color:#444;color:#ccc}
.tab-btn.active{background:#ff9500;border-color:#ff9500;color:#000}
.tab-btn b{font-weight:700;margin-left:4px;opacity:.85}
/* hide rows not in active tab */
/* tab visibility now handled in applyFilters JS */

/* flags */
.flag{border-radius:4px;display:inline-block;font-size:10px;margin:1px 2px 1px 0;padding:2px 7px}
.green{background:#0a2a15;color:#30d158}
.gray{background:#1c1c1e;color:#555}
.note{background:#1c1c22;color:#888}
/* bump sub-nav */
.bump-sub-nav{display:none;align-items:center;gap:6px;padding:4px 0 0}
.bump-sub-btn{background:#1c1c1e;border:1px solid #2c2c2e;border-radius:6px;color:#777;cursor:pointer;font-size:11px;padding:3px 10px;transition:all .15s}
.bump-sub-btn:hover{background:#2c2c2e;color:#ccc}
.bump-sub-btn.active{background:#2a1a00;border-color:#ff9500;color:#ff9500}
.bump-sub-btn b{font-weight:700;margin-left:3px;opacity:.85}
</style>
</head>
<body>
<div class="header">
  <h1>Keith — Network Audit &nbsp;<span style="color:#555;font-weight:400;font-size:13px">zv</span></h1>
  <div class="tab-nav">
    <button class="tab-btn" data-tab="reply" onclick="switchTab('reply')" title="us    total NEEDS REPLY in system. Tab shows pending (not yet marked done this session).">💬 REPLY <b id="replyCount">uu   </b></button>
    <button class="tab-btn" data-tab="bump" onclick="switchTab('bump')">↑ BUMP UPS <b id="bumpCount">u   </b></button>
    <button class="tab-btn" data-tab="partners" onclick="switchTab('partners')">🤝 PARTNERS <b id="partnerCount">uy   </b></button>
    <button class="tab-btn" data-tab="paused" onclick="switchTab('paused')">⏸ PAUSED <b id="pausedCount">u   </b></button>
    <button class="tab-btn tab-newfollowers" data-tab="newfollowers" onclick="switchTab('newfollowers')">👋 NEW FOLLOWERS <b id="newfollowersCount">u   </b></button>
    <button class="tab-btn tab-engagers" data-tab="engagers" onclick="switchTab('engagers')">🎯 ENGAGERS <b id="engagersCount">u   </b></button>
    <button class="tab-btn tab-inskool" data-tab="inskool" onclick="switchTab('inskool')">📚 IN SKOOL <b id="inskoolCount">u   </b></button>
    <button class="tab-btn tab-smteachers" data-tab="smteachers" onclick="switchTab('smteachers')">🌿 SM TEACHERS <b id="smteachersCount">u   </b></button>
    <button class="tab-btn tab-bookedcall" data-tab="bookedcall" onclick="switchTab('bookedcall')">📞 BOOKED CALL <b id="bookedcallCount">u   </b></button>
    <button class="tab-btn tab-archive" data-tab="archive" onclick="switchTab('archive')">🗂 ARCHIVE <b id="archiveCount">uf  </b></button>
    <button class="tab-btn tab-all" data-tab="all" onclick="switchTab('all')">📋 ALL <b id="allCount">0</b></button>
  </div>
  <div class="bump-sub-nav" id="bumpSubNav">
    <span style="color:#555;font-size:11px">bump stage:</span>
    <button class="bump-sub-btn active" data-bnum="0" onclick="setBumpNum(this,0)">all <b id="bumpAllCount">zv</b></button>
    <button class="bump-sub-btn" data-bnum="1" onclick="setBumpNum(this,1)">1st bump <b id="bump1Count">r   zv</b></button>
    <button class="bump-sub-btn" data-bnum="2" onclick="setBumpNum(this,2)">2nd bump <b id="bump2Count">r   zw</b></button>
    <button class="bump-sub-btn" data-bnum="3" onclick="setBumpNum(this,3)">3rd+ bump <b id="bump3Count">r   z,</b></button>
  </div>
  <div class="pills">u   </div>
  <div class="search-row">
    <input type="text" id="search" placeholder="Search name or @handle…" oninput="applyFilters()">
    <span class="count-label" id="countLabel">u   shown</span>
  </div>
  <div class="presets">
    <span style="color:#444;font-size:11px;align-self:center">quick filters:</span>
    <button class="preset" onclick="applyPreset(this,'hot')" data-preset="hot">🔥 HOT leads</button>
    <button class="preset" onclick="applyPreset(this,'warm')" data-preset="warm">🌤 WARM</button>
    <button class="preset" onclick="applyPreset(this,'needs_reply')" data-preset="needs_reply">⚡ needs reply</button>
    <button class="preset" onclick="applyPreset(this,'score70')" data-preset="score70">score 70+</button>
    <button class="preset" onclick="applyPreset(this,'fresh1d')" data-preset="fresh1d">&lt; 1d</button>
    <button class="preset" onclick="applyPreset(this,'fresh1w')" data-preset="fresh1w">&lt; 1w</button>
    <button class="preset" onclick="applyPreset(this,'fresh2w')" data-preset="fresh2w">&lt; 2w</button>
    <button class="preset" onclick="applyPreset(this,'fresh1m')" data-preset="fresh1m">&lt; 1m</button>
    <button class="preset" onclick="applyPreset(this,'recent3d')" data-preset="recent3d">last 3 days</button>
    <button class="preset" onclick="applyPreset(this,'older1w')" data-preset="older1w">&gt; 1w old</button>
    <button class="preset" onclick="applyPreset(this,'older2w')" data-preset="older2w">&gt; 2w old</button>
    <button class="preset" onclick="applyPreset(this,'older1m')" data-preset="older1m">&gt; 1m old</button>
    <button class="preset" onclick="applyPreset(this,'')" data-preset="">clear</button>
  </div>
  <div id="tooRecentBanner" style="display:none;color:#ff9500;font-size:12px;padding:4px 0 0 2px"></div>
</div>

<table>
<thead>
  <tr>
    <th>Status</th>
    <th>Person</th>
    <th class="sortable" onclick="sortByDate()">Last Contact <span id="dateSortIcon">↓</span></th>
    <th>Last Message + Suggested Reply</th>
    <th class="sortable" onclick="sortByFlag()">Flags <span id="flagSortIcon"></span></th>
  </tr>
</thead>
<tbody id="tbody">
z:
</tbody>
</table>

<script>
const SENT_HANDLES = new Set(uA  );
let activeFilter = "";
let activePreset = "";
let activeBumpNum = 0;
let sortDir = "desc";
let originalOrder = null;

function setBumpNum(btn, num) {
  activeBumpNum = num;
  document.querySelectorAll('.bump-sub-btn').forEach(b => b.classList.toggle('active', parseInt(b.dataset.bnum) === num));
  applyFilters();
}

function rowDate(tr) {
  if (tr._cachedDate !== undefined) return tr._cachedDate;
  const txt = (tr.querySelector('.date-abs') || {}).textContent || '';
  const d = new Date(txt);
  tr._cachedDate = isNaN(d) ? null : d.getTime();
  return tr._cachedDate;
}

function setBucket(e, btn, bucket) {
  e.preventDefault();
  const row = btn.closest("tr");
  if (!row) return false;
  const handle = row.dataset.handle;
  const prev = row.dataset.bucket || "LEAD";
  row.dataset.bucket = bucket;
  applyFilters();
  updateTabCounts();
  fetch("/bucket", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({ handle, bucket })
  }).catch(() => { /* best-effort */ });
  return false;
}

function updateTabCounts() {
  const c = {reply: 0, bump: 0, partners: 0, paused: 0, archive: 0, engagers: 0, newfollowers: 0, inskool: 0, smteachers: 0, bookedcall: 0, all: 0, b1: 0, b2: 0, b3: 0};
  document.querySelectorAll('#tbody tr.row').forEach(tr => {
    const bucket = tr.dataset.bucket || 'LEAD';
    const tg = tr.dataset.tabGroup;
    const isSent = tr.dataset.sent === '1';
    if (tg === 'reply' && bucket === 'LEAD' && !isSent) c.reply++;
    else if (tg === 'bump' && bucket === 'LEAD' && !isSent) {
      c.bump++;
      const n = parseInt(tr.dataset.bumpNum || 1);
      if (n === 1) c.b1++; else if (n === 2) c.b2++; else c.b3++;
    }
    else if (bucket === 'PARTNER') c.partners++;
    else if (bucket === 'LONG_PAUSE') c.paused++;
    else if (bucket === 'EXCLUDED' || bucket === 'EXHAUSTED') c.archive++;
    else if (bucket === 'IN_SKOOL') c.inskool++;
    else if (bucket === 'SM_TEACHERS') c.smteachers++;
    else if (bucket === 'BOOKED_CALL') c.bookedcall++;
    if (tg === 'engagers' && bucket === 'LEAD') c.engagers++;
    if (tg === 'newfollowers' && bucket === 'LEAD') c.newfollowers++;
    c.all++;
  });
  ['reply','bump','partners','paused','archive','engagers','newfollowers','inskool','smteachers','bookedcall','all'].forEach(t => {
    const el = document.getElementById(t + 'Count');
    if (el) el.textContent = c[t];
  });
  const bump1El = document.getElementById('bump1Count'); if (bump1El) bump1El.textContent = c.b1;
  const bump2El = document.getElementById('bump2Count'); if (bump2El) bump2El.textContent = c.b2;
  const bump3El = document.getElementById('bump3Count'); if (bump3El) bump3El.textContent = c.b3;
  const bumpAllEl = document.getElementById('bumpAllCount'); if (bumpAllEl) bumpAllEl.textContent = c.bump;
}

function moveToBump(e, btn) {
  e.preventDefault();
  const row = btn.closest('tr');
  if (!row) return false;
  const handle = row.dataset.handle;
  row.dataset.tabGroup = 'bump';
  btn.remove();
  updateTabCounts();
  applyFilters();
  fetch('/reclassify', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ handle, follow_up_type: 'BUMP_UP' })
  }).catch(() => {});
  return false;
}

function unsendDM(e, btn) {
  e.preventDefault();
  const row = btn.closest("tr");
  if (!row) return false;
  const link = row.querySelector(".dm-link");
  const handle = row.dataset.handle;
  // revert UI state
  delete row.dataset.sent;
  row.classList.remove("row-sent");
  if (link) { link.textContent = "open DM →"; link.style.color = ""; }
  const sc = document.getElementById("sentCount");
  if (sc) sc.textContent = Math.max(0, parseInt(sc.textContent || 0) - 1);
  applyFilters();
  updateTabCounts();
  // tell server (best-effort; if it fails, UI revert still stands)
  fetch("/untrack", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({ handle })
  }).catch(() => {});
  return false;
}

function sortByDate() {
  const tbody = document.getElementById('tbody');
  sortDir = sortDir === 'asc' ? 'desc' : 'asc';
  const rows = [...tbody.querySelectorAll('tr.row')].sort((a, b) => {
    const da = rowDate(a); const db = rowDate(b);
    const av = da === null ? -Infinity : da;
    const bv = db === null ? -Infinity : db;
    return sortDir === 'asc' ? av - bv : bv - av;
  });
  rows.forEach(r => tbody.appendChild(r));
  const icon = document.getElementById('dateSortIcon');
  if (icon) icon.textContent = sortDir === 'asc' ? ' ↑' : ' ↓';
}

let flagSortDir = 'desc';
function sortByFlag() {
  const tbody = document.getElementById('tbody');
  flagSortDir = flagSortDir === 'asc' ? 'desc' : 'asc';
  const rows = [...tbody.querySelectorAll('tr.row')].sort((a, b) => {
    const ar = a.dataset.sent === '1' ? 1 : 0;
    const br = b.dataset.sent === '1' ? 1 : 0;
    return flagSortDir === 'asc' ? ar - br : br - ar;
  });
  rows.forEach(r => tbody.appendChild(r));
  const icon = document.getElementById('flagSortIcon');
  if (icon) icon.textContent = flagSortDir === 'asc' ? ' ↑' : ' ↓';
}

function filterStatus(btn) {
  document.querySelectorAll('.pill').forEach(p => p.classList.remove('active'));
  btn.classList.add('active');
  activeFilter = btn.dataset.filter;
  applyFilters();
}

function applyPreset(btn, preset) {
  document.querySelectorAll('.preset').forEach(p => p.classList.remove('active'));
  btn.classList.add('active');
  activePreset = preset;
  applyFilters();
}

function applyFilters() {
  const q = document.getElementById('search').value.toLowerCase();
  const now = Date.now();
  let shown = 0;
  const wantSent = activeFilter === 'sent';
  const currentTab = document.body.dataset.tab || 'reply';
  document.querySelectorAll('#tbody tr.row').forEach(tr => {
    const bucket = tr.dataset.bucket || 'LEAD';
    const tg = tr.dataset.tabGroup;
    let matchTab = false;
    if (currentTab === 'reply'   && tg === 'reply' && bucket === 'LEAD') matchTab = true;
    else if (currentTab === 'bump'    && tg === 'bump'  && bucket === 'LEAD') matchTab = true;
    else if (currentTab === 'partners' && bucket === 'PARTNER') matchTab = true;
    else if (currentTab === 'paused'   && bucket === 'LONG_PAUSE') matchTab = true;
    else if (currentTab === 'engagers' && tg === 'engagers' && bucket === 'LEAD') matchTab = true;
    else if (currentTab === 'newfollowers' && tg === 'newfollowers' && bucket === 'LEAD') matchTab = true;
    else if (currentTab === 'inskool'   && bucket === 'IN_SKOOL') matchTab = true;
    else if (currentTab === 'smteachers' && bucket === 'SM_TEACHERS') matchTab = true;
    else if (currentTab === 'bookedcall' && bucket === 'BOOKED_CALL') matchTab = true;
    else if (currentTab === 'archive'  && (bucket === 'EXCLUDED' || bucket === 'EXHAUSTED')) matchTab = true;
    else if (currentTab === 'all') matchTab = true;
    if (!matchTab) { tr.classList.add('row-hidden'); return; }
    const isSent = tr.dataset.sent === '1';
    const ignoreSent = (currentTab === 'paused' || currentTab === 'archive' || currentTab === 'partners' || currentTab === 'newfollowers' || currentTab === 'reply' || currentTab === 'inskool' || currentTab === 'smteachers' || currentTab === 'bookedcall' || currentTab === 'all');
    const matchSent = ignoreSent || (wantSent ? isSent : !isSent);
    // BUMP tab: hide contacts where last message < 3 weeks ago
    if (currentTab === 'bump' && tg === 'bump') {
      const d = rowDate(tr);
      if (d !== null && (now - d) < 21*86400000) { tr.classList.add('row-hidden'); return; }
    }
    // BUMP sub-filter: 1st / 2nd / 3rd+
    if (currentTab === 'bump' && activeBumpNum > 0) {
      if (parseInt(tr.dataset.bumpNum || 1) !== activeBumpNum) { tr.classList.add('row-hidden'); return; }
    }
    const matchStatus = wantSent || !activeFilter || tr.dataset.status === activeFilter;
    const matchQ = !q || tr.dataset.name.includes(q) || tr.dataset.handle.includes(q);
    let matchPreset = true;
    if (activePreset === 'hot')         matchPreset = tr.dataset.intent === 'hot';
    else if (activePreset === 'warm')   matchPreset = tr.dataset.intent === 'warm';
    else if (activePreset === 'needs_reply') matchPreset = tr.dataset.status === 'needs_reply';
    else if (activePreset === 'score70') matchPreset = parseInt(tr.dataset.score||0) >= 70;
    else if (activePreset === 'recent3d') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) < 3*86400000;
    }
    else if (activePreset === 'older1w') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) > 7*86400000;
    }
    else if (activePreset === 'older2w') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) > 14*86400000;
    }
    else if (activePreset === 'older1m') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) > 30*86400000;
    }
    else if (activePreset === 'fresh1d') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) < 1*86400000;
    }
    else if (activePreset === 'fresh1w') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) < 7*86400000;
    }
    else if (activePreset === 'fresh2w') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) < 14*86400000;
    }
    else if (activePreset === 'fresh1m') {
      const d = rowDate(tr);
      matchPreset = d !== null && (now - d) < 30*86400000;
    }
    const show = matchSent && matchStatus && matchQ && matchPreset;
    tr.classList.toggle('row-hidden', !show);
    if (show) shown++;
  });
  document.getElementById('countLabel').textContent = shown + ' shown';
}

async function loadSentState() {
  let sentSet = SENT_HANDLES;
  try {
    const r = await fetch('/log.json', {cache:'no-store'});
    if (r.ok) sentSet = new Set(await r.json());
  } catch(e) {}
  let count = 0;
  document.querySelectorAll('#tbody tr.row').forEach(tr => {
    if (sentSet.has(tr.dataset.handle)) {
      tr.dataset.sent = '1';
      tr.classList.add('row-sent');
      count++;
    }
  });
  const sc = document.getElementById('sentCount');
  if (sc) sc.textContent = count;
  applyFilters();
  updateTabCounts();
}
window.addEventListener('DOMContentLoaded', loadSentState);

function updateQueueBadge() {
  const q = JSON.parse(localStorage.getItem('trackQueue') || '[]');
  let badge = document.getElementById('queueBadge');
  if (!badge) {
    badge = document.createElement('span');
    badge.id = 'queueBadge';
    badge.style.cssText = 'margin-left:8px;background:#ff9500;color:#000;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;cursor:pointer';
    badge.title = 'queued tracks waiting to send — click to retry now';
    badge.onclick = retryQueue;
    document.querySelector('.count-label').after(badge);
  }
  if (q.length === 0) { badge.style.display = 'none'; return; }
  badge.style.display = '';
  badge.textContent = '⏳ ' + q.length + ' queued';
}

async function retryQueue() {
  let q = JSON.parse(localStorage.getItem('trackQueue') || '[]');
  if (!q.length) return;
  const remaining = [];
  for (const item of q) {
    try {
      const r = await fetch('/track', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(item)
      });
      if (!r.ok) remaining.push(item);
    } catch(e) { remaining.push(item); }
  }
  localStorage.setItem('trackQueue', JSON.stringify(remaining));
  updateQueueBadge();
  if (remaining.length === 0) loadSentState();
}
window.addEventListener('DOMContentLoaded', () => { updateQueueBadge(); retryQueue(); });

function copyText(btn, text) {
  navigator.clipboard.writeText(text).then(() => {
    btn.textContent = 'copied!';
    btn.classList.add('copied');
    setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
  });
}

function openDM(e, link) {
  e.preventDefault();
  const sug  = link.dataset.sug;
  const url  = link.dataset.url;
  const row  = link.closest('tr');
  const handle = row ? row.dataset.handle : '';
  const name   = row ? row.dataset.name   : '';
  const type   = row ? row.dataset.status : '';

  function doOpen() { window.open(url, '_blank'); }

  function trackAndOpen() {
    if (row) {
      row.dataset.sent = '1';
      row.classList.add('row-sent');
      link.textContent = '✓ done';
      link.style.color = '#30d158';
      const sc = document.getElementById('sentCount');
      if (sc) sc.textContent = (parseInt(sc.textContent||0) + 1);
      if (activeFilter !== 'sent') applyFilters();
    }
    const payload = { handle, name, type, suggestion: sug, dm_url: url };
    fetch('/track', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(payload)
    }).then(r => { if (!r.ok) throw new Error('rc='+r.status); }).catch(() => {
      const q = JSON.parse(localStorage.getItem('trackQueue') || '[]');
      q.push({...payload, ts_client: new Date().toISOString().slice(0,16).replace('T',' ')});
      localStorage.setItem('trackQueue', JSON.stringify(q));
      link.textContent = '✓ queued';
      link.style.color = '#ff9500';
      updateQueueBadge();
    });
    doOpen();
  }

  if (!sug) { trackAndOpen(); return false; }

  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(sug).then(() => {
      link.textContent = '✓ copied — opening...';
      setTimeout(trackAndOpen, 400);
    }).catch(() => { fallbackCopy(sug); trackAndOpen(); });
  } else {
    fallbackCopy(sug);
    trackAndOpen();
  }
  return false;
}

function fallbackCopy(text) {
  const ta = document.createElement('textarea');
  ta.value = text;
  ta.style.position = 'fixed';
  ta.style.opacity = '0';
  document.body.appendChild(ta);
  ta.focus();
  ta.select();
  try { document.execCommand('copy'); } catch(e) {}
  document.body.removeChild(ta);
}

function switchTab(tab) {
  document.body.dataset.tab = tab;
  document.querySelectorAll(".tab-btn").forEach(b => b.classList.toggle("active", b.dataset.tab === tab));
  const tbody = document.getElementById("tbody");
  const rows = [...tbody.querySelectorAll("tr.row")];
  if (tab === "bump" || tab === "paused" || tab === "archive") {
    rows.sort((a, b) => (parseInt(b.dataset.score)||0) - (parseInt(a.dataset.score)||0));
  } else {
    rows.sort((a, b) => {
      const da = rowDate(a); const db = rowDate(b);
      return (db === null ? -Infinity : db) - (da === null ? -Infinity : da);
    });
  }
  rows.forEach(r => tbody.appendChild(r));
  // auto-default preset per tab: bump → older2w, others → none
  activePreset = (tab === 'bump') ? 'older2w' : '';
  document.querySelectorAll('.preset').forEach(p => p.classList.toggle('active', p.dataset.preset === activePreset));
  // bump sub-nav: show only on bump tab, reset filter
  const bumpSubNav = document.getElementById('bumpSubNav');
  if (bumpSubNav) bumpSubNav.style.display = (tab === 'bump') ? 'flex' : 'none';
  if (tab !== 'bump') {
    activeBumpNum = 0;
    document.querySelectorAll('.bump-sub-btn').forEach(b => b.classList.toggle('active', b.dataset.bnum === '0'));
  }
  // BUMP tab: show "too recent" counter (rows where last contact < 3 weeks)
  const tooRecentBanner = document.getElementById('tooRecentBanner');
  if (tab === 'bump' && tooRecentBanner) {
    const now2 = Date.now();
    let tooRecent = 0;
    document.querySelectorAll('#tbody tr.row[data-tab-group="bump"]').forEach(tr => {
      const d = rowDate(tr);
      if (d !== null && (now2 - d) < 21*86400000) tooRecent++;
    });
    tooRecentBanner.textContent = tooRecent > 0 ? tooRecent + ' contacts hidden (last message < 3 weeks ago)' : '';
    tooRecentBanner.style.display = tooRecent > 0 ? 'block' : 'none';
  } else if (tooRecentBanner) {
    tooRecentBanner.style.display = 'none';
  }
  applyFilters();
  updateTabCounts();
  history.replaceState(null, "", "#" + tab);
}
// init: hash overrides default; default = reply
document.addEventListener("DOMContentLoaded", () => {
  const valid = ["reply", "bump", "partners", "paused", "archive", "engagers", "newfollowers"];
  const initial = (window.location.hash || "#reply").slice(1);
  switchTab(valid.includes(initial) ? initial : "reply");
});

function copyText(btn, text) {
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(text).then(() => {
      btn.textContent = 'copied!'; btn.classList.add('copied');
      setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
    }).catch(() => { fallbackCopy(text); btn.textContent = 'copied!'; setTimeout(() => btn.textContent = 'copy', 2000); });
  } else {
    fallbackCopy(text);
    btn.textContent = 'copied!';
    setTimeout(() => btn.textContent = 'copy', 2000);
  }
}
</script>
</body>
</html>
z	written: zELibrary/Mobile Documents/com~apple~CloudDocs/keith_network_audit.htmlzsynced to iCloud: ztotal: z entriesz  z: )r	   )r>   )__doc__csvjsonr&   r   pathlibr   	SHEET_CSV__file__parentDM_CSVOUTPUT_HTMLACTIVITY_LOG_sent_handlesexistsloads	read_text_log
isinstancedictlistr)   dumpsSENT_HANDLES_JS
sheet_rowsopenfreaderr^   rR   r$   r   r   upperr   r   r   r   
startswithr   	SKIP_FILEr   skip_handles
splitlinesliner   r   dm_by_handle
DictReaderrC   strr.   r2   EXCLUDE_HANDLES_seen_handles_r_h_name_scorevaluesmerged_seen_mergedrE   r?   rA   	note_dater   r@   itemsstubr   sortcompileUNICODE_REACTION_ONLY_REboolrU   rc   r   r   rn   _json_buckets_pathr   collectionsro   rp   r   	_log_pathr   r   _entriesrt   sorted_entries_sorted
_prev_bumprs   _tsr%   total_secondsENGAGERS_DATA_engagers_pathENGAGERS_POSTS_BY_IDNEW_FOLLOWERS_DATA_nf_pathr   r\   r]   rD   r   r   r   r   r   r   bucket_counts	rows_htmlstcolorlabel_row_bucket_next_bump_iso_lbstrftime	_bump_num_BUMP_LABELS_BUMP_COLORSlstrip
handle_rawig_urldm_urldate_displaydate_rawpreview_labelpreview_textpreview_cls
suggestionscore
intent_rawr'   intent_labelintent_signalINTENT_COLORSintent_color	score_barintent_badgeflag_reachedflag_followed	flag_note_handle_cleanrQ   _handle_alphas_candidates_first_src_tok_clean_greett_bumpt_circlet_sm	t_content_focus_focus_block_convo_block_bucket_info_ai_link
_tpl_blocksuggestion_htmlpreview_htmlr   _NEW_FOLLOWER_HANDLES_engh_lcpostr   post_urlr   when_eng_tid	_src_name_tokens_first_e_greet_etemplatecomment_blurb_nf_nf_tid_first_n_greet_nwelcomechrwelcome_flokiwelcome_casualsummary_pillssent_handles_jsuser_buckets_jsrk   html
write_textprintshutilhomeicloudcopy2r   v)er  r   r   r  s   00000r-   <module>r     s&   ? 
  	  ,-	H~$$';;H~$$';;H~$$':: tzz,0023JtAw- t!M!x1}!H+!MNM M $**]+ 
	)g& !SZZ]F s8a<Q#&s8a<QR<?HqLs1v||~++-7e<?HqLs1v||~++-7e(+C1c!flln"::<<<.@X  %Dlln&&
 	!6 N!!O3	u##%002  JJL Q  	&7# 
qS^^AF 
M!'')#&77>2#>#&77<#<#&'7#8#&#7#&}#5#&{#3JMcRbNcJdgjJj3'7#8##>#Fps  uE  qFJMcRbNcJdgjJj3'7#8##>#Fps  uE  qF#&'=#>#&77+<b#A#&77<#=#&778R#8
Q	

,# ( S X 
 
 )B	H				#	#	%BVVFB%2,,.E5LbE%:J:J3:OqUV[^_d[eeF	&=+<Q+?"?Lb) *0023qad3
	u	 A	(!!#AO  8-Bb12G&	*IGY'I
MMMQMbyWMNQ ! PEArLA0VVN#)r0027aEQuU\^_DB/0G
MMNTN'gNOPG ($ 7 BJJNPRPZPZ[ 
-C 
-D 
-. '"&! 
 
c 
 X%%6"u{{=#:#:#<=
   ,N!!$77	i5;;y2245	2 !y9
! 	*B"%++-335Bxvvf~*HB-Chll"HZS:b>%9$H$H$JS$P JrNOq O(C/"2E,E&)#	*" "-h&&8#N$<$<$>? -:,=,=gr,JKq$
K !2& >  #77??(U[[););)=># $ ( (c c   
3 3 S SV `c <d3 d3 ds s 3 2 %	%1!Q$	% qQa]^mn|}  QR  `a  rs  DE  QR  ^_  kl  m		 CA1IBEEdGB 8Rbff_b.IUWvxY[Y_Y_`moqYr  A  ik  CE  CI  CI  J`  bd  Ce  qs  tKNl"!!!H+"3"3"5";";"=>!JJ$??II*UNI
2J!OJii A% 		$l#q(#	
	"j!Q&!		#k"a'"	
	"j!Q&!		%m$)$		%m$)$	}	l#q(#	y$2RVV,BB%GSUV	Iq)1-	k"a'"i[)*a/*	 
Y9q='O@RS$yAY'Y'8##C(J*:,a8FIK3B{O3DEQWFAkN+L+-B'1V9FH 
=0(M#3 45L+K)M#3 45L+K J	bff&'3D0E0P0PQT0U-./
 &(BFF<,@C< !aE)+"%J7:j7H:##C(+113jL8;z8IJ$$S)!,224rM%y)Xamv  AJ  KM $$\6:LNugUbpuwypycl  MR  TV  MV  @I  \b  cc  cI  JO  IP  PW  XI JVB<.PYZ]^kZlYmmop|o}  ~E  F  \^L EFmDT@  [GLEF}EUA[]MJKF).s1V9~.>gFY[I hK&&s+113MRVVIr=9N K	bff^$2n-.uuV}1V9%F BHH]DJJL9 	DRVVL"d3F6{aFLLNn$D*VABZ-=-=-??		
  !'tF8_KF  B  CF  I  JH(  S  TD(  n  oI)+RVVL"%F"0@)A])R >c&k]&QXZ  bff%56-GBFFSiLj y  zK  LN  Oe  Lf  hi  zj  yk  k{  |pr l"no}n~  I  J			! U	
	"]		#^	
	"Q		%S		%J
 	 ;6( CAADRHYEZA[@\ ]#H$I* VEEOLPac $&	 ..4X 6"6{m<x @..4X 6"8}o\& B..4X 6"4ykfX >..4X 6"9~.l6( C	J &(I&~l^H:j\! .0   &- (""/ G &( 
  $ $  $T$($)+$,>$KMQ^K^prv|p|dl  CI  ?K$ KZ$ [f  Zg$ gy$ zH  yI$ IX$ Ye  Yk  Yk  Ym  Xn$ n|$ }B  |C$ CT$ U^  T_$ _l$ mp  qr  sy  qz  q@  q@  qB  mC  lD$ DS$ TW  XY  Zb  Xc  Td  Se$e-$ .3G$ 46$ 7<W$=$ n$$ k$$" x#$" >#$" ?B!F)n=M#$"N#$$ x%$$ @%$$ ADAhK@P?Q%$$R%$& GI{Vpp  SU  Z\  Z`  Z`  ar  Zs  |~  P  |Q  |\  |\  ]`  |atwxz  |M  yN  uO  gi  j  m{  {  ~D  D  GC  C  OQ  R'$&R'$( DF  KM  KQ  KQ  Rb  Kc  gt  Kt~  z|  })$(})$. +^/$.,/$0 085/@hsmeL1$0M1$6 n7$67$8 9$89$> n?$>?$@ oA$@A$B kC$BC$ $ICP GYF\F\]hjlFmnx,224::<n j"- DDXXh#F88FFMM#./D8<<>!D$$hb!%%h/3tt*"##DHHY$;R@D,(:;Jxxr"HXXh	*F88D"Ds#J)*Q7FxxR(HAI28*=vF

Ibhh}i0GH b$/v;!*2B2B2D Day(6!":+;+;+==H	
 %-hZ +HEj\  RL  MHM)!4Si#!678: ! ! ] ! ^a  bf  bl  bl  bn  ^o  ]p! p! @C  DJ  @K  L!L! x! >! ?B$i[!I!  x!!  @!!  ADF}!! M!!" x#!" h#!" illthugv#!" wC#!" DJ  CK#!" KM#!" _g  N[  m  M@#!"@#!( %X)!(&)!* "$s)n-+!*.&+!0 '*&k]1!0 341!0 AIizI{4|  OZ  4[1!0 \i  [j1!0j11!6 287!6 9y7!6 z}  ~F  zG  yH7!6 HT7!6 U[  T\7!6 \F7!6 GJ  KS  GT  FU7!6U;7!8 <D*9!8E9! !IGDP !!+r2 FCWWXr"F7766==-.D8<<>!Db!%%h/3tt/"a'"s#J)*Q7Fggk2&G@G27)<VF77="%D

Ibhh}i0GH b$/v;!*2B2B2D Day(6!":+;+;+==H	
 %-hZ +H
stG*  	"g	E	F 
 *&'#b'1 5= 	=   % % k % lo  pt  pz  pz  p|  l}  k~% ~M% NQ  RX  NY  MZ%Z% x% >% ?B$i[%I%  x!%  @!%  ADF}!% M!%" x#%" h#%" illshtgu#%" vB#%" CI  BJ#%" JL#%" ^e  MZ  k}  L~#%"~#%* "$s)n-+%*.1+%6 287%6 9y7%6 z}  ~E  zF  yG7%6 GS7%6 TZ  S[7%6 [@7%6 AD  EL  AM  @N7%6N;7%8 <C)9%8D$9%< %+8=%< ,l=%< mpp}l~k=%< @L=%< MS  LT=%<T$=%> %+8?%> ,l?%> mpp~l  lA?%> AM?%> NT  MU?%>U?% %ICFR    
 knqruv|r}n~~  BQ  Q  Q   V  V "%++l+_
 _
   _

 !- 7 7
 CD_

uE]_
t ^j]e]i]i]k]t]tu  ^A  ]Bu_
tBRu_
x S``lRmQny_
x oby_
x cp  q}  c~  by_
xhy_
z iv  wB  iC  hD{_
zDt{_
| uB  CL  uM  tN}_
|Nl}_
~ mz  {G  mH  lI_
~IW_
@ Xe  fu  Xv  WwA_
@wBA_
B CP  Q[  C\  B]C_
B]~C_
D L  MW  X  ~YE_
DYME_
F N[  \i  Nj  MkG_
FkMG_
H N[  \i  Nj  MkI_
Hk}I_
J ~K  LV  ~W  Zg  hs  Zt  ~t  }uK_
JumK_
T n{  |G  nH  mIU_
TIiU_
V jww  jA  iBW_
VBiW_
X jww  jA  iBY_
XBjY_
Z kx  yA  kB  jC[_
ZC[_
^ $___
^%/__
d 036{me_
d <e_
d e_
de_
n ..o_
nh/o_
B   tg  . 	+   	^	^ [& ! 6;;-( ) F}H% &LLN +DAq<?#2aS)*+O- "N   J
 
B 4P  >    K  V 
&X os   54A\) )	A\$3A\$:
A\) "D0A\5(C)A]A]' A] CA]" A]"  A]. 8A]:2 A]? 
A^<5A^\$A\) \)A\2\1A\2\5A\?]A]]A]]A]]"A]+]*A]+].A]7]6A]7]?A^^A^