Add hop width to channel info. Closes #153.

This commit is contained in:
Jack Kingsman
2026-04-03 13:57:04 -07:00
parent be2b2604df
commit 55081d4a2d
6 changed files with 166 additions and 4 deletions
+13
View File
@@ -355,6 +355,18 @@ class ChannelTopSender(BaseModel):
message_count: int
class PathHashWidthStats(BaseModel):
"""Hop byte width distribution for parsed raw packets."""
total_packets: int = 0
single_byte: int = 0
double_byte: int = 0
triple_byte: int = 0
single_byte_pct: float = 0.0
double_byte_pct: float = 0.0
triple_byte_pct: float = 0.0
class ChannelDetail(BaseModel):
"""Comprehensive channel profile data."""
@@ -363,6 +375,7 @@ class ChannelDetail(BaseModel):
first_message_at: int | None = None
unique_sender_count: int = 0
top_senders_24h: list[ChannelTopSender] = Field(default_factory=list)
path_hash_width_24h: PathHashWidthStats = Field(default_factory=PathHashWidthStats)
class MessagePath(BaseModel):
+44 -2
View File
@@ -786,12 +786,14 @@ class MessageRepository:
@staticmethod
async def get_channel_stats(conversation_key: str) -> dict:
"""Get channel message statistics: time-windowed counts, first message, unique senders, top senders.
"""Get channel message statistics: time-windowed counts, first message, unique senders, top senders, path hash widths.
Returns a dict with message_counts, first_message_at, unique_sender_count, top_senders_24h.
Returns a dict with message_counts, first_message_at, unique_sender_count, top_senders_24h, path_hash_width_24h.
"""
import time as _time
from app.path_utils import parse_packet_envelope
now = int(_time.time())
t_1h = now - 3600
t_24h = now - 86400
@@ -843,11 +845,51 @@ class MessageRepository:
for r in top_rows
]
# Path hash width distribution for last 24h (in-Python parse of raw packet envelopes)
single_byte = 0
double_byte = 0
triple_byte = 0
cursor3 = await db.conn.execute(
"""
SELECT rp.data FROM raw_packets rp
JOIN messages m ON rp.message_id = m.id
WHERE m.type = 'CHAN' AND m.conversation_key = ?
AND rp.timestamp >= ?
""",
(conversation_key, t_24h),
)
while True:
batch = await cursor3.fetchmany(500)
if not batch:
break
for pkt_row in batch:
envelope = parse_packet_envelope(bytes(pkt_row["data"]))
if envelope is None:
continue
if envelope.hash_size == 1:
single_byte += 1
elif envelope.hash_size == 2:
double_byte += 1
elif envelope.hash_size == 3:
triple_byte += 1
hash_total = single_byte + double_byte + triple_byte
path_hash_width_24h = {
"total_packets": hash_total,
"single_byte": single_byte,
"double_byte": double_byte,
"triple_byte": triple_byte,
"single_byte_pct": (single_byte / hash_total * 100) if hash_total else 0.0,
"double_byte_pct": (double_byte / hash_total * 100) if hash_total else 0.0,
"triple_byte_pct": (triple_byte / hash_total * 100) if hash_total else 0.0,
}
return {
"message_counts": message_counts,
"first_message_at": row["first_message_at"],
"unique_sender_count": row["unique_sender_count"] or 0,
"top_senders_24h": top_senders,
"path_hash_width_24h": path_hash_width_24h,
}
@staticmethod
+1
View File
@@ -215,6 +215,7 @@ async def get_channel_detail(key: str) -> ChannelDetail:
first_message_at=stats["first_message_at"],
unique_sender_count=stats["unique_sender_count"],
top_senders_24h=[ChannelTopSender(**s) for s in stats["top_senders_24h"]],
path_hash_width_24h=stats["path_hash_width_24h"],
)