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.
This commit is contained in:
166
analyze.py
Executable file
166
analyze.py
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user