Skip to content

Commit bfb9d55

Browse files
committed
Add price comparisons tables
This is very very rough code to try and extract information about price deviations among participants. It may be better presented visually than it is as a table. The data should be there to do that but not the UI skills! Let's test this first and see how it goes.
1 parent e7e52d2 commit bfb9d55

File tree

4 files changed

+298
-52
lines changed

4 files changed

+298
-52
lines changed

htmx/index.htm

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,58 +33,68 @@
3333
<h1>ITN diagnostics</h1>
3434
<section id="license-holders">
3535
<div>
36-
<h2>License holders</h2>
37-
<div
38-
hx-get="{baseURL}/participants"
39-
hx-trigger="load"
40-
hx-swap="innerHTML"
41-
/>
42-
</div>
36+
<h2>License holders</h2>
37+
<div
38+
hx-get="{baseURL}/participants"
39+
hx-trigger="load"
40+
hx-swap="innerHTML"
41+
/>
42+
</div>
4343
</section>
4444
<hr>
4545
<section id="node-counts">
46-
<h2>Node counts</h2>
47-
<div
48-
hx-get="{baseURL}/count_active_participants"
49-
hx-trigger="load"
50-
hx-swap="innerHTML"
51-
/>
52-
</div>
53-
</section>
54-
<hr>
46+
<h2>Node counts</h2>
47+
<div
48+
hx-get="{baseURL}/count_active_participants"
49+
hx-trigger="load"
50+
hx-swap="innerHTML"
51+
/>
52+
</div>
53+
</section>
54+
<hr>
5555
<section id="collector-counts">
56-
<h2>Active collector counts</h2>
57-
<div
58-
hx-get="{baseURL}/online_collectors"
59-
hx-trigger="load, every 90s"
60-
hx-swap="innerHTML"
61-
/>
62-
</div>
56+
<h2>Active collector counts</h2>
57+
<div
58+
hx-get="{baseURL}/online_collectors"
59+
hx-trigger="load, every 90s"
60+
hx-swap="innerHTML"
61+
/>
62+
</div>
63+
</section>
64+
<hr>
65+
<section id="price comparisons">
66+
<h2>Price comparisons</h2>
67+
<div
68+
hx-get="{baseURL}/validator_price_stats_hx"
69+
hx-trigger="load, every 300s"
70+
hx-swap="innerHTML"
71+
/>
72+
</div>
6373
</section>
6474
<hr>
6575
<section id="locations map">
66-
<center><h2>Collector map</h2></center>
67-
<div
68-
hx-get="{baseURL}/locations_map"
69-
hx-trigger="load"
70-
hx-swap="innerHTML"
71-
/>
72-
</div>
73-
</section>
74-
<hr>
76+
<center><h2>Collector map</h2></center>
77+
<div
78+
hx-get="{baseURL}/locations_map"
79+
hx-trigger="load"
80+
hx-swap="innerHTML"
81+
/>
82+
</div>
83+
</section>
84+
<hr>
7585
<section id="locations">
76-
<h2>Collector locations</h2>
77-
<div
78-
hx-get="{baseURL}/locations"
79-
hx-trigger="load"
80-
hx-swap="innerHTML"
81-
/>
82-
</div>
86+
<h2>Collector locations</h2>
87+
<div
88+
hx-get="{baseURL}/locations"
89+
hx-trigger="load"
90+
hx-swap="innerHTML"
91+
/>
92+
</div>
8393
</section>
8494
<hr>
8595
<div class="footer__bottom text--center">
86-
<div class="footer__copyright">ITN | Diagnostics</div>
96+
<div class="footer__copyright">ITN | Diagnostics</div>
97+
</div>
8798
</div>
88-
</div>
8999
</body>
90100
</html>

src/itn_api/api.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,50 @@ async def get_locations():
220220
return await reports.get_locations(app)
221221

222222

223+
@app.get("/validator_price_stats", tags=[TAG_STATISTICS])
224+
async def validator_price_stats() -> dict:
225+
"""Count active participants."""
226+
cursor = app.state.connection.cursor()
227+
228+
try:
229+
cursor.execute(
230+
"""SELECT distinct feed_id
231+
from data_points
232+
where date_time >= (SELECT DATE_SUB(NOW(), INTERVAL 1 DAY));
233+
"""
234+
)
235+
except mariadb.Error:
236+
return "zero collectors online"
237+
feeds = list(cursor)
238+
try:
239+
cursor.execute(
240+
"""select address, feed_id, min(source_price), max(source_price)
241+
from data_points
242+
where date_time < date_sub(Now(), interval 1 hour)
243+
group by address, feed_id;
244+
"""
245+
)
246+
except mariadb.Error as err:
247+
return {"error": f"{err}"}
248+
hour_data = list(cursor)
249+
try:
250+
cursor.execute(
251+
"""select address, feed_id, min(source_price), max(source_price)
252+
from data_points
253+
where date_time < date_sub(Now(), interval 1 day)
254+
group by address, feed_id;
255+
"""
256+
)
257+
except mariadb.Error as err:
258+
return {"error": f"{err}"}
259+
day_data = list(cursor)
260+
261+
out = await reports._analyze_price_stats(feeds, hour_data, day_data)
262+
263+
cursor.close()
264+
return out
265+
266+
223267
# HTMX #################################################################
224268
# HTMX #################################################################
225269
# HTMX #################################################################
@@ -229,7 +273,7 @@ async def get_locations():
229273
async def get_itn_participants() -> str:
230274
"""Return ITN aliases and licenses."""
231275
all_holders = reports.get_all_license_holders(app, 0, None)
232-
htmx = htm_helpers.aliases_to_html(all_holders)
276+
htmx = await htm_helpers.aliases_to_html(all_holders)
233277
return htmx.strip()
234278

235279

@@ -295,7 +339,7 @@ async def get_online_collectors() -> str:
295339
participant_count_1h_feed_average[address] = 0
296340
participant_count_1m_feed_average[address] = 0
297341

298-
htmx = htm_helpers.participants_count_table(
342+
htmx = await htm_helpers.participants_count_table(
299343
participants_count_total,
300344
participants_count_24hr,
301345
participant_count_24h_feed_average,
@@ -309,14 +353,14 @@ async def get_online_collectors() -> str:
309353
async def get_locations_hx():
310354
"""Return countries participating in the ITN."""
311355
locations = await reports.get_locations_stake_key(app)
312-
return htm_helpers.locations_table(locations)
356+
return await htm_helpers.locations_table(locations)
313357

314358

315359
@app.get("/locations_map", tags=[TAG_HTMX], response_class=HTMLResponse)
316360
async def get_locations_map_hx():
317361
"""Return countries participating in the ITN."""
318362
locations = await reports.get_locations(app)
319-
return htm_helpers.locations_map(locations)
363+
return await htm_helpers.locations_map(locations)
320364

321365

322366
@app.get("/count_active_participants", tags=[TAG_HTMX], response_class=HTMLResponse)
@@ -332,6 +376,12 @@ async def count_active_participants():
332376
return f"{data[0][0]}"
333377

334378

379+
@app.get("/validator_price_stats_hx", tags=[TAG_HTMX], response_class=HTMLResponse)
380+
async def htmx_validator_price_stats() -> str:
381+
price_data = await validator_price_stats()
382+
return await htm_helpers.price_comparisons_section(price_data)
383+
384+
335385
def main():
336386
"""Primary entry point for this script."""
337387

src/itn_api/htm_helpers.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
logger = logging.getLogger(__name__)
99

1010

11-
def aliases_to_html(alias_report: dict) -> str:
11+
async def aliases_to_html(alias_report: dict) -> str:
1212
"""Take the alias report and convert it to HTML.
1313
1414
e.g.
@@ -65,7 +65,7 @@ def aliases_to_html(alias_report: dict) -> str:
6565
return f"{head}\n{rows}\n{count_row}</table>\n"
6666

6767

68-
def participants_count_table(
68+
async def participants_count_table(
6969
participants_count_total,
7070
participants_count_24hr,
7171
participant_count_24h_feed_average,
@@ -74,7 +74,7 @@ def participants_count_table(
7474
):
7575
"""Return a table with active participant counts."""
7676

77-
logging.info("formatting participants table")
77+
logging.info("formatting participants count table")
7878

7979
if not participants_count_total:
8080
return "zero collectors online"
@@ -113,10 +113,10 @@ def participants_count_table(
113113
return f"{head}\n{rows}</table>\n"
114114

115115

116-
def locations_table(locations):
116+
async def locations_table(locations):
117117
"""Create a table for participant locations."""
118118

119-
logging.info("formatting participants table")
119+
logging.info("formatting participants location table")
120120

121121
if not locations:
122122
return "no locations available"
@@ -157,7 +157,7 @@ def locations_table(locations):
157157
return f"{head}\n{rows}\n{country_count}</table>\n"
158158

159159

160-
def locations_map(locations):
160+
async def locations_map(locations):
161161
"""Create a map for participant locations."""
162162

163163
logging.info("formatting participants map")
@@ -183,6 +183,8 @@ def locations_map(locations):
183183

184184
collectors_map.get_root().html.add_child(folium.Element(collector_count_html))
185185

186+
if not locations:
187+
return "problem gathering collectors, please try again shortly"
186188
for locale in locations:
187189
region = locale["region"]
188190
country = locale["country"]
@@ -208,3 +210,64 @@ def locations_map(locations):
208210
)
209211

210212
return collectors_map_html
213+
214+
215+
async def price_comparisons_section(price_data: dict) -> str:
216+
"""Output the ITN price comparison table so that we can start
217+
to understand deviation better.
218+
"""
219+
220+
feeds = [item for item in price_data.keys()]
221+
222+
htm = ""
223+
224+
threshold = 1
225+
226+
for feed in feeds:
227+
htm = f"{htm}<div><h3>{feed}</h3>\n"
228+
summary = "<h4>summary</h4>\n"
229+
htm = f"{htm}{summary}\n"
230+
231+
o1 = f"min values in last hour greater than {threshold}%: {price_data[feed]["breached_hourly_min"]} | range diff (%): {price_data[feed]["min_min_hourly_diff"]}"
232+
o2 = f"max values in last hour greater than {threshold}%: {price_data[feed]["breached_hourly_max"]} | range diff (%): {price_data[feed]["max_max_hourly_diff"]}"
233+
o3 = f"min values in last day greater than {threshold}%: {price_data[feed]["breached_daily_min"]} | range diff (%): {price_data[feed]["min_min_daily_diff"]}"
234+
o4 = f"max values in last day greater than {threshold}%: {price_data[feed]["breached_daily_max"]} | range diff (%): {price_data[feed]["max_max_daily_diff"]}"
235+
236+
htm = f"{htm}\n<pre>{o1}\n{o2}\n{o3}\n{o4}</pre>\n</div>\n"
237+
238+
for feed in feeds:
239+
htm = f"{htm}<div>\n"
240+
summary = f"<h4>detailed: {feed}</h4>\n"
241+
htm = f"{htm}{summary}\n"
242+
htm = f"{htm}<table><th>price (min hour)</th><th>key</th>\n"
243+
for item in price_data[feed]["min_price_hour"]:
244+
price = item[0]
245+
key = item[1]
246+
tr = f"<tr><td>{price:1.7f}</td><td>{key}</td></tr>"
247+
htm = f"{htm}{tr}\n"
248+
htm = f"{htm}</table>\n<br>\n"
249+
htm = f"{htm}<table><th>prices (max hour)</th><th>key</th>\n"
250+
for item in price_data[feed]["max_price_hour"]:
251+
price = item[0]
252+
key = item[1]
253+
tr = f"<tr><td>{price:1.7f}</td><td>{key}</td></tr>"
254+
htm = f"{htm}{tr}\n"
255+
htm = f"{htm}</table>\n<br>\n"
256+
"""
257+
htm = f"{htm}<table><th>prices (min day)</th><th>key</th>\n"
258+
for item in price_data[feed]["min_price_day"]:
259+
price = item[0]
260+
key = item[1]
261+
tr = f"<tr><td>{price:1.7f}</td><td>{key}</td></tr>"
262+
htm = f"{htm}{tr}\n"
263+
htm = f"{htm}</table>\n<br>\n"
264+
htm = f"{htm}<table><th>prices (max day)</th><th>key</th>\n"
265+
for item in price_data[feed]["max_price_day"]:
266+
price = item[0]
267+
key = item[1]
268+
tr = f"<tr><td>{price:1.7f}</td><td>{key}</td></tr>"
269+
htm = f"{htm}{tr}\n"
270+
htm = f"{htm}</table>\n<br>\n"
271+
"""
272+
273+
return htm

0 commit comments

Comments
 (0)