File size: 5,965 Bytes
78738de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# -*- coding: utf-8 -*-
"""BG30 Bước 1.1 — bảng đối chiếu quy ước φ trên 5 điểm dữ liệu thật.

Câu hỏi (BRIEF 30): Δφ model↔analytic chụm ~140–149° trên clip thật có phải
lỗi quy ước/mirror không? So φ model với 4 phép biến đổi của φ analytic:
    {φ, −φ mod 360, φ+180, 180−φ}
(−φ = lật trục x; 180−φ = lật trục y; φ+180 = xoay 180° — xoay 180° là
nhập nhằng VÔ HẠI duy nhất của quy ước chấm góc, homography.py:48-49).

5 điểm dữ liệu — KHÔNG đo mới, chỉ đọc số đã công bố:
  - 2 job BG28 (model c3): đọc từ JSON đã lưu ở
    ``poolcoach-docs\\results\\2026-08-12_shotnet_p2\\cu1?_app_bg28.json``
    (chỉ ĐỌC — file thuộc repo docs, không sửa).
  - 3 job smoke BG29b (model c4b): Redis hết TTL, số chép NGUYÊN VĂN từ
    HANDOFF 13/08/2026 10:25 (bảng "Số 2 cột") — đúng nguồn BRIEF chỉ định.

Chạy (venv app, không cần GPU/torch):
    python scripts/broadcast/diag_phi_conventions.py
Artifact: ``D:\\Khoa luan\\bb9_diag30_phi_conventions.csv`` (mới, không đè
gì) — mỗi dòng một (điểm × phép biến đổi), kèm commit hash lúc chạy.

Caveat đọc kết quả (ghi sẵn để khỏi đọc quá tay): φ analytic của cả 5 điểm
chỉ trải 258.0–261.6° (3.6°) — bảng này thực chất chỉ thử các phép biến đổi
tại MỘT hướng đánh, nên "phép nào khớp nhất" không tự nó phân biệt được
mirror với một offset cố định ~±140°; phải đọc KÈM audit code (Bước 1.2).
"""

from __future__ import annotations

import csv
import json
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
WORKSPACE = ROOT.parent
BG28_DIR = WORKSPACE / "poolcoach-docs" / "results" / "2026-08-12_shotnet_p2"
OUT_CSV = WORKSPACE / "bb9_diag30_phi_conventions.csv"

# 3 job smoke BG29b — nguyên văn HANDOFF 13/08/2026 10:25 (model c4b,
# commit lúc smoke a65787a); Redis TTL đã hết nên đây là nguồn duy nhất.
BG29B_POINTS = [
    {"diem": "cu11_lan1_bg29b", "model": "c4b", "phi_model": 109.3,
     "phi_analytic": 258.3},
    {"diem": "cu11_lan2_bg29b", "model": "c4b", "phi_model": 110.7,
     "phi_analytic": 258.0},
    {"diem": "cu12_bg29b", "model": "c4b", "phi_model": 121.4,
     "phi_analytic": 261.6},
]

TRANSFORMS = [
    ("phi", lambda p: p % 360.0),
    ("neg_phi", lambda p: (-p) % 360.0),
    ("phi_plus_180", lambda p: (p + 180.0) % 360.0),
    ("180_minus_phi", lambda p: (180.0 - p) % 360.0),
]


def circ_diff_signed(a: float, b: float) -> float:
    """a − b về (−180, 180] — dấu để thấy phần dư lệch về phía nào."""
    return ((a - b + 180.0) % 360.0) - 180.0


def load_bg28_points() -> list[dict]:
    pts = []
    for cu in (11, 12):
        p = BG28_DIR / f"cu{cu}_app_bg28.json"
        r = json.loads(p.read_text(encoding="utf-8"))["result"]
        pts.append({"diem": f"cu{cu}_bg28",
                    "model": "c3",
                    "phi_model": float(r["shotnet"]["phi_deg"]),
                    "phi_analytic": float(r["metrics"]["phi_deg"])})
    return pts


def git_head() -> str:
    try:
        return subprocess.run(["git", "-C", str(ROOT), "rev-parse",
                               "--short", "HEAD"], capture_output=True,
                              text=True, check=True).stdout.strip()
    except Exception:
        return "unknown"


def main() -> int:
    points = load_bg28_points() + BG29B_POINTS
    commit = git_head()

    rows = []
    for pt in points:
        for name, fn in TRANSFORMS:
            tv = fn(pt["phi_analytic"])
            resid = circ_diff_signed(pt["phi_model"], tv)
            rows.append({"diem": pt["diem"], "model": pt["model"],
                         "phi_model": pt["phi_model"],
                         "phi_analytic": pt["phi_analytic"],
                         "phep": name,
                         "phi_analytic_bien_doi": round(tv, 1),
                         "du_deg": round(resid, 1),
                         "abs_du_deg": round(abs(resid), 1),
                         "commit_luc_chay": commit})

    if OUT_CSV.exists():
        print(f"BO QUA ghi CSV: {OUT_CSV} da ton tai (luat khong de "
              f"artifact cu) -- xoa tay neu muon sinh lai.")
    else:
        with OUT_CSV.open("w", newline="", encoding="utf-8") as f:
            wr = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
            wr.writeheader()
            wr.writerows(rows)
        print(f"da ghi {OUT_CSV} ({len(rows)} dong, commit {commit})")

    # ----- bảng tóm tắt ra console (ASCII — bẫy cp1252)
    print(f"\n{'diem':<18}{'model':<7}{'phi_mod':>8}{'phi_ana':>8}"
          + "".join(f"{n:>15}" for n, _ in TRANSFORMS))
    for pt in points:
        cells = ""
        for name, fn in TRANSFORMS:
            resid = circ_diff_signed(pt["phi_model"], fn(pt["phi_analytic"]))
            cells += f"{resid:>+15.1f}"
        print(f"{pt['diem']:<18}{pt['model']:<7}{pt['phi_model']:>8.1f}"
              f"{pt['phi_analytic']:>8.1f}{cells}")

    print("\nmedian |du| theo phep (5 diem / rieng c4b):")
    for name, fn in TRANSFORMS:
        alls = sorted(abs(circ_diff_signed(pt["phi_model"],
                                           fn(pt["phi_analytic"])))
                      for pt in points)
        c4b = sorted(abs(circ_diff_signed(pt["phi_model"],
                                          fn(pt["phi_analytic"])))
                     for pt in points if pt["model"] == "c4b")
        print(f"  {name:<15} all={alls[len(alls) // 2]:>6.1f}   "
              f"c4b={c4b[len(c4b) // 2]:>6.1f}")
    print("\nCAVEAT: phi analytic 5 diem chi trai 258.0-261.6 (3.6 deg) — "
          "bang chi thu duoc MOT huong danh; doc kem audit code Buoc 1.2.")
    return 0


if __name__ == "__main__":
    sys.exit(main())