o
    kï«j�à  ã                   @   s  d Z ddlZddlZddlZej dd¡ ej dd¡ ddlmZmZ ddl	m
Z
mZmZ dZe ¡ ZedƒZe e¡\ZZZer]ed	ƒ erLeeƒ ed
ƒ eerUdndƒ e d¡ ernedƒ eedƒƒ e d¡ e ¡ Zh d£Zdd„ Zeƒ Zddddœddddœddddœddddœdd d!dœd"d#d$dœd%d&d'dœd(d)d*dœd+d,d-dœd.d/d0dœd1d2d3dœgZg d4dd5d6d7d8gd9d:œ‘d;dd5d<d=dgd>d:œ‘d?dd5d@dAdBgdCd:œ‘dDdd5dEdFdGgdHd:œ‘dIdd5dJdKdLgdMd:œ‘dNdd5dOdPdQgdRd:œ‘dSdd5dTdUdQgdVd:œ‘dWdd5dXdYdZgd[d:œ‘d\dd]d^d_d`dagdbdcddœ‘dedd]dfdgd8dagdhd:œ‘didd]djdkdlgdmd:œ‘dndd]dodpdBgdqd:œ‘drdd]dsdtdBgdud:œ‘dvdd]dwdxdgdyd:œ‘dzdd{d|d}d`dagd~dd€œ‘d�dd{d‚dƒdagd„d…d€œ‘d†dd{d†d‡d`gdˆd:œ‘d‰dd{d‰dŠdBgd‹d:œ‘dŒdd{dŒd�d8gdŽd:œ‘d�dd{d�d�dlgd‘d:œ‘d’dd{d“d”dQgd•d:œ‘d–dd{d—d˜d`gd™d:œ‘dšddd›dœg d�¢džd:œ‘dŸddd d¡dadgd¢d:œ‘d£ddd¤d¥g d¦¢d§d¨d€œ‘d©dddªd«dgd¬d:œ‘d­d"d®d¯d°d¯d`gd±d²d³œ	‘d´d"d®dµd¶dµd8gd·d¸d³œ	‘d¹d"d®d¹dºd¹dlgd»d¼d³œ	‘d½d"d®d¾d¿d¾d`gdÀdÁd³œ	‘dÂd"d®dÂdÃdÂdBgdÄdÅd³œ	‘dÆd"d®dÇdÈdÇdZgdÉdÊœ‘dËd%d®dËdÌdËg dÍ¢dÎdÏd³œ	‘dÐd%dÑdÒdÓdÒd8dagdÔdÕd³œ	‘dÖd%dÑd×dÓd×dgdØdÊœ‘dÙd%dÑdÚdFdGgdÛd:œ‘dÜd%dÑdÜdÝdÜd8dagdÞdßd³œ	‘dàd%dÑdádÓdgdâd:œ‘dãd%dãdädåg dæ¢dçd:œ‘dèd(d(dédêdëgddìd€œ‘díd(d(dîdïd`gdðdñd€œ‘dòd(d(dódôg dõ¢död:œ‘d÷d(d(dødùd`gdúd:œ‘dûd(d(düdýd8gdþd:œ‘dÿd(d(�d �ddlg�dd:œ‘�dd(d(�d�dd8dLg�dd:œ‘�dd(d(�d�ddQg�d	d:œ‘�d
d(d(�d�ddGg�dd:œ‘�dd(d(�d�ddag�dd:œ‘�dd+d®�d�d�ddad`g�d�dd³œ	‘�dd+d®�d�d�dd8g�ddÊœ‘�dd+d®�d�d�dd`g�ddÊœ‘�dd+d®�d�d�dd`g�d dÊœ‘�d!d+d®�d"�d#�d"d8g�d$dÊœ‘�d%d+d®�d&�d'�d&d8dLg�d(dÊœ‘�d)d+d®�d*�d+�d*d8dag�d,dÊœ‘�d-d+d®�d-�d.�d-dg�d/dÊœ‘�d0d+d®�d0�d1�d0d`g�d2dÊœ‘�d3d+d®�d3�d4�d3dBdag�d5�d6d³œ	‘�d7d+dÑ�d7�d8�d7dBg�d9�d:d³œ	‘�d;d+d®�d<�d=�d<�d>g�d?dÊœ‘�d@d+d®�dA�dB�dAdGg�dCdÊœ‘�dDd+d®�dE�dFd`dLg�dGd:œ‘�dHd%d®�dH�dI�dHdQg�dJ�dKd³œ	‘�dLd+d®�dM�dN�dMdLg�dOdÊœ‘�dPd.�dQ�dR�dSg dÍ¢�dTd:œ‘�dUd.�dQ�dV�dWd`dag�dXd:œ‘�dYd.�dQ�dZ�d[dëg�d\d:œ‘�d]d.�dQ�d^�d_g �d`¢�dad:œ‘�dbd.�dQ�dc�dddBg�ded:œ‘�dfd.�dQ�dg�dhdGg�did:œ‘�djd.�dQ�dk�dldQg�dmd:œ‘�dnd.�dQ�do�dpd8dQg�dqd:œ‘�drd.�dQ�ds�dtdLg�dud:œ‘�dvd.�dQ�dw�dxdZg�dyd:œ‘�dzd1d5�d{�d|�d>g�d}d:œ‘�d~d1d5�d~�d�d>g�d€d:œ‘�d�d1d5dX�d‚dLg�dƒd:œ‘Zg d\dz�d„�d…d`g�d†�d‡œ‘d\d†�dˆ�d‰d`g�dŠ�d‡œ‘d\d–�d‹�dŒd`g�d��d‡œ‘dedŒ�dŽ�d�d8g�d†�d‡œ‘did��d��d‘dlg�d��d‡œ‘dnd‰�d’�d“dBg�dŠ�d‡œ‘dvdz�d”�d•dg�dŠ�d‡œ‘d?d‰�d–�d—dBg�dŠ�d‡œ‘d;dv�d˜d
dg�dŠ�d‡œ‘dzdš�d™�dšd`g�d†�d‡œ‘dzdš�d›�dœd`g�d†�d‡œ‘d†dš�d�d
d`g�dŠ�d‡œ‘dŒdš�dž�dŸd8g�d†�d‡œ‘d�dš�d �d‘dlg�d��d‡œ‘d‰dš�d¡�d¢dBg�dŠ�d‡œ‘d–dš�d£d
d`g�dŠ�d‡œ‘dšd©�d¤�d¥d`g�d†�d‡œ‘dšd£�d¦�d§g �d¨¢�d†�d‡œ‘d©d­d
d
d`g�d†�d‡œ‘d£d´�d©d
d8g�d†�d‡œ‘d£d¹�dªd
dlg�d��d‡œ‘d£d½�d«d
d`g�d†�d‡œ‘d£dÂ�d¬d
dBg�dŠ�d‡œ‘d£dÆ�d­�d®dZg�dŠ�d‡œ‘d­dí�d¯d
d`g�d†�d‡œ‘d­dË�d°�d±d`g�d†�d‡œ‘d´dË�d²�d³d8g�d†�d‡œ‘d´dû�d´�dµd8g�d��d‡œ‘d¹dÿ�d¶d
dlg�d��d‡œ‘d¹dË�d·d
dlg�d��d‡œ‘dÂ�d
�d¸�d¹dBg�dŠ�d‡œ‘dÆdã�dº�d»dZg�dŠ�d‡œ‘d4dÐ�d¼�d½d8g�d��d‡œ‘dËdÐ�d¾�d¿d8dag�d†�d‡œ‘dÐdË�dÀd
d8g�d†�d‡œ‘dÐdè�dÁ�dÂdag�d��d‡œ‘dÐ�d�dÃ�dÄdag�d��d‡œ‘dÐ�d;�dÅd
�d>g�dŠ�d‡œ‘dÐdò�dÆd
d8g�d��d‡œ‘dÜ�d)�dÇ�dÈdag�dŠ�d‡œ‘dËdÜ�dÉd
d8g�d��d‡œ‘dûdÜ�dÊ�dËd8g�dŠ�d‡œ‘d;dÖ�dÌd
dg�d��d‡œ‘dŸdà�dÍ�dÎdg�d��d‡œ‘dà�d;�dÏd
�d>g�dŠ�d‡œ‘dÖdË�dÐd
dg�d��d‡œ‘dDdÙ�dÑdFdGg�dŠ�d‡œ‘dÙdË�dÒd
dGg�dŠ�d‡œ‘dWdã�dÓdFdZg�dŠ�d‡œ‘dèdË�dÔd
dëg�d��d‡œ‘dídã�dÕ�dÖd`g�d��d‡œ‘dãdò�d×�dÖd`g�d��d‡œ‘dãd÷�dØ�dÙd`g�d��d‡œ‘dã�d�dÚ�dÛdag�dŠ�d‡œ‘dã�d�dÜ�dÝd8g�dŠ�d‡œ‘dã�dz�dÞ�dß�d>dLg�dŠ�d‡œ‘dIdã�dàd
dLg�dŠ�d‡œ‘dã�d��dád
dLg�dŠ�d‡œ‘dò�d�dâd
d8g�d��d‡œ‘dò�d�dâd
d`g�d��d‡œ‘dò�d�dâd
d`g�d��d‡œ‘d÷�d�dãd
d`g�d��d‡œ‘�d�d!�däd
d8g�dŠ�d‡œ‘�d�d%�dåd
d8g�dŠ�d‡œ‘dí�d�dæ�dçdag�d��d‡œ‘�d�d�dè�dédag�dŠ�d‡œ‘dè�dL�dê�dëdLg�dŠ�d‡œ‘d÷�dL�dì�dídLg�dŠ�d‡œ‘�ddŸ�dî�dïdag�d��d‡œ‘�d)dŸ�dð�dñdag�dŠ�d‡œ‘dŸd��dòd
dag�d��d‡œ‘d�d\�dó�dôdag�d��d‡œ‘dŸde�dõ�dödag�dŠ�d‡œ‘dèdz�d÷�dødëdag�d��d‡œ‘dË�dP�dù�dúd`d8g�d†�d‡œ‘dË�dU�dûd
d`g�d��d‡œ‘dË�dY�dûd
dëg�d��d‡œ‘dË�d]�dûd
dLg�dŠ�d‡œ‘�d�dP�düd
dag�d��d‡œ‘�d�dU�dýd
dag�d��d‡œ‘�d)�dP�dþ�dÿdag�dŠ�d‡œ‘�d!�dP�d d
d8g�dŠ�d‡œ‘�d�dP�dd
d8g�d��d‡œ‘�d�d]�dd
dLg�d��d‡œ‘�d�d]�dd
dLg�d��d‡œ‘�d%�d]�dd
dLg�dŠ�d‡œ‘d½�dP�d�dd`g�d†�d‡œ‘�d@�dP�dd
dGg�dŠ�d‡œ‘�d0�dU�dd
d`g�d��d‡œ‘�d-�dU�d	d
dg�dŠ�d‡œ‘dÂ�db�d
d
dBg�dŠ�d‡œ‘drdŸ�d�ddBg�d��d‡œ‘dŸ�d3�d�ddBg�d��d‡œ‘dŸ�d7�d�ddBg�d†�d‡œ‘�d-�d3�d�ddg�dŠ�d‡œ‘�d3�db�d�ddBg�d��d‡œ‘�d7�db�d�ddBg�d†�d‡œ‘�d3dr�d�ddadBg�dŠ�d‡œ‘�d
�df�dd
dGg�dŠ�d‡œ‘�dD�d]�dd
dLg�dŠ�d‡œ‘�dL�dr�d�ddLg�dŠ�d‡œ‘dÆ�dv�dd
dZg�dŠ�d‡œ‘�dYdè�d�ddëg�dŠ�d‡œ‘dNd’�d �d!dQg�dŠ�d‡œ‘d’d£�d"�d#dQg�d��d‡œ‘d£�dH�d$d
dQg�d��d‡œ‘dS�dH�d%�d&dQg�d��d‡œ‘�dH�d�d'd
dQg�d��d‡œ‘�dH�dj�d(�d)dQg�d��d‡œ‘�dHdS�d*�d+dQg�d��d‡œ‘�dHd’�d,�d-dQg�d��d‡œ‘d’dN�d.�d/dQg�dŠ�d‡œ‘�d;�dz�d0�d1�d>g�dŠ�d‡œ‘�d!�d~�d2�d3�d>g�dŠ�d‡œ‘�d!dS�d4�d5d8dQg�dŠ�d‡œ‘dS�d!�d6�d7d8dQg�d��d‡œ‘�d!�dn�d8d
d8g�d��d‡œ‘�dn�d!�d9�d:d8dQg�dŠ�d‡œ‘d´�d)�d;�d<dag�d��d‡œ‘Zd`�d=�d>�d?œd8df�d@�d?œdl�dA�dB�d?œda�dC�dD�d?œdB�dE�dF�d?œdë�dG�dH�d?œdL�dI�dJ�d?œ�d>�dK�dL�d?œdQ�dM�dN�d?œd�dO�dP�d?œdZ�dQ�dR�d?œdG�dS�dT�d?œgZ�dP�dRg �dU¢�dVœ�dU�dWg �dX¢�dVœ�dY�dZg �dY¢�dVœ�db�dcg �dZ¢�dVœ�dj�dkg �d[¢�dVœ�dn�dog �d\¢�dVœ�d]�d]g �d^¢�dVœgZ eeeee e�d_œZ!edƒ e�d`eƒ › �dae
eƒ› �dbe "e!¡› �dceƒ › �dd�	ƒ dS (e  u�  Data Flow â€” an animated map of how data moves through the NGON stack.

Read-only and explanatory: field hardware -> collectors -> VPN/HTTPS -> cloud
APIs -> stores -> pages. The topology below is hand-maintained (it describes
intent, which no amount of grepping recovers); only the site/pod layer and the
live overlay are generated at runtime.

Live overlay comes from dataflow_data_cgi.py (disk-cached, ~5 s).
é    Nz/opt/ngon/appsz/var/www/html/ngon)ÚAuthManagerÚgenerate_login_page_html)Úgenerate_dropdown_htmlÚgenerate_dropdown_cssÚgenerate_dropdown_jsz#/opt/ngon/config/master_config.jsonÚdataflowzContent-Type: application/jsonÚ z6{"success": false, "error": "Authentication required"}z{"success": true}zContent-Type: text/html
z	Data Flow>   úWest TX TBDúOut of ServiceÚSparesc                  C   sP  zt tƒ�} t | ¡}W d   ƒ n1 sw   Y  W n ty&   g  Y S w g }| d¡p/i  ¡ D ]k\}}|tv r;q2g }| d¡pCi  ¡ D ]5\}}t| d¡pQi ƒ}| d¡pYi  ¡ D ]\}	}
| 	|	|
 d¡pn| d¡pnd|
 d¡ptd||d	œ¡ q\qF|sq2|j
d
d„ d� | 	||tdd„ |D ƒƒt| d¡ƒdœ¡ q2|j
dd„ d� |S )NÚsitesÚgenerator_groupsÚ
generatorsÚpodsÚnetworkr   Úminer_countr   )ÚnameÚnetÚminersÚgroupÚgensc                 S   s   | d S )Nr   © )Úpr   r   ú%/var/www/html/ngon/status/dataflow.pyÚ<lambda>I   s    zbuild_sites.<locals>.<lambda>)Úkeyc                 s   s   � | ]}|d  V  qdS )r   Nr   )Ú.0r   r   r   r   Ú	<genexpr>M   s   € zbuild_sites.<locals>.<genexpr>Ú
flow_meter)r   r   r   Úflowc                 S   s
   | d  S )Nr   r   )Úsr   r   r   r   P   s   
 )ÚopenÚMASTER_CONFIG_FILEÚjsonÚloadÚ	ExceptionÚgetÚitemsÚ
SKIP_SITESÚlenÚappendÚsortÚsumÚbool)ÚfÚcfgr   Ú	site_nameÚsiter   Ú
group_namer   r   Úpod_nameÚpodr   r   r   Úbuild_sites1   sF   
ÿ€ÿ
ûÿ
ür5   Úext_inzUpstream sourceszthird-party APIs we pull from)ÚidÚtitleÚnoteÚfieldz	The fieldzmetal in a pastureÚcollectÚ
Collectorsz$field servers that talk to the metalr   Ú	Transportzhow bytes get to the cloudÚedgez
Cloud edgeu   ngon.us â€” Apache + UFWÚingestzIngest APIszfield pushes land hereÚbrainzState & automationzthe part that decides thingsÚstoreÚStoreszwhere it persistsÚreadzRead / action APIszwhat the pages callÚoutÚ	Consumerszhumans and other boxesÚext_outÚ
Downstreamzwhere output goesÚmesa_apiÚextzMesa Provider APIz
HTTPS pollÚgenuï   Our generator vendor's cloud. Run status, kW, pressures, engine hours for every gen. Polled every 60 s. Can return HTTP 200 with frozen data â€” that's what the staleness watchdog exists for. Being retired in favour of our own Modbus feed.)r7   ÚlaneÚkindÚlabelÚsubÚflowsÚdescÚ
peplink_iczPeplink InControlÚOAuthu§   Cloud manager for our 28 Peplink routers. Gives us WAN health plus the client list per pod â€” which is how agents find miner IPs fast instead of sweeping a whole /23.Úblink_cloudzBlink CloudÚAmazonÚcamsuƒ   Site cameras. Cloudflare blocks our datacenter IP, so only the field server can talk to it â€” hence the outbound job-queue design.Úweather_apizWeather APIÚhourlyÚmiscz-Per-site conditions shown on the status page.ÚfoundryzFoundry Pool APIz	X-API-KEYÚreportz™The mining pool. Real earnings and pool-side hashrate per worker (ND and TX are separate workers). The honest number our own hashrate is checked against.Ú
sms_peopleÚTextersu'   gas providers Â· field staff Â· haulersÚtextuº   The people on the other end of a group text. They need no account and see nothing but an ordinary SMS thread from our company number â€” no app, no Google login, no idea a bridge exists.Úgchat_spaceszGoogle Chat Spacesu)   service account Â· domain-wide delegationuj  The NGON-internal half of the same conversation. A Space is bridged to an SMS group: staff talk in Chat, the outside party texts, and neither sees the other's mechanism. Auth impersonates a real Workspace user (textbridge@) rather than running as a Marketplace bot â€” one admin screen instead of a publishing process, at the cost of posts appearing from a user.Ú
gsheets_inzGoogle Sheetszinventory masterÚ	inventoryuO   The miner inventory master sheet â€” serial, MAC, pod, position. Pulled hourly.r   ÚhwzPods & minerszWhatsminer M60ÚminerÚcontrolr   z‡Every container of miners, grouped by site. Each pod is a /23. A pod chip lights up when a scanner is actually talking to it right now.)r7   rK   rL   rM   rN   rO   ÚspecialrP   Úgens_hwÚ
Generatorsu   DSE controllers Â· Modbus TCPuc  Deep Sea controllers on each genset, reached over MikroTik wifi bridges. Source of live kW, pressures, coolant temp, alarms â€” and the write path for remote clear-alarm / manual / start. A tech putting the panel into service mode shows up here too, as register 42944 â€” which is how a man with a wrench on a gen becomes something the automation can see.Úflow_hwzGas flow metersu   ABB Totalflow Â· GN, WillÚgasu�   Modbus 32-bit floats off the ABB Totalflow. How much gas the site is actually getting â€” the ceiling on how many miners can run.Úcams_hwzBlink cameraszsync modulesz*Battery cameras and sync modules per site.Úptz_hwzPTZ camerasu   ONVIF / PoE Â· on the pod LANu  Wired cameras that sit on the pod network itself, so the cloud reaches them straight down the tunnel â€” no field agent, no vendor cloud, no rate limit. They run DHCP, so the lease moves and the MAC is the only stable handle; that's what master_config stores.Úpeps_hwzPeplink + Starlinkz
28 routerszNStarlink dish into a Peplink router at every pod. Everything below rides this.Ú
pod_agentsÚagentz
Pod agentsu   agent_launcher Â· 39 threadsz"/home/ngon/backend_v2/pod_agent.pyzàOne thread per pod network on the field server (cherub). Scans its miners every ~30 s, does the hourly pool sweep, and runs the Emergency-Sleep hammer loop when EMS is armed. This is the 'scanner' you see on the status page.)r7   rK   rL   rM   rN   rO   ÚfilerP   Ú	field_apiÚfield_miner_apizcherub :6060z(/home/ngon/backend_v2/field_miner_api.pyuˆ   Executes commands against miners: sleep, wake, reboot, set power, fix pools. The cloud never talks to a miner directly â€” it asks this.Úplucker_relayzUDP 8888zcRelays miner IPFOUND broadcasts up to the cloud so a newly-plugged M60 shows itself without a scan.Úblink_agentzoutbound job pollz†Only process allowed to touch Blink. Polls the cloud for snapshot jobs, takes the picture, posts the JPG back. No inbound port needed.Úgen_collectoru   VM Â· Modbus 1 Hzz�Polls every DSE controller directly: a fast 1 Hz power/status read, plus a ~5-minute full register block (~80 fields). Our own gen data, independent of Mesa.Úflow_collectoru   VM Â· ~1/minzDReads the ABB Totalflow meters and pushes the readings to the cloud.Úcompany_phonezCompany phoneu   Android Â· holds the SIMum  One handset, two apps, one job each. Ours sends â€” it drives the radio directly and can do real group MMS â€” by polling /outbox, sending, then acking. The SMS Gateway app only receives, posting signed webhooks for inbound SMS/MMS. The phone is behind carrier CGNAT and can never be reached inbound, so every exchange is phone-initiated; nothing here pushes to it.ÚpickaxeszForeman Pickaxesu   office LAN Â· Ã—6u³   Foreman's own scanners, on the office network. They compete with our agents for the miners' limited connection slots â€” so we watch what they're scanning and stay out of the way.Ú	push_pathzDirect HTTPS pushu   field â†’ ngon.us)rb   rJ   rh   rU   u¨   The load-bearing path. Field boxes push out to ngon.us over the public internet â€” deliberately NOT through the VPN tunnel, so tunnel trouble can't stop data arriving.ÚvpnzSpeedFusion VPNu#   FusionHub 10.116.0.3 â†’ Hearne hubu³   Cloud â†’ field direction rides the tunnel: monitoring polls and anything that needs to reach into a pod network. One hop through the hub; a bad DO host shows up here as flapping.ÚapacheÚApachez:443 + ProxyPass)rb   rJ   rh   rc   rU   ÚconfigrZ   r`   z4/etc/apache2/sites-available/000-default-le-ssl.confzjTLS termination and the whole /api/* routing table. Every internal service hides behind this on localhost.ÚufwÚUFWu&   4999 Â· 80/443 Â· 5006 Â· 5014 Â· 5020z”What's actually reachable from outside: SSH on 4999, the web, the miner-push port 5006, and two VPC-scoped ports. Everything else is localhost-only.Úlisten_minerÚapiÚlisten_miner_data_apiz:5006z&/opt/ngon/api/listen_miner_data_api.pyz§Receives every pod scan. Writes miner_status.csv, computes the pod summary (online / hashing / sleeping) and pushes it to the status API. The busiest thing on the box.)	r7   rK   rL   rM   rN   ÚsvcrO   rn   rP   Ú
gen_ingestÚgen_ingest_apiz:5018z/opt/ngon/api/gen_ingest_api.pya"  Two doors: 1 Hz live power/status straight into live state, and the slow 5-minute register block into gen_telemetry.db. Also evaluates warning thresholds, recomputes PM due-dates as engine hours arrive, and mirrors the panel service-mode flag so the control side knows a tech is on the gen.Úflow_apiz:5017z/opt/ngon/api/flow_api.pyzFGas readings in, gas_flow.db out, latest value mirrored to live state.Úagent_monitorÚagent_monitor_apiz:5013z"/opt/ngon/api/agent_monitor_api.pyuÓ   Scanner health. The field launcher posts a full snapshot every 5 s â€” which pod is scanning, how many miners it saw, Peplink stats. Also holds the Pickaxe watcher state. This page's live overlay is mostly this.Ú	blink_apiz:5016z/opt/ngon/api/blink_api.pyzvJob queue + snapshot cache. Browsers ask it for a picture; the field agent picks the job up on its next outbound poll.Úinv_webhookÚinventory_webhook_apiu   :5014 Â· VPC onlyz…inventory.ngon.us tells us when someone puts or pulls a miner, and validates login cookies for the other subdomains (single sign-on).)r7   rK   rL   rM   rN   r�   rO   rP   Ú
status_apiz:5050 + WebSocket)rb   rJ   rh   rc   r{   z/opt/ngon/api/status_api.pyuÏ   The hub. Holds the entire live picture in memory â€” config merged with live stats â€” and streams every change to open dashboards over WebSocket. If a number moves on the status page, it moved through here.Úgen_monitorr�   Úgenerator_monitorÚ
continuousz'/opt/ngon/services/generator_monitor.pyzÕThe power brain. Reconciles miner load against running generator capacity every ~15 s: arms Emergency-Sleep when a gen trips, wakes miners back in batches when capacity returns, and watches for a frozen Mesa feed.Úpep_monitorÚpeplink_monitorz$Router and WAN connectivity per pod.Úweather_svcÚweather_servicez"Site weather onto the status page.Úgen_autostartu   :5021 Â· default OFFz#/opt/ngon/services/gen_autostart.pyu8  Auto-restart for a group that goes fully down: clear alarms, start the healthy gens, warm up, hand them to AUTO so they load-share. Three independent safety switches, all off by default â€” plus a servicing hold: one gen being worked on freezes the whole group, because the man under it may be under any of them.Ú
tunnel_monÚtunnel_flap_monitorz$Watches the VPN tunnel for flapping.ÚcronzScheduled jobszcron (user ngon))rb   rZ   r`   z´get_hashrate_and_status every 5 min (InfluxDB + history snapshots), reboot_zero at :17, fetch_inventory hourly, the 06:00 daily chat report, nightly gen mapping and uptime rollups.Úmaster_configzmaster_config.jsonzsingle source of truthr{   z—Sites, groups, pods, generators, networks, users, every automation knob. Edited by Site Manager and the automations themselves; never committed to git.Úlive_csvz	Live CSVsz100 MB ramdiskz/opt/ngon/data/live/zrminer_status.csv and friends. Current state of all ~10,000 miners, on a ramdisk because it's rewritten constantly.ÚinfluxÚInfluxDBztime series)rb   rJ   rZ   zMLong-run history for charts: pod hashrate, gen power, per-state miner counts.Úminer_hist_dbzminer_history.dbu   SQLite Â· per-minerz{Half-hourly snapshot of every miner, rolled up hourly then daily. ~5 GB steady state. Backs the trends and dead-miner work.Ú
gen_tel_dbzgen_telemetry.dbu   SQLite Â· ~80 fieldszFThe 5-minute register blocks, plus the gen down-event log with causes.Úgas_dbzgas_flow.dbÚSQLitez8Meter readings; daily-usage reporting reads it directly.Úgen_dbsz&gen_history / gen_service / gen_statusu
   SQLite Ã—3ztMaintenance notes, service invoices and costs, and one row per gen per day of where it was and what state it was in.Útextbridge_dbztextbridge.dbzÕContacts, groups, membership with an audit log of who added whom, messages in and out, and send jobs. Jobs are claimed rather than just read, so a phone that dies mid-send retries instead of stranding the message.Úapp_dbsztodo.db / connex.dbu
   SQLite Ã—2z3Shared todo list and the asset/container inventory.ÚlogsÚLogsz/opt/ngon/logsus   powercontrol.log is the audit trail for every sleep, wake and reboot â€” who, what pod, how many actually answered.Ú	miner_apiz:5004z/opt/ngon/api/miner_api.pyz†Sleep, wake, reboot, single-miner diagnostics. Every action is logged with the count that actually succeeded, not the count requested.Úgen_data_apiz:5001z,Historical generator series out of InfluxDB.Úpod_hash_apiÚpod_hashrate_apiz:5002zPod and fleet hashrate history.Úminer_hist_apiÚminer_history_apiz:5008z+Per-miner and per-state history for charts.Úgen_hist_apiÚgen_history_apiz:5009uÙ  Gen notes, moves, maintenance history, PM schedule â€” and the whole Mesa loop. The chat bridge runs both ways now: what Mesa and we write on the tracker page goes out to ntfy and into the region's Google Space, and replies typed in that Space are polled back (every 20 s) into the same feed. Notes, PM completions and a tech's power request all ride it, so a conversation is one thread no matter which end it was typed at. Also owns the servicing flag a tech sets by hand.Úgen_svc_apiÚgen_service_apiz:5010z)Service records, invoices, cost analysis.Úgen_ctrl_apiÚgen_control_apiz:5019u   Remote DSE control from the cloud: clear alarms, manual mode, start. Guarded by its own permission key and a master engine-action flag â€” and locked out entirely while a gen reads as being serviced, from either the tech's own flag or Mesa's control panel.Úpeplink_apiz:5007u4   Cached Peplink client lists â€” MAC/IP/name per pod.Úplucker_apiz:5012zSAd-hoc miner scanning and the normalized collectors behind every miner detail view.Úptz_apiz:5024z/opt/ngon/api/ptz_api.pyuÐ  Snapshots and PTZ control for the wired cameras. Two jobs worth knowing: it resolves each camera's current IP from its MAC via the Peplink client list, and it proxies every image â€” the camera's snapshot URL carries credentials, so the browser never sees it and only ever gets bytes from us. Caches a frame for 8 s (0.35 s in live mode) so ten operators watching cost the camera one fetch. Kept out of blink_api so a wedged camera can't stall the Blink job queue.Úgo2rtcu   :1984 Â· live videoz/opt/go2rtc/go2rtc.yamluF  Pulls RTSP off the cameras and republishes it to browsers as MSE over a WebSocket â€” actual live video rather than a snapshot loop. A Go binary run under the same screen-session pattern as everything else. Its WebSocket proxy rule must precede the HTTP one in Apache, or mod_proxy_http swallows the upgrade and playback dies.Ú	notif_apiÚnotifications_apiz:5005Úalertz)One place that formats and routes alerts.Úsvc_ctrlÚservice_control_apiz:5015z[Which services are up, and restart buttons for them. This page's green dots come from here.Ú	misc_apisz&miner_status / miner_moves / reportingu   :5011 Â· :5000 Â· :5003zSmaller endpoints: status lookups, miner movement records, and a legacy reporting port an old spreadsheet may still be pulling.Útextbridge_apiz:5023z/opt/ngon/api/textbridge_api.pyu½  The bridge itself, and the system of record for something SMS has no concept of: a group. Every outbound text carries its full recipient list, so 'the group' only exists here. Holds membership (soft-deleted, so history stays truthful), message history, and the queue the phone drains. Polls Chat every 5 s in one direction and posts into the Space in the other. Unlike the older UI APIs it authenticates every call â€” it can text third parties.Úresearch_apiÚresearch_sync_apiu   :5020 Â· VPC onlyz�Strictly read-only bulk egress to the research box: incremental snapshots, rollups, sanitized config. Opens every source read-only and never touches control.Úpage_statusÚpagezStatus pagez/status/zqThe main dashboard. WebSocket-driven, ten-odd modals deep, and the place most control actions actually get taken.Úpage_nmtzNMT + miner toolsz/nmm/z2Miner tool, zero-hash analysis, dead-miner hunter.Úpage_sitemgrzSite Managerz/nmm/site_manager.pyzPWhere the config itself gets edited: sites, pods, gens, users, automation gates.Úpage_reportszReports & trendsu   performance Â· trends Â· uptime)rZ   rb   rJ   zLPerformance, MARA report, miner and gen trends, site uptime, report builder.Ú	page_camsÚCamsz/cams/z-Camera viewer with on-demand fresh snapshots.Ú	page_miscu   Todo Â· Assets Â· Mapu   /todo/ Â· /connex/ Â· mapzThe supporting pages.Úpage_group_textz
Group Textz/status/group_text.pyu¡   Where staff run the threads: groups, membership, history, and sending. A thin shell â€” everything goes through textbridge_api, gated on the group_text page key.Ú	page_mesaz
Mesa pagesu(   field app Â· gen tracker Â· service planu)  The vendor's own end of the system: a phone app for the tech standing at the gen, the gen tracker with its chat feed, and the PM service plan. They log in as Mesa and see their gens only. Everything they can do here is a note, a flag or a request â€” no page of theirs reaches our control surface.Úresearch_boxzResearch dropletz
10.116.0.4zrSeparate box with read-only replicas, so questions can be asked of the data without any path to the control plane.Úinv_sitezinventory.ngon.uszseparate serveru¼   Put/pull inventory app, and now Site Development â€” planning a site build or a move. Shares our login cookie via the verify endpoint, which also says which restricted apps the user holds.ÚgchatzGoogle ChatÚwebhooksz3Alerts and the 06:00 daily report, routed per site.Úntfyu   push Â· TX + ND topicsuC   Push notifications to Mesa's phones â€” data push, no carrier fees.Úgsheets_outzreporting exportsz#Engine hours and reporting exports.u   scan Â· cgminer/btminerzevery ~30 s per podÚfast)r.   ÚtÚlÚnÚflÚspzUDP broadcastzminer announces itselfÚslowzForeman scanzcompetes for connection slotsÚmedz
Modbus TCPz1 Hz live + 5 min blockÚModbusz~1/minÚsnapshotzon demand + dailyzclient listzfinds miner IPs fastz	Blink APIzfield-only pathÚmanageszPOST /listen_pod_datazper pod, per scanzagent snapshotz	every 5 szrelayed broadcastzlive + telemetryz1 Hz / 5 minzflow readingszjob poll + JPGzoutbound onlyzwatcher statez:5006 directzthe one raw-open ingest portz
HTTPS :443z&/api/gen_ingest, /api/flow, /api/blink)rJ   rh   rU   z/api/gen_ingest/z
/api/flow/z/api/agent/z
/api/blinkz	:5014 VPCzput/pull webhook + SSOzwrite miner_status.csvzpod summariesÚbulk_update_minerszlive gen fieldszkW, pressure, warningszregister blockzevery ~5 minÚreadingsz
live_gas_*zsnapshot cachezfiles on diskztriggers refreshÚfetch_inventoryzprovider pollz
every 60 sz
live statezcapacity vs loadzgen state + controlzarms EMS / AW flagszper podzsleep / wake batcheszone gen worth at a timez
gen alertszgen historyu   reset Â· start Â· AUTOzthree safety gateszgroup down?zdown causeszculprit exclusionz
WAN healthztunnel healthzflap detectionz
flap alertzpod connectivityÚ
conditionszsite weatherzinventory pullzconfig load + broadcastzread current statezevery 5 minzhashrate + powerÚ	snapshotszevery 30 min, rolled upzreboot zero-hashzhourly at :17zdaily gen mappingz00:05zdaily reportz06:00zpool earningszengine hours exportÚqueryzper-miner historyznotes + moveszservice recordszwho is asleepztargets sleep/wakezpowercontrol.logz#who did what, and how many answeredzsanitized configz	read-onlyzincremental snapshotszrowid cursorzsleep / wake / rebootu   cloud â†’ fieldz
DSE writesu   clear alarm Â· manual Â· startz:6060 commandzbtminer commandz#the only thing that touches a minerzModbus writezguarded, off by defaultzpod config + EMS flagszlauncher polls /api/statusÚ	WebSocketzlive, no refreshz/api/statuszactions + miner detailÚactionszgen control modalzpermission-gatedznotes + Mesa chatz
gen chartszhashrate historyzminer trendszservice costszscanner + watcher modalszand this pagezservice healthzscan resultszclient listszthumbnails + snapszsnapshot + RTSPz"cloud reaches the pod LAN directlyzauthenticated snapshotzcredentials stay server-sidez	RTSP pullzcontinuous while watchedu   MAC â†’ current IPzDHCP lease moves; MAC does notzproxied framesz"cached ~8 s, shared across viewerszMSE over WebSocketz
live videou   ONVIF move Â· presetzoperator driving the cameraztodo + assetsÚlookupszbulk read-only synczVPC onlyzcookie verify (SSO)zsaves configzthen broadcastsz	SMS / MMSzover cellularu"   signed webhook Â· ack Â· heartbeatzphone-initiated only (CGNAT)z/api/textbridge/z
poll Spacez
every 10 su   messages Â· groups Â· send jobszthreads + membershipzcookie-authedzpost inbound textzas textbridge@z/outbox job claimzphone polls; we never pushzgroup MMS / SMSzone thread, N recipientsÚalertszrouted per sitez	Mesa pushzTX / ND topicsu   notes Â· PMs Â· power requestszto the region's Space, TX or NDzpoll replieszevery 20 s, back into the feedu   gen feed Â· chat Â· PM planu#   notes Â· servicing Â· power requestza request, never an actionzservice modeu&   reg 42944 â€” locks out engine actionsz
Miner dataz#00ff00)r7   rM   Úcolorz#ffaa00ÚGasz#4a9effÚControlz#ff4444ÚCamerasz#aa44ffÚConfigz#44ffaaÚ	Reportingz#ff44aaÚAlertsz#ff6600z
Group textz#00e5ffÚNetworkz#888888Ú	Inventoryz#c0c0c0ÚOtherz#666666)r¼   rŠ   r£   r¤   r¥   r§   r©   r­   r‚   r’   r…   r¯   r³   r¶   r¸   r–   r—   r˜   ry   )r7   rM   ÚnodesÚNMT)r¾   rŠ   r£   r°   r¯   r§   r—   ry   )r¿   rŠ   r–   r¶   ry   )rÁ   r‡   rr   rS   ri   rw   ry   r±   r²   rj   rx   r¯   )rÄ   r¹   rŸ   ru   r^   r[   ry   )	rÅ   r©   rž   rŠ   r^   rÊ   r‚   r–   ry   ÚReports)rÀ   rŠ   r¥   r¤   r§   r«   r¸   r˜   rš   rž   ry   )Úlanesrë   ÚedgesrO   Úlensesr   u¤   <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NGON â€” Data Flow</title>
<style>
u‚  
  * { box-sizing: border-box; }
  body {
      margin: 0; background: #0a0a0a; color: #e0e0e0;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      overflow: hidden;
  }
  .header {
      background-color: rgba(255,255,255,0.05) !important;
      border-radius: 8px !important;
      padding: 14px 20px !important;
      margin: 12px !important;
      border: 1px solid rgba(255,255,255,0.1) !important;
      display: flex !important; justify-content: space-between !important;
      align-items: center !important; gap: 16px; flex-wrap: wrap;
  }
  .header h1 { font-size: 20px; margin: 0; }
  .hdr-right { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
  .kpi { font-size: 12px; color: #888; white-space: nowrap; }
  .kpi b { color: #00ff00; font-weight: 600; }
  .kpi.warn b { color: #ffaa00; }

  .toolbar {
      display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
      margin: 0 12px 8px; font-size: 12px;
  }
  .chip {
      background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
      color: #bbb; border-radius: 999px; padding: 4px 11px; cursor: pointer;
      user-select: none; transition: all .12s;
  }
  .chip:hover { background: rgba(255,255,255,0.12); color: #fff; }
  .chip.on { background: rgba(0,255,0,0.13); border-color: #00ff00; color: #eaffea; }
  .chip .dot {
      display: inline-block; width: 8px; height: 8px; border-radius: 50%;
      margin-right: 6px; vertical-align: middle;
  }
  .sep { width: 1px; height: 20px; background: rgba(255,255,255,0.12); margin: 0 4px; }
  .tb-label {
      color: #666; font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
      align-self: center;
  }
  .chip.lens.on { background: rgba(74,158,255,0.16); border-color: #4a9eff; color: #dbecff; }

  #stage {
      position: absolute; inset: 118px 0 0 0; overflow: hidden; cursor: grab;
  }
  #stage.drag { cursor: grabbing; }
  #canvas { position: absolute; transform-origin: 0 0; }
  #wires { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }

  .lane { position: absolute; top: 0; }
  .lane-head {
      font-size: 11px; text-transform: uppercase; letter-spacing: .09em;
      color: #666; padding-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.08);
      margin-bottom: 12px;
  }
  .lane-head span { display: block; text-transform: none; letter-spacing: 0; color: #444; font-size: 10px; }

  .node {
      position: absolute; border-radius: 8px; padding: 9px 11px; cursor: pointer;
      background: #141414; border: 1px solid rgba(255,255,255,0.14);
      transition: border-color .15s, box-shadow .15s, opacity .15s, background .15s;
  }
  .node:hover { border-color: rgba(255,255,255,0.45); background: #1b1b1b; }
  .node .n-label { font-size: 13px; font-weight: 600; color: #f0f0f0; }
  .node .n-sub { font-size: 10.5px; color: #7c7c7c; margin-top: 2px; }
  .node.sel { border-color: #fff; box-shadow: 0 0 0 1px #fff, 0 0 22px rgba(255,255,255,0.16); }
  .node.dim { opacity: .13; }
  .node.hl { border-color: #00ff00; box-shadow: 0 0 16px rgba(0,255,0,0.18); }

  .node.k-ext   { background: #10131a; border-color: rgba(120,160,255,0.35); }
  .node.k-hw    { background: #16120c; border-color: rgba(255,170,0,0.35); }
  .node.k-agent { background: #0c1610; border-color: rgba(0,255,0,0.28); }
  .node.k-net   { background: #111; border-color: rgba(255,255,255,0.3); border-style: dashed; }
  .node.k-api   { background: #0d1418; border-color: rgba(74,158,255,0.4); }
  .node.k-svc   { background: #17100f; border-color: rgba(255,68,68,0.35); }
  .node.k-cron  { background: #17100f; border-color: rgba(255,102,0,0.4); border-style: dashed; }
  .node.k-store { background: #14100f; border-color: rgba(255,68,170,0.32); }
  .node.k-page  { background: #0f0f14; border-color: rgba(170,68,255,0.38); }

  .svc-dot {
      position: absolute; top: 9px; right: 9px; width: 7px; height: 7px; border-radius: 50%;
      background: #333;
  }
  .svc-dot.up { background: #00ff00; box-shadow: 0 0 6px rgba(0,255,0,.7); }
  .svc-dot.down { background: #ff4444; box-shadow: 0 0 6px rgba(255,68,68,.8); }

  /* sites panel */
  .site-row { margin-top: 7px; }
  .site-name { font-size: 10.5px; color: #888; margin-bottom: 3px; }
  .pod-chips { display: flex; flex-wrap: wrap; gap: 3px; }
  .pod {
      font-size: 9.5px; padding: 2px 5px; border-radius: 4px; background: #1e1e1e;
      border: 1px solid rgba(255,255,255,0.09); color: #999; white-space: nowrap;
  }
  .pod.scanning {
      background: rgba(0,255,0,0.22); border-color: #00ff00; color: #d8ffd8;
      animation: pulse 1.1s ease-in-out infinite;
  }
  .pod.foreman { background: rgba(170,68,255,0.22); border-color: #aa44ff; color: #eddcff; }
  .pod.ems { background: rgba(74,158,255,0.22); border-color: #4a9eff; color: #dbecff; }
  .pod.offline { background: rgba(255,68,68,0.2); border-color: #ff4444; color: #ffdada; }
  @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }

  /* stroke, stroke-width and opacity are set per-edge as SVG attributes from
     dataflow.js â€” don't declare them here, CSS would win and flatten them. */
  .wire { fill: none; }
  .wire-label {
      font-size: 9px; fill: #777; pointer-events: none;
      paint-order: stroke; stroke: #0a0a0a; stroke-width: 3px;
  }

  #panel {
      position: absolute; right: 12px; top: 130px; width: 330px; max-height: calc(100vh - 160px);
      overflow-y: auto; background: rgba(17,17,17,0.97); border: 1px solid rgba(255,255,255,0.16);
      border-radius: 10px; padding: 16px; display: none; backdrop-filter: blur(4px);
      box-shadow: 0 10px 40px rgba(0,0,0,0.6);
  }
  #panel.open { display: block; }
  #panel h3 { margin: 0 0 2px; font-size: 15px; }
  #panel .p-sub { font-size: 11px; color: #4a9eff; margin-bottom: 10px; }
  #panel .p-desc { font-size: 12.5px; line-height: 1.55; color: #ccc; }
  #panel .p-file {
      font-size: 10.5px; color: #666; margin-top: 10px; word-break: break-all;
      font-family: ui-monospace, Menlo, monospace;
  }
  #panel h4 {
      font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; color: #666;
      margin: 14px 0 5px;
  }
  #panel .conn { font-size: 11.5px; color: #aaa; padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
  #panel .conn b { color: #ddd; font-weight: 600; }
  #panel .conn i { color: #666; font-style: normal; }
  #panel .close { position: absolute; right: 12px; top: 10px; color: #666; cursor: pointer; font-size: 18px; }
  #panel .close:hover { color: #fff; }
  #panel .live-box {
      margin-top: 12px; padding: 9px; border-radius: 6px; background: rgba(0,255,0,0.05);
      border: 1px solid rgba(0,255,0,0.18); font-size: 11.5px; color: #bfb;
  }
  .hint {
      position: absolute; left: 16px; bottom: 12px; font-size: 11px; color: #555;
      pointer-events: none;
  }
</style>
</head>
<body>
<div class="header">
  <div class="dropdown">
    <h1 class="dropdown-title">NGON â€” Data Flow</h1>
    <div class="dropdown-content">uü  </div>
  </div>
  <div class="hdr-right">
    <div class="kpi" id="kpiScan">scanner â€”</div>
    <div class="kpi" id="kpiMiners">miners â€”</div>
    <div class="kpi" id="kpiSvc">services â€”</div>
    <div class="kpi" id="kpiFeeds">feeds â€”</div>
  </div>
</div>

<div class="toolbar">
  <span class="chip on" id="btnLive">â—� Live</span>
  <span class="chip on" id="btnAnim">Animate</span>
  <span class="chip" id="btnFit">Fit</span>
  <div class="sep"></div>
  <span class="tb-label">Page lens</span>
  <span id="lensChips" style="display:flex;gap:6px;flex-wrap:wrap"></span>
  <div class="sep"></div>
  <span class="tb-label">Flows</span>
  <span id="flowChips" style="display:flex;gap:6px;flex-wrap:wrap"></span>
</div>

<div id="stage">
  <div id="canvas">
    <svg id="wires"></svg>
  </div>
</div>
<div id="panel"><span class="close" onclick="DF.closePanel()">&times;</span><div id="panelBody"></div></div>
<div class="hint">drag to pan Â· scroll to zoom Â· click a box for detail</div>

<script>window.TOPO = zD;</script>
<script src="/status/dataflow.js?v=4"></script>
<script>
z
</script>
</body>
</html>)#Ú__doc__Úcgir#   ÚsysÚpathÚinsertÚmanagers.auth_managerr   r   Úlinksr   r   r   r"   ÚFieldStorageÚformÚauthÚrequire_authÚauth_requiredÚshould_exitÚheadersÚprintÚexitÚget_user_accessÚ_user_accessr(   r5   ÚSITESÚLANESÚNODESÚEDGESÚFLOWSÚLENSESÚTOPOÚdumpsr   r   r   r   Ú<module>   s   

ÿ

#










õþþþøþóÿïþíþèþã$þÜ
)þ×-þÓ4þÌ8ÿÈ:þÆ@þÀEý»KýµPþ°Tþ¬Xþ¨\þ¤_þ¡fþšmþ“rþŽ
yý‡~þ‚ ý �û ý �õ 
þ �î ý �ë 
þ �å þ �á 
%ý �Û +
ý �Õ 1þ �Ï 4ÿ �Ì 6
ý �Ê =ÿ �Ã ?þ �Á Fý �º Ký �µ Pþ �° Sþ �­ Wþ �© 
Zÿ �¦ \
þ �¤ `þ �  eÿ �› gþ �™ mþ �“ qÿ �� sÿ �� uÿ �‹ wþ �‰ ÿ ��  
þ � �þ  ÿ � �ø  
ÿ � �ö  þ � �ó  þ � �ë  ÿ � �ä  þ � �â  !
þ � �ß  %þ � �Û  -þ � �Ó  3
þ � �Í  7
þ � �É  :þ � �Æ  =þ � �Ã  @ÿ � �À  Bÿ � �¾  Dþ � �¼  H
þ � �¸  Nþ � �²  Rþ � �®  Yÿ � �§  [
þ � �¥  
^ÿ � �¢  bÿþÿüÿúÿø
öõôóòÿïíìëêéèÿåÿã!ß"Þ#Ý$Ü%Û&Ú)×*Ö+Õ,Ô-Ó.Ò/Ñ0Ð3Í4Ì5Ë6Ê7É8È9Ç:Æ;Å<Ä=Ã>Â?Á@ÀA¿B¾C½D¼G¹H¸I·J¶Kµ L´M³N²Q¯R®S­T¬U«VªW©Xÿ¨Z¦[¥^¢_¡` aÿŸc�d
ÿœh˜i—j–k•l”m“n’o‘p�q�rŽs�tÿŒvŠw‰xˆy‡|ÿ„~ÿ‚  �ÿ ÿ �þ ÿ �ü  �ú 
ÿ �ù 	 �÷ 
 �ö  �õ  �ô  �ó  �ð ÿ �ï  �í  �ì ÿ �ë ÿ �é ÿ �ç ÿ �å ÿ �ã  ! �ß  " �Þ 
#ÿ �Ý %
ÿ �Û ' �Ù (ÿ �Ø *ÿ �Ö /ôÿÿÿÿÿÿÿìþù  �â < �Ä ? �Á