Files
3pmonitor/analyze.py
Junaid Saeed Uppal 2b8e3dd456 add pattern analysis feature with web dashboard and CLI
New /patterns page with 9 analyses: chair win bias, bet rank
correlations, hand type distributions, pot size buckets, streaks,
hourly patterns, and recent-vs-overall comparison. Also adds a
standalone analyze.py CLI script for terminal output.
2026-02-25 22:45:43 +05:00

167 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Standalone CLI script for Teen Patti pattern analysis.
Usage:
python analyze.py --host localhost --port 8123
"""
import argparse
import sys
def fmt_pct(n, total):
return f"{n/total*100:.1f}%" if total else "0.0%"
def print_table(headers, rows, col_widths=None):
"""Print a simple formatted table."""
if not col_widths:
col_widths = [max(len(str(h)), *(len(str(r[i])) for r in rows))
for i, h in enumerate(headers)]
# Header
hdr = " ".join(str(h).ljust(w) for h, w in zip(headers, col_widths))
print(hdr)
print("-" * len(hdr))
for row in rows:
print(" ".join(str(c).ljust(w) for c, w in zip(row, col_widths)))
def main():
parser = argparse.ArgumentParser(description="Teen Patti Pattern Analysis CLI")
parser.add_argument("--host", default="localhost", help="ClickHouse host")
parser.add_argument("--port", type=int, default=8123, help="ClickHouse HTTP port")
args = parser.parse_args()
# Set config before importing db
from app import config
config.CLICKHOUSE_HOST = args.host
config.CLICKHOUSE_PORT = args.port
from app import db
print(f"Connecting to ClickHouse at {args.host}:{args.port}...")
try:
data = db.get_pattern_analysis()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
total = data["chair_bias"]["total_games"]
print(f"\n{'='*60}")
print(f" TEEN PATTI PATTERN ANALYSIS ({total:,} games)")
print(f"{'='*60}\n")
# 1. Chair Win Bias
print("1. CHAIR WIN BIAS (expected 33.3%)")
cb = data["chair_bias"]
rows = []
for ch in ("A", "B", "C"):
d = cb[ch]
diff = d["pct"] - 33.3
sign = "+" if diff >= 0 else ""
rows.append([ch, f"{d['wins']:,}", f"{d['pct']:.1f}%", f"{sign}{diff:.1f}%"])
print_table(["Chair", "Wins", "Win %", "vs Expected"], rows)
# 2. Bet Rank Analysis
print("\n2. BET RANK ANALYSIS")
br = data["bet_rank"]
br_total = br["high"] + br["mid"] + br["low"]
rows = []
for rank in ("high", "mid", "low"):
rows.append([rank.capitalize(), f"{br[rank]:,}", fmt_pct(br[rank], br_total)])
print_table(["Rank", "Wins", "Win %"], rows)
# 3. Per-Chair Bet Rank
print("\n3. PER-CHAIR: HIGHEST BET WIN RATE")
print(" When chair X has the highest bet, how often does X win?")
pcr = data["per_chair_rank"]
rows = []
for ch in ("A", "B", "C"):
d = pcr.get(ch, {})
rows.append([ch, f"{d.get('has_highest', 0):,}",
f"{d.get('wins', 0):,}", f"{d.get('win_pct', 0):.1f}%"])
print_table(["Chair", "Times Highest", "Wins", "Win %"], rows)
# 4. Hand Type Distribution by Chair
print("\n4. HAND TYPE DISTRIBUTION BY CHAIR")
htbc = data["hand_types_by_chair"]
hand_order = ["Trail", "Straight Flush", "Straight", "Flush", "Pair", "High Card"]
rows = []
for ht in hand_order:
a = htbc["A"].get(ht, 0)
b = htbc["B"].get(ht, 0)
c = htbc["C"].get(ht, 0)
if a + b + c == 0:
continue
rows.append([ht, f"{a:,}", f"{b:,}", f"{c:,}"])
print_table(["Hand Type", "Chair A", "Chair B", "Chair C"], rows)
# 5. Hand Type Win Rates
print("\n5. HAND TYPE WIN RATES")
htw = data["hand_type_wins"]
htw_total = sum(htw.values())
rows = []
for ht in hand_order:
v = htw.get(ht, 0)
if v == 0:
continue
rows.append([ht, f"{v:,}", fmt_pct(v, htw_total)])
print_table(["Hand Type", "Wins", "Win %"], rows)
# 6. Pot Size Buckets
print("\n6. WIN RATES BY POT SIZE")
pb = data["pot_buckets"]
ranges = pb.get("_ranges", {})
rows = []
for bucket in ("small", "medium", "large", "whale"):
d = pb.get(bucket)
if not d:
continue
t = d["total"] or 1
rows.append([
bucket.capitalize(), ranges.get(bucket, ""),
f"{d['total']:,}",
f"{d['A']/t*100:.1f}%", f"{d['B']/t*100:.1f}%", f"{d['C']/t*100:.1f}%",
])
print_table(["Bucket", "Range", "Games", "A %", "B %", "C %"], rows)
# 7. Streaks
print("\n7. STREAK ANALYSIS")
streaks = data["streaks"]
rows = []
for ch in ("A", "B", "C"):
s = streaks[ch]
rows.append([ch, str(s["max_streak"]), str(s["current_streak"])])
print_table(["Chair", "Max Streak", "Current Streak"], rows)
# 8. Hourly Patterns
print("\n8. HOURLY PATTERNS (win % by hour)")
hourly = data["hourly"]
hours = sorted(hourly.keys(), key=lambda h: int(h))
rows = []
for h in hours:
d = hourly[h]
t = d["total"] or 1
rows.append([
f"{int(h):02d}:00", str(d["total"]),
f"{d['A']/t*100:.1f}%", f"{d['B']/t*100:.1f}%", f"{d['C']/t*100:.1f}%",
])
print_table(["Hour", "Games", "A %", "B %", "C %"], rows)
# 9. Recent vs Overall
print("\n9. RECENT (LAST 100) vs ALL-TIME")
rva = data["recent_vs_all"]
for label, section in [("All-Time", rva["all"]), ("Last 100", rva["recent"])]:
t = section["total"] or 1
d = section["dist"]
parts = " | ".join(f"{ch}: {d[ch]:>4} ({d[ch]/t*100:.1f}%)" for ch in ("A", "B", "C"))
print(f" {label:>10} [{t:>5} games] {parts}")
print(f"\n{'='*60}")
print(" Done.")
if __name__ == "__main__":
main()