o
    Ð…¢j—K  ã                
   @   sN  d dl Z d dlZd dlZd dlZd dlmZ ej d d¡ d dlmZ e	dƒ d dl
mZmZmZmZ dd„ Zdd	d
„Zdd„ Zddd„Zdd„ Zdd„ Zdd„ Zdd„ Zz1d dlZe j dd¡Zej e¡Ze ddg¡d  ZeswddiZ neeƒZ e	ej!e dd�ƒ W dS  e"y¦ Z# ze	ej!de$e#ƒidd�ƒ W Y dZ#[#dS dZ#[#ww )é    N)Údatetimez/opt/ngon/apps)ÚconfigzContent-Type: application/json
)Úload_inventoryÚload_minersÚload_sleeping_minersÚget_miner_colorc           
   
   C   sü   dd„ | D ƒ} | st ƒ S zPddl}|jdddd�}| ¡ }t ƒ }t| ƒ}tdt|ƒd	ƒD ])}|||d	 … }d
 dt|ƒ ¡}| d|› d�|¡ | 	dd„ | 
¡ D ƒ¡ q,| ¡  |W S  ty} }	 ztd|	› d�tjd� t | ƒW  Y d}	~	S d}	~	ww )uÖ  Return the subset of `macs` that have EVER recorded hs_rt > 0, via the
    maintained miner_lifecycle.ever_hashed flag (instant indexed lookup â€” the
    flag is set monotonically by get_hashrate_and_status.py on each snapshot).

    MACs are normalized to uppercase (how the DB stores them). On ANY DB error
    we fail safe by returning the full input set (treat all as having hashed),
    so a DB hiccup never floods the problem list with false "never hashed" flags.c                 S   s   h | ]}|r|  ¡ ’qS © ©Úupper©Ú.0Úmr   r   ú3/var/www/html/ngon/status/status_site_detail_cgi.pyÚ	<setcomp>   ó    z'get_ever_hashed_macs.<locals>.<setcomp>r   Nz,file:/opt/ngon/data/miner_history.db?mode=roTé   )ÚuriÚtimeoutiô  ú,ú?zBSELECT mac FROM miner_lifecycle WHERE ever_hashed = 1 AND mac IN (ú)c                 s   s$   � | ]}|d  r|d    ¡ V  qdS )r   Nr	   )r   Úrr   r   r   Ú	<genexpr>/   s   €" z'get_ever_hashed_macs.<locals>.<genexpr>z$WARNING: ever-hashed lookup failed (z); treating all as hashed©Úfile)ÚsetÚsqlite3ÚconnectÚcursorÚlistÚrangeÚlenÚjoinÚexecuteÚupdateÚfetchallÚcloseÚ	ExceptionÚprintÚsysÚstderr)
Úmacsr   ÚconnÚcurÚeverÚmac_listÚiÚchunkÚphÚer   r   r   Úget_ever_hashed_macs   s8   ÿ
þÿ€ýr4   c                 C   sì  t ƒ }t ƒ }| |v r$||  D ]}| |¡ ||  | D ]}	| |	¡ qq| |v rB||  D ]}| |¡ ||  | D ]}	| |	¡ q9q,|rF|sKg ddœS t|ƒt|ƒ}
}t|ƒt|ƒ}}g |
|||dœ}t|
|d ƒD �]}|g dœ}t||d ƒD ]ð}t| ||||||ƒ}||||dœ}| |v �r|||  v �r|||  | v �r||  | | }| dd¡ ¡ }||v }d}| |v rÕ|||  v rÕ|||  | v rÕ||  | | d	 }t|d
uoá|oá| 	¡ |vƒ}|rð| d¡pëddkrðd}| 
|d |d |d |d |d |d |d ||||ddœ¡ nS| |v �r`|||  v �r`|||  | v �r`||  | | }|d �p4d}t|d
u�oH|d �oH|�oH| 	¡ |vƒ}| 
|d	 |d ||d �rZdnddœ¡ nd|d< |d  |¡ q{|d  |¡ ql|S )z5Generate data structure for a single pod's miner gridzNo data)ÚrowsÚmessage)r5   Úmin_rowÚmax_rowÚmin_posÚmax_posé   )Úrow_numÚ	positions)Úpos_numÚcolorÚrowÚposÚmacÚ ÚserialNÚhashrater   FÚ
miner_typeÚpowerÚerror_codesÚtempÚ	fan_speedÚuptimeÚonline)rF   rE   rG   rH   rI   rJ   rK   rB   rD   Úis_sleepingÚnever_hashedÚstatusÚ	installedÚofflineÚempty)rD   rB   rN   rO   rO   r=   r5   )r   ÚaddÚminÚmaxr    r   ÚgetÚlowerÚboolr
   r$   Úappend)Úpod_keyÚ	inventoryÚminersÚsleeping_minersÚminer_type_specsÚever_hashed_macsÚall_rowsÚall_positionsr<   r>   r7   r8   r9   r:   Ú	grid_datar@   Úrow_datarA   r?   Úposition_dataÚminerrB   rM   rD   rN   Úinventory_dataÚomacr   r   r   Úgenerate_pod_grid_data7   sš   
ÿ
ÿ
û
ü*$
ÿ
ô*ÿ
ÿ
ürh   c                  C   s  i } zot ddƒ�_}t |¡}|D ]M}| dd¡ ¡ }|sq|| vr%g | |< | |  | dd¡ ¡  ¡ t| dd¡p:dƒt| dd¡pCdƒ| d	d¡| d
d¡ ¡ | dd¡ ¡  ¡ dœ¡ qW d  ƒ W | S 1 sjw   Y  W | S  tyŽ } zt	d|› �t
jd� W Y d}~| S d}~ww )z“Load all miners grouped by pod field from miner_status.csv.
    Returns dict of pod_name -> list of miner dicts (includes miners not in inventory).z$/opt/ngon/data/live/miner_status.csvr   ÚpodrC   rB   Úhs_rtr   rG   rF   rH   Úmining_state)rB   rE   rG   rF   rH   rk   Nz!Error loading all miners by pod: r   )ÚopenÚcsvÚ
DictReaderrV   ÚstriprY   rW   Úfloatr'   r(   r)   r*   )ÚpodsÚfÚreaderr@   ri   r3   r   r   r   Úload_all_miners_by_pod›   s8   


úúþîþ€þrt   c                 C   sj   |r| |v rt dd„ ||  D ƒƒS d}| |v r3||  D ]}||  | D ]}|||  | | d 7 }q#q|S )zºCalculate total hashrate for a pod.
    Uses all_miners_by_pod (from pod field) for accurate totals that include
    miners not in inventory. Falls back to inventory-matched miners only.c                 s   s   � | ]}|d  V  qdS )rE   Nr   r   r   r   r   r   ¹   s   € z)calculate_pod_hashrate.<locals>.<genexpr>r   rE   )Úsum)rZ   r[   r\   Úall_miners_by_podÚtotalr<   r>   r   r   r   Úcalculate_pod_hashrate´   s   ÿrx   c           
      C   s®   |r| |vrdS t ƒ }| |v r4||  D ] }||  | D ]}||  | |  dd¡ ¡ }|r2| |¡ qqd}d}||  D ]}	|	d rR|	d |vrR|d7 }||	d 7 }q<||fS )z8Count miners in a pod that aren't in the inventory grid.)r   ç        rB   rC   r   ry   r;   rE   )r   rV   rW   rS   )
rZ   r\   rv   Úpositioned_macsr<   r>   rB   ÚcountrE   r   r   r   r   Úcount_unpositioned_minersÂ   s&   
€ý€r|   c                  C   sÂ   z'ddl } | jjddd��}t | ¡  ¡ ¡}W d  ƒ n1 s!w   Y  W n ty2   i  Y S w i }| d¡p;i  	¡ D ] }| d¡pFi  	¡ D ]}| d¡pQi  
¡ D ]\}}|||< qTqIq>|S )	aÒ  {unit_id: gen} from status_api, for gens the Mesa feed doesn't carry.

    status_api is the authority the rest of the status page renders from, so
    asking it here keeps this modal agreeing with the generator columns instead
    of quietly disagreeing. Best-effort by design: on any failure we return {}
    and resolve_generator() still emits the gen, just without a run status. A
    generator that is missing is a bug; one showing an unknown state is not.
    r   Nz http://localhost:5050/api/statusé   )r   ÚsitesÚgenerator_groupsÚ
generators)Úurllib.requestÚrequestÚurlopenÚjsonÚloadsÚreadÚdecoder'   rV   ÚvaluesÚitems)Úurllibr   ÚdataÚoutÚsiteÚgroupÚgen_idÚgenr   r   r   Úload_live_gen_index×   s"   	ÿ€ÿ
ÿÿr‘   c                 C   s2   |  | ¡}|r	|S |  | ¡pi }| |  d¡ddœS )a3  Always return a renderable generator entry for a configured gen.

    Prefers the full Mesa record when there is one. Otherwise synthesises the
    two fields the modal actually reads (unit_id, run_status) from status_api.
    Never returns None: if a gen is mapped to a pod in master_config it belongs
    on screen, and an unknown-state dot beats a missing generator.

    run_status here is the non-live branch of the frontend's
    genEffectiveRunStatus() -- status_api's `status`, i.e. what Mesa last said.
    The live-telemetry branch is deliberately NOT reimplemented: duplicating that
    freshness rule in a second place is how the two drift apart. A gen running on
    our own DSE feed with no Mesa record would read offline here; no gen is in
    that state today, and the modal's dot is binary anyway.
    rO   T)Úunit_idÚ
run_statusÚno_provider_data©rV   )r�   Úmesa_by_unitÚlive_by_unitÚgen_dataÚliver   r   r   Úresolve_generatorî   s   
ýrš   c           '   
      s6  �zùt ƒ \}}t|ƒ}tƒ }tƒ }zt ¡ }W n   i }Y g ‰t ¡  ¡ }‡ fdd„|D ƒ‰tƒ }ˆD ]N}	|	|v r\||	 D ]}
||	 |
 D ]}||	 |
 |  	d¡}|rZ| 
|¡ qFq>|	|v r‚||	 D ]}
||	 |
 D ]}||	 |
 |  	d¡}|r€| 
|¡ qlqdq4t|ƒ}z
t ¡ }t ¡ }W n   i }i }Y g }d}tj |¡r¾t|dƒ�}t |¡}W d  ƒ n1 s¹w   Y  dd„ |D ƒ}tƒ }ˆ d	g g i d
œ}ˆD ]}t||||ƒ}|d  |7  < qÒtƒ }| ¡ D ]­\}}| ˆ d ¡rÿ| dd¡d }n|ˆ k�sˆ dv �r|dv �r|}nqë‡fdd„|D ƒ}|�r˜|g dœ}|D ]:}t||||ƒ}t||||||ƒ}t|||ƒ\}} |||dœ}!|d	k�rR||!d< | |!d< |d  |!¡ | 
|¡ �q%g }"tƒ }#|D ]%}||v �r‹|| D ]}$|$|#v�r‡|" t|$||ƒ¡ |# 
|$¡ �qr n�qg|"|d< |d  |¡ qëˆD ]S}||v�rít||||ƒ}t||||||ƒ}t|||ƒ\}} g }%||v �rÒ|| D ]}$|% t|$||ƒ¡ �qÅ||||%dœ}!|d	k�ræ||!d< | |!d< |d  |!¡ �q›t ¡  d¡|d< |W S  t �y }& zt!|&ƒˆ t ¡  d¡dœW  Y d}&~&S d}&~&ww )z4Get comprehensive site detail data for modal displayc                    s&   g | ]}|  ˆ d  ¡s|ˆ kr|‘qS )ú )Ú
startswith©r   ri   )Ú	site_namer   r   Ú
<listcomp>  s   & z(get_site_detail_data.<locals>.<listcomp>rB   z'/opt/ngon/data/live/generator_data.jsonr   Nc                 S   s"   i | ]}|  d ¡r|  d ¡|“qS )r’   r•   )r   Úgr   r   r   Ú
<dictcomp>D  s   " z(get_site_detail_data.<locals>.<dictcomp>r   )rž   Útotal_hashrateÚ
pod_groupsÚstandalone_podsr€   r¢   Ú_r;   )úOut of ServiceÚSpares)r¦   zAvailable Sparesc                    s   g | ]}|ˆ v r|‘qS r   r   r�   )Ú	site_podsr   r   rŸ   e  r   )Ú
group_namerq   )rZ   rE   rb   Úunpositioned_countÚunpositioned_hashraterq   r€   r£   )rZ   rE   rb   r€   r¤   z%m/%d %H:%MÚ
updated_atz%m/%d %H:%M:%S)Úerrorrž   r¬   )"r   r   r   rt   r   Úget_miner_type_specsÚget_pod_miner_countsÚkeysr   rV   rS   r4   Úget_generator_groupsÚget_generator_to_pod_mappingÚosÚpathÚexistsrl   r„   Úloadr‘   rx   r‰   rœ   Úsplitrh   r|   rY   rš   r   ÚnowÚstrftimer'   Ústr)'rž   r[   Úmac_to_locationr\   r]   rv   r^   Úall_podsÚ	site_macsÚpkr   Úpr   r_   r   Úpod_to_gensÚgenerator_dataÚgenerator_data_pathrr   r–   r—   Úsite_detailrZ   Úpod_hashrateÚgrouped_podsr©   Ú	pod_namesÚgroup_display_nameÚpods_in_groupÚ
group_dataÚpod_gridÚunpos_countÚunpos_hashrateÚ	pod_entryÚgroup_generatorsÚseen_gen_idsr�   Úpod_generatorsr3   r   )rž   r¨   r   Úget_site_detail_data  sü   

€ý
€ý€	ÿû	þý



ÿ
€÷€


ÿü
€ý€ÿrÑ   ÚQUERY_STRINGrC   r�   r­   zMissing site parameterr}   )Úindent)N)%r³   r)   r„   rm   r   r´   ÚinsertÚmanagers.config_managerr   r(   Úminers.miner_gridr   r   r   r   r4   rh   rt   rx   r|   r‘   rš   rÑ   Úurllib.parserŠ   ÚenvironrV   Úquery_stringÚparseÚparse_qsÚparamsrž   r‹   Údumpsr'   r3   rº   r   r   r   r   Ú<module>   s>   
 d
 1
(€ÿ