# -*- coding: utf-8 -*-
"""VIET.PY — mô hình VIẾT bản tin từ số liệu code đã tính (phan_tich / lich_tin / ket_qua_bot). Không tự tính số.

Một lần gọi = một bản tin đủ 4 khổ: Fanpage (fb), Telegram chủ đề Kênh tin tức (tele), Zalo nhóm (zalo), website
(web: tiêu đề + mô tả + bài). Số liệu đưa vào đã định dạng kiểu Việt Nam để mô hình chép nguyên, không tự làm tròn.
"""
from __future__ import annotations

import json
import re
from typing import Any, Callable, Dict, List, Optional

LIEN_KET = 'https://tradingauto.org'
_CAM = re.compile(r'passview|chắc chắn (?:lãi|thắng|tăng|giảm)|cam kết lợi nhuận|nên mua ngay|nên bán ngay|'
                  r'không bao giờ thua|x2 tài khoản', re.I)


def so(x: Optional[float], le: int = 2, dau: bool = False) -> str:
    if x is None:
        return '—'
    s = f'{abs(x):,.{le}f}'.replace(',', '§').replace('.', ',').replace('§', '.')
    return ('+' if x > 0 and dau else '-' if x < 0 else '') + s


# ---------------------------------------------------------------------
# DỮ KIỆN ĐƯA CHO MÔ HÌNH
# ---------------------------------------------------------------------

def _vung(v: Dict[str, Any]) -> str:
    return so(v['duoi']) if v['duoi'] == v['tren'] else f"{so(v['duoi'])}–{so(v['tren'])}"


def _dien_giai_dong_luong(dl: Dict[str, Any]) -> Dict[str, str]:
    """Nghĩa của chỉ báo bằng lời — CODE quyết định, mô hình chỉ chép (bản lời thường không nêu tên chỉ báo)."""
    adx = dl['adx_d1']
    luc = 'yếu (chưa thành xu hướng)' if adx < 20 else ('vừa' if adx < 25 else 'mạnh')
    luc += ', đang tăng dần' if adx > dl['adx_3_phien_truoc'] + 1 else (', đang yếu đi' if adx < dl['adx_3_phien_truoc'] - 1 else '')
    phe = 'bên bán' if dl['mdi'] > dl['pdi'] else 'bên mua'

    def _rsi(x):
        if x is None:
            return None
        return 'quá bán' if x < 30 else ('quá mua' if x > 70 else ('nghiêng yếu' if x < 45 else ('nghiêng mạnh' if x > 55 else 'trung tính')))
    h, ht = dl['macd_hist_d1'], dl['macd_hist_truoc']
    dong = None
    if h is not None and ht is not None:
        if h < 0:
            dong = 'đà giảm đang chậm lại' if h > ht else 'đà giảm đang mạnh thêm'
        else:
            dong = 'đà tăng đang mạnh thêm' if h > ht else 'đà tăng đang chậm lại'
    ra = {'luc_xu_huong_ngay': luc, 'phe_chiem_uu_the': phe, 'trang_thai_ngay': _rsi(dl['rsi_d1']),
          'trang_thai_4_gio': _rsi(dl['rsi_h4']), 'da_ngay': dong}
    if dl.get('phan_ky_h4'):
        ra['tin_hieu_dao_chieu_som_khung_4_gio'] = dl['phan_ky_h4']
    return {k: v for k, v in ra.items() if v}


def _phan_ung(p: Optional[Dict[str, Any]]) -> Optional[str]:
    if not p:
        return None
    c = (f"{p['so_lan']} lần ra tin gần nhất: trong 2 giờ sau tin vàng chạy trung vị {so(p['trung_vi_bien_do_2h'])} giá, "
         f"lớn nhất {so(p['lon_nhat_2h'])} giá")
    # chỉ nói hướng khi lệch rõ (>= 70% một phía, >= 5 lần) — ít mẫu thì không kết luận
    for k, ten, ve in (('khi_cao_hon_du_bao', 'cao hơn', 'vang_giam'), ('khi_thap_hon_du_bao', 'thấp hơn', 'vang_tang')):
        x = p.get(k)
        if not x or x['so_lan'] < 5:
            continue
        ti_le = x[ve] / x['so_lan']
        if ti_le >= .7:
            c += f"; khi số ra {ten} dự báo, vàng {'giảm' if ve == 'vang_giam' else 'tăng'} {x[ve]}/{x['so_lan']} lần"
        elif ti_le <= .3:
            c += f"; khi số ra {ten} dự báo, vàng {'tăng' if ve == 'vang_giam' else 'giảm'} {x['so_lan'] - x[ve]}/{x['so_lan']} lần"
    return c


_TEN_KB = {'tang': 'tăng', 'giam': 'giảm', 'di_ngang': 'đi ngang'}


def du_kien_boi_canh(bc: Dict[str, Any]) -> Dict[str, Any]:
    ra: Dict[str, Any] = {'ghi_chu_bat_buoc': 'thiên hướng là bối cảnh để đặt mức vô hiệu, không phải dự báo giá'}
    for k, ten in (('thang', 'thien_huong_thang'), ('tuan', 'thien_huong_tuan')):
        x = bc.get(k)
        if not x:
            continue
        ra[ten] = {'huong': x['huong'], 'vi_sao': f"khung {'tuần' if k == 'thang' else 'ngày'} {x['xu_huong_khung_duoi'].lower()} "
                                                   f"({x['cau_truc']}), nến {'tháng' if k == 'thang' else 'tuần'} trước {x['nen_ky_truoc']}",
                   'ky_truoc': f"đỉnh {so(x['ky_truoc']['cao'])}, đáy {so(x['ky_truoc']['thap'])}, đóng {so(x['ky_truoc']['dong'])}",
                   'vi_tri_gia_hien_tai': x['vi_tri_gia'], 'vo_hieu': x['vo_hieu']}
    if bc.get('nam'):
        n = bc['nam']
        ra['vi_tri_trong_52_tuan'] = f"đỉnh {so(n['dinh_52_tuan'])}, đáy {so(n['day_52_tuan'])}, giá ở {n['vi_tri_pct']}% từ đáy lên"
    return ra


def _tan_suat(ts: Optional[Dict[str, Any]]) -> Optional[str]:
    if not ts or not ts.get('so_phien'):
        return None
    return (f"{ts['so_phien']} phiên gần nhất (cùng cách đặt kịch bản): không chạm mục tiêu nào {ts['di_ngang']}%, "
            f"chạm mục tiêu tăng trước {ts['tang']}%, chạm mục tiêu giảm trước {ts['giam']}%")


def du_kien_a(sl: Dict[str, Any], chi_so: bool, he_thong: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Dữ kiện 11 phần, MỌI SỐ đã định dạng. Khoá p2…p11 theo thứ tự phần trong bài web (p1 tóm tắt mô hình tự viết)."""
    hq, tt = sl['hom_qua'], sl.get('trang_thai') or {}
    bd, kb, lich = sl['bien_dong'], sl['kich_ban'], sl['lich']
    d: Dict[str, Any] = {
        'ngay_ban_tin': sl['ngay_ban_tin'], 'thu': ['Thứ Hai', 'Thứ Ba', 'Thứ Tư', 'Thứ Năm', 'Thứ Sáu', 'Thứ Bảy',
                                                    'Chủ nhật'][sl['thu']],
        'kieu': 'mo_cua_tuan' if sl['mo_cua_tuan'] else 'ngay',
        'phien_truoc': {'ngay': sl['ngay_nen'], 'mo': so(hq['mo']), 'cao': so(hq['cao']), 'thap': so(hq['thap']),
                        'dong': so(hq['dong']), 'thay_doi': so(hq['thay_doi'], dau=True),
                        'thay_doi_pct': so(hq['thay_doi_pct'], dau=True) + '%', 'bien_do': so(hq['bien_do']),
                        'bien_do_so_trung_binh_14_ngay': so(hq['bien_do_so_tb'], 1) + ' lần'},
        'gia_sang_nay': so(sl['gia_sang_nay']), 'gio_gia_sang_nay': sl['gio_gia'],
    }
    if sl['mo_cua_tuan']:
        tv = sl.get('tuan_truoc') or {}
        if tv:
            d['tuan_vua_qua'] = {k: so(v) for k, v in tv.items()}
            d['tuan_vua_qua']['thay_doi_pct'] = so((tv['dong'] - tv['mo']) / tv['mo'] * 100, dau=True) + '%'
        if sl.get('gap') is not None:
            d['khoang_trong_mo_cua_tuan'] = so(sl['gap'], dau=True)
    d['p02_boi_canh_thang_tuan'] = du_kien_boi_canh(sl['boi_canh'])
    ten_khung = {'D1': 'Ngày (D1)', 'H4': '4 giờ (H4)', 'H1': '1 giờ (H1)'}
    d['p03_da_khung'] = []
    for k in sl['da_khung']:
        x = {'khung': ten_khung[k['khung']], 'xu_huong': k['nhan'], 'cau_truc_dinh_day': k['cau_truc'],
             'gia_so_voi_duong_trung_binh_50': None if k['tren_ema50'] is None else ('trên' if k['tren_ema50'] else 'dưới'),
             'gia_so_voi_duong_trung_binh_200': None if k['tren_ema200'] is None else ('trên' if k['tren_ema200'] else 'dưới')}
        if chi_so:
            x['EMA50'], x['EMA200'] = so(k['ema50']), so(k['ema200'])
        d['p03_da_khung'].append(x)
    d['p04_vung_gia'] = {
        'ghi_chu_bat_buoc': 'chỉ để tham khảo, không phải khuyến nghị mua bán',
        'khang_cu_tu_gan_den_xa': [{'vung': _vung(v), 'do_manh': v['do_manh'], 'gom_cac_muc': v['gom']}
                                   for v in sl['vung_gia']['khang_cu']],
        'ho_tro_tu_gan_den_xa': [{'vung': _vung(v), 'do_manh': v['do_manh'], 'gom_cac_muc': v['gom']}
                                 for v in sl['vung_gia']['ho_tro']]}
    pv = bd['atr_bach_phan_vi_3_thang']
    d['p05_bien_dong'] = {'bien_do_trung_binh_mot_ngay': so(bd['atr14']),
                         'so_voi_3_thang': ('thấp' if pv < 30 else ('cao' if pv > 70 else 'trung bình')) + f' (cao hơn {pv}% số ngày)',
                         'trung_vi_3_thang': so(bd['atr_trung_vi_3_thang'])}
    if 'tran_du_kien' in bd:
        d['p05_bien_dong'].update({
            'tu_dau_ngay_da_chay': f"{so(bd['da_chay'])} giá ({bd['da_chay_pct_atr']}% biên độ trung bình)",
            'neu_hom_nay_chay_du_bien_do_trung_binh': f"trần quanh {so(bd['tran_du_kien'])}, sàn quanh {so(bd['san_du_kien'])}"})
    dl = sl['dong_luong']
    d['p06_dong_luong'] = _dien_giai_dong_luong(dl)
    if chi_so:
        d['p06_dong_luong']['chi_so'] = {'ADX D1': so(dl['adx_d1'], 1), '+DI / −DI': f"{so(dl['pdi'], 1)} / {so(dl['mdi'], 1)}",
                                        'RSI 14 D1': so(dl['rsi_d1'], 1), 'RSI 14 H4': so(dl['rsi_h4'], 1),
                                        'MACD histogram D1': so(dl['macd_hist_d1'])}
    lt = sl['lien_thi_truong']
    p6: Dict[str, Any] = {}
    if lt.get('usdx'):
        u = lt['usdx']
        p6['chi_so_usd_USDX'] = {'phien': u['ngay'], 'dong': so(u['dong'], 3), 'doi_1_phien': so(u['doi_1_phien_pct'], dau=True) + '%',
                                 'doi_5_phien': so(u['doi_5_phien_pct'], dau=True) + '%', 'xu_huong_ngay': u['xu_huong_d1']}
    tq = lt.get('tuong_quan_vang_usdx_20_phien')
    if tq is not None:
        p6['tuong_quan_vang_va_usd_20_phien'] = so(tq) + ' — ' + ('ngược chiều rõ' if tq <= -.5 else (
            'ngược chiều nhẹ' if tq <= -.2 else ('cùng chiều' if tq >= .2 else 'gần như không liên quan')))
    if lt.get('loi_suat_my_10_nam'):
        y = lt['loi_suat_my_10_nam']
        p6['loi_suat_trai_phieu_my_10_nam'] = {'phien': y['ngay'], 'muc': so(y['muc_pct']) + '%',
                                               'doi_1_phien': so(y['doi_1_phien_diem_co_ban'], 0, dau=True) + ' điểm cơ bản',
                                               'doi_5_phien': so(y['doi_5_phien_diem_co_ban'], 0, dau=True) + ' điểm cơ bản'}
    p6['ghi_chu'] = 'vàng thường đi ngược USD và lợi suất trái phiếu Mỹ: hai thứ này tăng thường gây sức ép lên vàng'
    d['p07_lien_thi_truong'] = p6
    d['p08_lich_tin_va_su_kien'] = {'pham_vi': 'cả tuần' if lich['pham_vi'] == 'tuan' else 'từ 7:00 hôm nay tới 7:00 mai (giờ VN)',
                        'tin': [{'luc': m['gio'] + (' ' + m['ngay'] if lich['pham_vi'] == 'tuan' else ''),
                                 'muc_do': 'rất mạnh' if m['hang'] == 1 else 'mạnh',
                                 'cac_tin': [{k: v for k, v in t.items() if k != 'thuc_te' and v} for t in m['tin']],
                                 'phan_ung_cua_vang_cac_lan_truoc': _phan_ung(m['phan_ung_cac_lan_truoc'])}
                                for m in lich['tin']] or 'không có tin mạnh của Mỹ'}
    if sl.get('su_kien_fed_24h'):
        d['p08_lich_tin_va_su_kien']['su_kien_chinh_thuc_cua_Fed_vua_qua'] = sl['su_kien_fed_24h']
    d['p09_kich_ban'] = {
        'tang': {'kich_hoat': f"nến H1 đóng trên {so(kb['tang']['kich_hoat'])}", 'muc_tieu': so(kb['tang']['muc_tieu']),
                 'vo_hieu': f"nến H1 đóng dưới {so(kb['tang']['vo_hieu_gia'])}"},
        'giam': {'kich_hoat': f"nến H1 đóng dưới {so(kb['giam']['kich_hoat'])}", 'muc_tieu': so(kb['giam']['muc_tieu']),
                 'vo_hieu': f"nến H1 đóng trên {so(kb['giam']['vo_hieu_gia'])}"},
        'di_ngang': f"giá dao động trong {so(kb['di_ngang']['tu'])}–{so(kb['di_ngang']['den'])}",
        'tan_suat_thuc_te': _tan_suat(sl.get('tan_suat')),
        'kich_ban_thuan_boi_canh_tuan_thang': _TEN_KB.get(kb.get('thuan_boi_canh')) if kb.get('thuan_boi_canh') else 'không có (tuần và tháng chưa cùng hướng)',
        'ghi_chu_bat_buoc': 'kịch bản là điều kiện, không phải dự đoán; bản tin không chọn kịch bản ưu tiên'}
    tr, dc = sl['diem_trend'], sl['diem_dca']
    d['p10_chien_luoc'] = {
        'Trend': {'diem': f"{tr['diem']}/100", 'muc': tr['muc'], 'huong_xu_huong': tr['huong'],
                  'yeu_to_cong_diem': [x['chi_so' if chi_so else 'yeu_to'] for x in tr['thanh_phan'] if x['diem'] >= 5],
                  'loi_khuyen': sl['loi_khuyen']['trend']},
        'DCA': {'diem': f"{dc['diem']}/100", 'muc': dc['muc'], 'bi_tru_diem_vi': [x['chi_so' if chi_so else 'yeu_to'] for x in dc['tru_diem'] if x['tru'] >= 3],
                'loi_khuyen': sl['loi_khuyen']['dca']},
        'ghi_chu': 'điểm phù hợp của thị trường với từng kiểu chiến lược, chấm theo luật cố định mỗi ngày; không phải lệnh mua/bán'}
    p10: Dict[str, Any] = {}
    if tt:
        p10['trang_thai_thi_truong_theo_he_thong'] = {'ten': tt['ten'], 'giai_thich': tt['mo_ta'],
                                                      'vua_chuyen_tu': tt['tu'] if tt['vua_chuyen'] else None}
    if he_thong is not None:
        mo = he_thong.get('viptrend_mo') or []
        p10['viptrend_dang_giu_lenh'] = ([{'bot': x['bot'], 'huong': x['huong'], 'gia_vao': so(x['vao']), 'luc_vao': x['gio_vao']}
                                          for x in mo] or 'không có lệnh mở')
    d['p11_he_thong_hom_nay'] = p10 or None
    return d


def du_kien_b(kq: Dict[str, Any], tt_thi_truong: Optional[Dict[str, Any]]) -> Dict[str, Any]:
    t = kq.get('dca_tong')
    return {
        'ngay': kq['ngay_nen'], 'khung_gio': kq['khung'],
        'smart_dca': ({'ghi_chu': 'kết quả perday của 10 mã signal SMART DCA theo sổ tiêu chuẩn (vốn danh nghĩa $10.000 mỗi '
                                  'mã) — không phải tài khoản thật; chỉ nêu lãi/lỗ trong ngày',
                       'tong': so(t['lai'], dau=True) + ' $', 'so_ma_lai': t['so_ma_lai'], 'so_ma_lo': t['so_ma_lo'],
                       'so_ma': t['so_ma'],
                       'tung_ma': [{'ma': x['ma'], 'lai_lo': so(x['lai'], dau=True) + ' $'} for x in kq['dca']]}
                      if t else 'chưa có kết quả perday của ngày này'),
        'viptrend_lenh_dong': [{'bot': x['bot'], 'huong': x['huong'], 'vao': so(x['vao']), 'ra': so(x['ra']),
                                'ket_qua_diem': so(x['diem'], dau=True), 'ly_do': x['ly_do']} for x in kq['viptrend_dong']],
        'viptrend_tong_diem': so(kq['viptrend_tong_diem'], dau=True) if kq['viptrend_dong'] else None,
        'viptrend_dang_mo': ([{'bot': x['bot'], 'huong': x['huong'], 'vao': so(x['vao']), 'luc_vao_gio_san': x['gio_vao'],
                               'tam_tinh_diem': so(x['tam_tinh'], dau=True) if x.get('tam_tinh') is not None else None}
                              for x in kq['viptrend_mo']] or 'không có lệnh mở'),
        'gia_dong_cua_ngay_de_tam_tinh': so(kq['gia_dong_ngay']) if kq.get('gia_dong_ngay') is not None else None,
        'thi_truong_hom_qua': tt_thi_truong,
    }


def du_kien_tuan(t: Dict[str, Any], chi_so: bool, he_thong: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Dữ kiện bài tổng kết + tầm nhìn tuần (sáng Chủ nhật), khoá t2…t10 theo thứ tự phần; số đã định dạng."""
    a = du_kien_a(t['dau_tuan_moi'], chi_so, he_thong)          # phần khung tuần mới dùng lại cách định dạng bản ngày
    w = t['nen_tuan']
    d: Dict[str, Any] = {
        'tuan': f"{t['tuan_tu'][8:]}/{t['tuan_tu'][5:7]} – {t['tuan_den'][8:]}/{t['tuan_den'][5:7]}/{t['tuan_den'][:4]}",
        'ngay_ban_tin': t['ngay_ban_tin'],
        't2_nen_tuan': {'mo': so(w['mo']), 'cao': so(w['cao']), 'thap': so(w['thap']), 'dong': so(w['dong']),
                        'thay_doi': so(w['thay_doi'], dau=True), 'thay_doi_pct': so(w['thay_doi_pct'], dau=True) + '%',
                        'bien_do_tuan': so(w['bien_do']),
                        'bien_do_trung_binh_10_tuan': so(w['bien_do_tb_10_tuan']) if w['bien_do_tb_10_tuan'] else None,
                        'tung_phien': [{'ngay': x['ngay'][8:] + '/' + x['ngay'][5:7], 'dong': so(x['dong']),
                                        'thay_doi': so(x['thay_doi'], dau=True) + f" ({so(x['thay_doi_pct'], dau=True)}%)",
                                        'bien_do': so(x['bien_do']), 'trang_thai_he_thong': x['trang_thai']}
                                       for x in t['phien']]},
    }
    kt, kn = t['khung_tuan'], t['khung_ngay']
    d['t3_xu_huong'] = {k: {'xu_huong': x['nhan'], 'cau_truc_dinh_day': x['cau_truc'],
                            'gia_so_voi_duong_trung_binh_50': None if x['tren_ema50'] is None else ('trên' if x['tren_ema50'] else 'dưới'),
                            **({'EMA50': so(x['ema50'])} if chi_so and x['ema50'] else {})}
                        for k, x in (('khung_tuan', kt), ('khung_ngay', kn)) if x}
    lt = t['lien_thi_truong']
    d['t4_lien_thi_truong'] = {
        **({'chi_so_usd_USDX': f"đóng tuần {so(lt['usdx']['dong_tuan'], 3)}, {so(lt['usdx']['doi_tuan_pct'], dau=True)}% trong tuần"}
           if lt.get('usdx') else {}),
        **({'loi_suat_trai_phieu_my_10_nam': f"{so(lt['loi_suat_my_10_nam']['cuoi_tuan_pct'])}%, "
                                            f"{so(lt['loi_suat_my_10_nam']['doi_tuan_diem_co_ban'], 0, dau=True)} điểm cơ bản trong tuần"}
           if lt.get('loi_suat_my_10_nam') else {}),
        **({'tuong_quan_vang_va_usd_20_phien': so(lt['tuong_quan_vang_usdx_20_phien'])}
           if lt.get('tuong_quan_vang_usdx_20_phien') is not None else {}),
        'ghi_chu': 'vàng thường đi ngược USD và lợi suất trái phiếu Mỹ'}
    d['t3_xu_huong']['thien_huong_tuan_moi_va_thang'] = a['p02_boi_canh_thang_tuan']
    d['t5_tin_da_ra'] = [{'luc': m['ngay'] + ' ' + m['gio'],
                          'so_lieu': [f"{x['ten']}: thực tế {x['thuc_te']} / dự báo {x['du_bao'] or '—'}" for x in m['tin'] if x['thuc_te']],
                          'vang_trong_2_gio': f"chạy {so(m['phan_ung_that']['bien_do_2h'])} giá, "
                                              f"{'tăng' if m['phan_ung_that']['huong_2h'] > 0 else 'giảm'} {so(abs(m['phan_ung_that']['huong_2h']))}",
                          'cac_lan_truoc': _phan_ung(m['phan_ung_cac_lan_truoc'])}
                         for m in t['tin_da_ra']] or 'tuần qua không có tin mạnh của Mỹ'
    if t.get('su_kien_fed'):
        d['t5b_su_kien_chinh_thuc_cua_Fed'] = t['su_kien_fed']
    pv = t['pivot_tuan']
    d['t7_vung_gia_tuan_moi'] = {
        'ghi_chu_bat_buoc': 'chỉ để tham khảo, không phải khuyến nghị mua bán',
        ('pivot_tuan' if chi_so else 'diem_xoay_tuan'): {k: so(v) for k, v in pv.items()},
        'dinh_day_tuan_vua_qua': f"đỉnh {so(w['cao'])}, đáy {so(w['thap'])}",
        'khoang_trong_mo_cua_tuan': so(t['gap'], dau=True) if t.get('gap') is not None else None,
        'vung_gan_gia_sang_nay': a['p04_vung_gia']}
    d['t8_lich_tin_tuan_moi'] = a['p08_lich_tin_va_su_kien']
    d['t9_kich_ban_va_chien_luoc'] = {'kich_ban': a['p09_kich_ban'], 'chien_luoc': a['p10_chien_luoc']}
    d['t10_he_thong_dau_tuan'] = a['p11_he_thong_hom_nay']
    return d


def du_kien_thang(m: Dict[str, Any], chi_so: bool, he_thong: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Dữ kiện bài tầm nhìn tháng (ngày giao dịch đầu tháng), khoá m2…m11."""
    a = du_kien_a(m['dau_thang_moi'], chi_so, he_thong)
    n = m['nen_thang']
    d: Dict[str, Any] = {'thang_vua_qua': m['thang_truoc'], 'thang_moi': m['thang_moi'], 'ngay_ban_tin': m['ngay_ban_tin']}
    d['m2_nen_thang_va_tung_tuan'] = {
        'mo': so(n['mo']), 'cao': so(n['cao']), 'thap': so(n['thap']), 'dong': so(n['dong']),
        'thay_doi': so(n['thay_doi'], dau=True) + f" ({so(n['thay_doi_pct'], dau=True)}%)", 'bien_do_thang': so(n['bien_do']),
        'bien_do_trung_binh_12_thang': so(n['bien_do_tb_12_thang']),
        'tung_tuan': [{'tuan_tu': w['tuan_tu'][8:] + '/' + w['tuan_tu'][5:7], 'mo': so(w['mo']), 'dong': so(w['dong']),
                       'cao': so(w['cao']), 'thap': so(w['thap'])} for w in m['tung_tuan']]}
    d['m3_xu_huong_khung_thang_tuan'] = {k: {'xu_huong': x['nhan'], 'cau_truc_dinh_day': x['cau_truc'],
                                             **({'EMA50': so(x['ema50'])} if chi_so and x.get('ema50') else {})}
                                         for k, x in (('khung_thang', m['khung_thang']), ('khung_tuan', m['khung_tuan'])) if x}
    d['m4_vi_tri_gia_trong_nam'] = (a['p02_boi_canh_thang_tuan'] or {}).get('vi_tri_trong_52_tuan')
    lt = m['lien_thi_truong']
    d['m5_lien_thi_truong'] = {
        **({'chi_so_usd_USDX': f"đóng tháng {so(lt['usdx']['dong_thang'], 3)}, {so(lt['usdx']['doi_thang_pct'], dau=True)}% trong tháng"}
           if lt.get('usdx') else {}),
        **({'loi_suat_trai_phieu_my_10_nam': f"{so(lt['loi_suat_my_10_nam']['cuoi_thang_pct'])}%, "
                                            f"{so(lt['loi_suat_my_10_nam']['doi_thang_diem_co_ban'], 0, dau=True)} điểm cơ bản trong tháng"}
           if lt.get('loi_suat_my_10_nam') else {}),
        'ghi_chu': 'vàng thường đi ngược USD và lợi suất trái phiếu Mỹ'}
    d['m6_tin_va_su_kien_thang_qua'] = {
        'tin_rat_manh': [{'luc': x['ngay'] + ' ' + x['gio'],
                          'so_lieu': [f"{t['ten']}: thực tế {t['thuc_te']} / dự báo {t['du_bao'] or '—'}" for t in x['tin'] if t['thuc_te']],
                          'vang_trong_2_gio': f"chạy {so(x['phan_ung_that']['bien_do_2h'])} giá, "
                                              f"{'tăng' if x['phan_ung_that']['huong_2h'] > 0 else 'giảm'} {so(abs(x['phan_ung_that']['huong_2h']))}"}
                         for x in m['tin_thang_qua']] or None,
        'su_kien_chinh_thuc_cua_Fed': m['su_kien_fed_thang_qua'] or None}
    pv = m['pivot_thang']
    d['m8_thien_huong_va_moc_gia_thang_moi'] = {
        'boi_canh': a['p02_boi_canh_thang_tuan'],
        ('pivot_thang' if chi_so else 'diem_xoay_thang'): {k: so(v) for k, v in pv.items()},
        'dinh_day_thang_vua_qua': f"đỉnh {so(n['cao'])}, đáy {so(n['thap'])}",
        'ghi_chu_bat_buoc': 'chỉ để tham khảo, không phải khuyến nghị mua bán'}
    d['m9_lich_su_kien_thang_moi'] = {
        'tin_rat_manh': [{'luc': x['gio'] + ' ' + x['ngay'], 'cac_tin': [t['ten'] for t in x['tin']],
                          'phan_ung_cua_vang_cac_lan_truoc': _phan_ung(x['phan_ung_cac_lan_truoc'])} for x in m['tin_thang_moi']]
        or 'chưa có trong lịch',
        'ghi_chu': (f"lịch MT5 hiện có tới {m['lich_co_den']}; phần sau chưa có lịch" if m.get('lich_co_den') else None)}
    d['m10_kich_ban_va_chien_luoc'] = {'kich_ban_ngay_dau_thang': a['p09_kich_ban'], 'chien_luoc': a['p10_chien_luoc']}
    d['m11_he_thong_dau_thang'] = a['p11_he_thong_hom_nay']
    return d


def du_kien_sau(sl: Dict[str, Any], chi_so: bool, he_thong: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Dữ kiện bài kết phiên thứ Sáu (sáng thứ Bảy), khoá s2…s6."""
    a = du_kien_a(sl, chi_so, he_thong)
    tn, tt = sl.get('tuan_nay') or {}, sl.get('tuan_truoc') or {}
    d: Dict[str, Any] = {'ngay_ban_tin': sl['ngay_ban_tin'], 's2_phien_thu_sau': a['phien_truoc']}
    if tn:
        d['s3_ca_tuan_den_luc_dong_cua'] = {
            'mo_tuan': so(tn['mo']), 'cao': so(tn['cao']), 'thap': so(tn['thap']), 'dong_tuan': so(tn['dong']),
            **({'so_voi_dong_tuan_truoc': so(tn['dong'] - tt['dong'], dau=True) + f" ({so((tn['dong'] / tt['dong'] - 1) * 100, dau=True)}%)"}
               if tt else {}),
            'bien_do_tuan': so(tn['cao'] - tn['thap'])}
    d['s4_tin_da_ra_thu_sau'] = [{'luc': x['gio'],
                                  'so_lieu': [f"{t['ten']}: thực tế {t['thuc_te']} / dự báo {t['du_bao'] or '—'}" for t in x['tin'] if t['thuc_te']],
                                  'vang_trong_2_gio': f"chạy {so(x['vang_chay_2h'])} giá, {'tăng' if x['huong_2h'] > 0 else 'giảm'} "
                                                      f"{so(abs(x['huong_2h']))} sau 2 giờ"}
                                 for x in sl.get('tin_thu_sau') or []] or 'thứ Sáu không có tin mạnh của Mỹ'
    if sl.get('su_kien_fed_24h'):
        d['s4b_su_kien_chinh_thuc_cua_Fed'] = sl['su_kien_fed_24h']
    d['s5_boi_canh_thang_tuan'] = a['p02_boi_canh_thang_tuan']
    if chi_so:
        d['s5_chi_so'] = a['p06_dong_luong'].get('chi_so')
    d['s6_he_thong_cuoi_tuan'] = a['p11_he_thong_hom_nay']
    d['ghi_chu'] = 'bản tổng kết và tầm nhìn tuần ra sáng Chủ nhật'
    return d


def du_kien_d(kq: Dict[str, Any]) -> Dict[str, Any]:
    t = kq.get('dca_tong')
    vt = kq['viptrend_dong']
    return {
        'tuan': kq['khung'],
        'smart_dca': ({'ghi_chu': 'lãi/lỗ cả tuần của 10 mã signal SMART DCA theo kết quả perday (sổ tiêu chuẩn, vốn danh nghĩa '
                                  '$10.000 mỗi mã) — không phải tài khoản thật',
                       'tong': so(t['lai'], dau=True) + ' $', 'so_ma_lai': t['so_ma_lai'], 'so_ma_lo': t['so_ma_lo'],
                       'so_ma': t['so_ma'], 'tung_ma': [{'ma': x['ma'], 'lai_lo_tuan': so(x['lai'], dau=True) + ' $'} for x in kq['dca']]}
                      if t else 'chưa có kết quả perday của tuần này'),
        'viptrend_lenh_dong_trong_tuan': ({'so_lenh': len(vt), 'tong_diem': so(kq['viptrend_tong_diem'], dau=True),
                                           'so_cham_tp': sum(1 for x in vt if x['ly_do'] == 'chạm TP'),
                                           'so_cham_sl': sum(1 for x in vt if x['ly_do'] == 'chạm SL'),
                                           'tot_nhat': {'bot': max(vt, key=lambda x: x['diem'])['bot'], 'diem': so(max(x['diem'] for x in vt), dau=True)},
                                           'kem_nhat': {'bot': min(vt, key=lambda x: x['diem'])['bot'], 'diem': so(min(x['diem'] for x in vt), dau=True)}}
                                          if vt else 'không có lệnh đóng trong tuần'),
        'viptrend_giu_qua_cuoi_tuan': ([{'bot': x['bot'], 'huong': x['huong'], 'vao': so(x['vao']), 'luc_vao_gio_san': x['gio_vao'],
                                         'tam_tinh_diem': so(x['tam_tinh'], dau=True) if x.get('tam_tinh') is not None else None}
                                        for x in kq['viptrend_mo']] or 'không có lệnh mở'),
        'gia_dong_cua_thu_sau_de_tam_tinh': so(kq['gia_dong_ngay']) if kq.get('gia_dong_ngay') is not None else None,
    }


# ---------------------------------------------------------------------
# CÂU DẪN
# ---------------------------------------------------------------------

# Hai lượt viết (02/10: viết cả 4 khổ một lượt chạm trần 4000 token, bị cắt, phải viết lại): lượt MANG XA HOI = Fanpage +
# Telegram + Zalo; lượt WEB = bài website. Mỗi lượt một khuôn JSON nhỏ.
_CHUNG = ('Văn bản thuần, KHÔNG dùng ** hay markdown khác. Tiếng Việt, gọi người đọc là "bạn", bên mình là "TradingAuto". '
          'CHÉP NGUYÊN các con số đã cho (đã định dạng sẵn), không tự tính, không làm tròn, không thêm số khác. '
          'Viết gọn, không lặp ý.\n')
_PHAN_WEB = ('1. Tóm tắt', '2. Bối cảnh tháng và tuần', '3. Xu hướng đa khung', '4. Vùng hỗ trợ và kháng cự', '5. Biến động',
             '6. Động lượng', '7. Liên thị trường', '8. Lịch tin và sự kiện', '9. Ba kịch bản',
             '10. Góc chiến lược Trend / DCA', '11. Hệ thống TradingAuto hôm nay')
_PHAN_THANG = ('1. Tóm tắt tháng', '2. Nến tháng và từng tuần', '3. Xu hướng khung tháng và tuần', '4. Vị trí giá trong năm',
               '5. Liên thị trường trong tháng', '6. Tin và sự kiện tháng qua', '7. Thiên hướng và mốc giá tháng mới',
               '8. Lịch sự kiện tháng mới', '9. Kịch bản và góc chiến lược Trend / DCA', '10. Hệ thống TradingAuto đầu tháng')
_KHO_MXH = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. Bản mạng xã hội chỉ gồm: tóm tắt 1–2 câu, vùng '
    'giá gần nhất mỗi phía, bối cảnh tuần/tháng 1 câu, 3 kịch bản rút gọn (2 mức kích hoạt + tần suất thực tế), góc Trend/DCA '
    '(điểm + 1 câu khuyên), tin mạnh trong ngày.\n'
    '- fb (Fanpage): 160–260 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, chia ý gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD và 1 cái hợp nội dung).\n'
    '- tele (Telegram, nhóm trader): 110–180 chữ, gọn, gạch đầu dòng, không hashtag, cuối có link bài đầy đủ ' + LIEN_KET + '/tin-tuc.\n'
    '- zalo (nhóm Zalo): 70–120 chữ, thân mật, cuối có link ' + LIEN_KET + '/tin-tuc, không hashtag.\n' + _CHUNG
)
_KHO_MXH_B = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. CHỈ nói kết quả bot từ số liệu đã cho; KHÔNG nêu '
    'vùng giá, kịch bản, lời khuyên giao dịch hay phân tích kỹ thuật.\n'
    '- fb (Fanpage): 120–220 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, chia ý gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD #SmartDCA).\n'
    '- tele (Telegram, nhóm trader): 70–130 chữ, gọn, gạch đầu dòng, không hashtag.\n'
    '- zalo (nhóm Zalo): 50–90 chữ, thân mật, cuối có link ' + LIEN_KET + '/pages/dca_signals, không hashtag.\n' + _CHUNG
)
_PHAN_TUAN = ('1. Tóm tắt tuần', '2. Nến tuần và từng phiên', '3. Xu hướng khung tuần và khung ngày',
              '4. Liên thị trường trong tuần', '5. Tin đã ra và phản ứng của vàng', '6. Vùng giá cho tuần mới',
              '7. Lịch tin tuần mới', '8. Kịch bản và góc chiến lược Trend / DCA', '9. Hệ thống TradingAuto cuối tuần')
_PHAN_SAU = ('1. Tóm tắt', '2. Phiên thứ Sáu', '3. Cả tuần đến lúc đóng cửa', '4. Tin đã ra và phản ứng của vàng',
             '5. Bối cảnh tháng và tuần', '6. Hệ thống TradingAuto cuối tuần')
_KHO_MXH_TUAN = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. Bản mạng xã hội chỉ gồm: tuần vừa qua 1–2 câu '
    '(đóng tuần, tăng/giảm, biên độ), thiên hướng tuần mới, vùng giá tuần mới gần nhất mỗi phía, tin mạnh tuần mới, điểm '
    'Trend/DCA.\n'
    '- fb (Fanpage): 170–270 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, chia ý gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD và 1 cái hợp nội dung).\n'
    '- tele (Telegram, nhóm trader): 110–180 chữ, gọn, gạch đầu dòng, không hashtag, cuối có link bài đầy đủ ' + LIEN_KET + '/tin-tuc.\n'
    '- zalo (nhóm Zalo): 70–120 chữ, thân mật, cuối có link ' + LIEN_KET + '/tin-tuc, không hashtag.\n' + _CHUNG
)


_KHO_MXH_THANG = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. Bản mạng xã hội chỉ gồm: tháng vừa qua 1–2 câu '
    '(đóng tháng, tăng/giảm, biên độ so với trung bình), thiên hướng tháng mới + mức vô hiệu, mốc giá tháng (điểm xoay, '
    'đỉnh/đáy tháng trước), tin rất mạnh của tháng mới.\n'
    '- fb (Fanpage): 170–270 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, chia ý gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD và 1 cái hợp nội dung).\n'
    '- tele (Telegram, nhóm trader): 110–180 chữ, gọn, gạch đầu dòng, không hashtag, cuối có link bài đầy đủ ' + LIEN_KET + '/tin-tuc.\n'
    '- zalo (nhóm Zalo): 70–120 chữ, thân mật, cuối có link ' + LIEN_KET + '/tin-tuc, không hashtag.\n' + _CHUNG
)


def _kho_web(phan: Optional[tuple]) -> str:
    dau = ('Trả về JSON đúng khuôn: {"tieu_de": "...", "mo_ta": "...", "noi_dung": "..."} — bài cho mục Tin tức trên website.\n'
           '- tieu_de ≤ 80 ký tự (có ngày), mo_ta ≤ 160 ký tự (dùng làm mô tả khi chia sẻ link).\n')
    if phan:
        than = ('- noi_dung 650–1000 chữ, ĐỦ ' + str(len(phan)) + ' phần theo đúng thứ tự, mỗi phần mở bằng một dòng tiêu đề '
                '"## <tên phần>", tên phần lần lượt: ' + '; '.join(phan) + '. Phần không có dữ kiện thì một câu nói rõ là '
                'chưa có. Trong phần dùng đoạn văn ngắn hoặc dòng gạch đầu "- ".\n')
    else:
        than = ('- noi_dung 250–450 chữ, chia đoạn bằng dòng trống, 2–4 dòng tiêu đề nhỏ bắt đầu bằng "## ", có thể dùng dòng '
                'gạch đầu "- ".\n')
    return dau + than + _CHUNG.replace('Văn bản thuần,', 'Văn bản thuần (trừ "## " và "- "),')


_KHO_MXH_SAU = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. Bản mạng xã hội chỉ gồm: phiên thứ Sáu 1–2 câu (đóng '
    'cửa, tăng/giảm, biên độ), cả tuần đến lúc đóng cửa, tin đã ra và vàng phản ứng, một dòng hẹn bản tổng kết tuần sáng Chủ nhật.\n'
    '- fb (Fanpage): 120–200 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD và 1 cái hợp nội dung).\n'
    '- tele (Telegram, nhóm trader): 80–140 chữ, gọn, gạch đầu dòng, không hashtag, cuối có link ' + LIEN_KET + '/tin-tuc.\n'
    '- zalo (nhóm Zalo): 50–90 chữ, thân mật, cuối có link ' + LIEN_KET + '/tin-tuc, không hashtag.\n' + _CHUNG
)
_KHO_MXH_D = (
    'Trả về JSON đúng khuôn: {"fb": "...", "tele": "...", "zalo": "..."}. CHỈ nói kết quả bot CẢ TUẦN từ số liệu đã cho; KHÔNG nêu '
    'vùng giá, kịch bản, lời khuyên giao dịch hay phân tích kỹ thuật.\n'
    '- fb (Fanpage): 140–240 chữ, dòng đầu là tiêu đề ngắn + 1 emoji, gạch đầu dòng có emoji, cuối bài 3 hashtag '
    '(#TradingAuto #XAUUSD #SmartDCA).\n'
    '- tele (Telegram, nhóm trader): 80–150 chữ, gọn, gạch đầu dòng, không hashtag.\n'
    '- zalo (nhóm Zalo): 50–90 chữ, thân mật, cuối có link ' + LIEN_KET + '/pages/dca_signals, không hashtag.\n' + _CHUNG
)


# loại bài -> (khổ mạng xã hội, các phần bắt buộc của bài web)
LOAI = {'A': (_KHO_MXH, _PHAN_WEB), 'T': (_KHO_MXH_TUAN, _PHAN_TUAN), 'M': (_KHO_MXH_THANG, _PHAN_THANG),
        'S': (_KHO_MXH_SAU, _PHAN_SAU), 'B': (_KHO_MXH_B, None), 'D': (_KHO_MXH_D, None)}

def cau_dan_a(chi_so: bool) -> str:
    return (
        'Em là biên tập viên phân tích kỹ thuật của TradingAuto (bot giao dịch vàng tự động trên MT5), viết BẢN TIN '
        'VÀNG SÁNG NAY. Văn phong chuyên gia: điềm tĩnh, chính xác, có điều kiện, không giật tít, không cảm thán.\n'
        'Dữ kiện chia theo phần p02…p11 (phần 1 tóm tắt em tự viết từ các phần). Quy tắc:\n'
        '- Bối cảnh tháng và tuần: nêu thiên hướng, lý do, mức vô hiệu, vị trí giá; ghi rõ thiên hướng là bối cảnh, không '
        'phải dự báo.\n'
        '- Vùng giá: ghi nguyên văn "chỉ để tham khảo, không phải khuyến nghị mua bán".\n'
        '- Kịch bản: viết cả 3 dạng điều kiện (nếu… thì…), nêu mức kích hoạt, mục tiêu, mức vô hiệu, chép tần suất thực tế, '
        'nói kịch bản nào thuận bối cảnh tuần/tháng; KHÔNG chọn kịch bản ưu tiên, KHÔNG nói kịch bản nào "khả năng cao"; ghi '
        '"kịch bản là điều kiện, không phải dự đoán".\n'
        '- Góc chiến lược: Trend và DCA là tên chung hai kiểu chiến lược (không gắn tên sản phẩm); nêu điểm /100, mức, '
        'lý do chính, rồi chép lời khuyên quản trị.\n'
        '- Lịch tin và sự kiện: nêu phản ứng của vàng các lần trước nếu có; nhắc giờ tin giá thường giật mạnh; sự kiện Fed '
        'chỉ nêu ai / thông cáo gì / lúc nào (dịch tên bài sang tiếng Việt), không suy diễn nội dung.\n'

        + ('- Bản KÈM CHỈ SỐ: đưa các con số chỉ báo (EMA, ADX, +DI/−DI, RSI, MACD) vào đúng phần, mỗi chỉ số kèm nửa câu nghĩa.\n'
           if chi_so else
           '- Bản LỜI THƯỜNG: KHÔNG nêu tên chỉ báo (EMA, ADX, RSI, MACD, ATR, pivot, Fibonacci…); diễn đạt bằng lời '
           '(vd "đường trung bình 50 ngày", "biên độ trung bình một ngày", "điểm xoay").\n')
        + 'KHÔNG dự đoán giá, KHÔNG khuyên mua/bán, KHÔNG quảng cáo sản phẩm (chỉ một dòng cuối: "Theo dõi bản tin mỗi sáng '
          'tại ' + LIEN_KET + '").\n'
    )


def cau_dan_tuan(chi_so: bool) -> str:
    return (
        'Em là biên tập viên phân tích kỹ thuật của TradingAuto (bot giao dịch vàng tự động trên MT5), viết bài TỔNG KẾT '
        'PHÂN TÍCH TUẦN cho sáng Chủ nhật: nhìn lại tuần vừa qua bằng số liệu, rồi dựng khung cho tuần mới. Văn phong chuyên gia: điềm tĩnh, chính xác, có điều kiện, không giật tít.\n'
        'Dữ kiện chia theo phần t2…t10 (phần 1 tóm tắt tuần em tự viết). Bản tin không chọn kịch bản ưu tiên. Quy tắc:\n'
        '- Thiên hướng tuần mới / tháng: là bối cảnh, không phải dự báo; nêu mức vô hiệu.\n'
        '- Tin đã ra: số thực tế so với dự báo và vàng chạy bao nhiêu sau tin, không suy diễn nguyên nhân ngoài số đã cho.\n'
        '- Vùng giá tuần mới: ghi nguyên văn "chỉ để tham khảo, không phải khuyến nghị mua bán".\n'
        '- Kịch bản: dạng điều kiện (nếu… thì…), có mức kích hoạt, mục tiêu, mức vô hiệu; ghi "kịch bản là điều kiện, '
        'không phải dự đoán". Trend và DCA là tên chung hai kiểu chiến lược.\n'
        + ('- Bản KÈM CHỈ SỐ: đưa các con số chỉ báo có trong dữ kiện vào đúng phần, mỗi chỉ số kèm nửa câu nghĩa.\n'
           if chi_so else
           '- Bản LỜI THƯỜNG: KHÔNG nêu tên chỉ báo (EMA, ADX, RSI, MACD, ATR, pivot, Fibonacci…); diễn đạt bằng lời.\n')
        + 'KHÔNG dự đoán giá, KHÔNG khuyên mua/bán, KHÔNG quảng cáo sản phẩm (chỉ một dòng cuối: "Theo dõi bản tin mỗi sáng '
          'tại ' + LIEN_KET + '").\n'
    )


def cau_dan_thang(chi_so: bool) -> str:
    return (
        'Em là biên tập viên phân tích kỹ thuật của TradingAuto (bot giao dịch vàng tự động trên MT5), viết bài TẦM NHÌN '
        'THÁNG cho ngày giao dịch đầu tháng: nhìn lại tháng vừa qua bằng số liệu, rồi dựng bối cảnh và mốc giá cho tháng mới. '
        'Văn phong chuyên gia: điềm tĩnh, chính xác, có điều kiện, không giật tít.\n'
        'Dữ kiện chia theo phần m2…m11 (phần 1 tóm tắt tháng em tự viết). Quy tắc:\n'
        '- Thiên hướng tháng là bối cảnh, không phải dự báo; luôn nêu mức vô hiệu.\n'

        '- Lịch tháng mới: chỉ nêu tin có trong dữ kiện; lịch chưa có thì nói rõ.\n'
        '- Mốc giá: ghi nguyên văn "chỉ để tham khảo, không phải khuyến nghị mua bán". Kịch bản dạng điều kiện, không chọn '
        'kịch bản ưu tiên.\n'
        + ('- Bản KÈM CHỈ SỐ: đưa con số chỉ báo có trong dữ kiện vào đúng phần, mỗi chỉ số kèm nửa câu nghĩa.\n'
           if chi_so else
           '- Bản LỜI THƯỜNG: KHÔNG nêu tên chỉ báo (EMA, ADX, RSI, MACD, ATR, pivot, Fibonacci…); diễn đạt bằng lời.\n')
        + 'KHÔNG dự đoán giá, KHÔNG khuyên mua/bán, KHÔNG quảng cáo sản phẩm (chỉ một dòng cuối: "Theo dõi bản tin mỗi sáng '
          'tại ' + LIEN_KET + '").\n'
    )


def cau_dan_sau(chi_so: bool) -> str:
    return (
        'Em là biên tập viên phân tích kỹ thuật của TradingAuto (bot giao dịch vàng tự động trên MT5), viết bài KẾT PHIÊN THỨ '
        'SÁU cho sáng thứ Bảy: thị trường đã đóng cửa tới sáng thứ Hai, nên chỉ nhìn lại phiên thứ Sáu và cả tuần đến lúc đóng '
        'cửa. Văn phong chuyên gia: điềm tĩnh, chính xác. Dữ kiện chia theo phần s2…s6 (phần 1 tóm tắt em tự viết).\n'
        '- KHÔNG ra kịch bản, KHÔNG nêu mức kích hoạt hay mục tiêu; cuối bài hẹn: bản tổng kết và tầm nhìn tuần ra sáng Chủ nhật.\n'
        '- Tin đã ra: số thực tế so với dự báo và vàng chạy bao nhiêu sau tin, không suy diễn nguyên nhân ngoài số đã cho.\n'
        '- Bối cảnh tháng/tuần là bối cảnh, không phải dự báo.\n'
        + ('- Bản KÈM CHỈ SỐ: đưa con số chỉ báo có trong dữ kiện vào đúng phần, mỗi chỉ số kèm nửa câu nghĩa.\n'
           if chi_so else
           '- Bản LỜI THƯỜNG: KHÔNG nêu tên chỉ báo (EMA, ADX, RSI, MACD, ATR, pivot, Fibonacci…); diễn đạt bằng lời.\n')
        + 'KHÔNG dự đoán giá, KHÔNG khuyên mua/bán, KHÔNG quảng cáo sản phẩm (chỉ một dòng cuối: "Theo dõi bản tin mỗi sáng '
          'tại ' + LIEN_KET + '").\n'
    )


def cau_dan_d() -> str:
    return (
        'Em là biên tập viên của TradingAuto, viết bài KẾT QUẢ BOT CẢ TUẦN (sáng Chủ nhật), MINH BẠCH: tuần lãi hay lỗ đều nói '
        'thẳng con số, không tô vẽ, không bào chữa. Nội dung: (1) SMART DCA: lãi/lỗ cả tuần (thứ Hai–thứ Sáu, giờ sàn) của 10 mã '
        'signal theo kết quả perday (sổ tiêu chuẩn, vốn danh nghĩa $10.000 mỗi mã) — ghi rõ KHÔNG phải tài khoản thật; nêu tổng, '
        'số mã lãi / lỗ, mã tốt nhất và kém nhất; CHỈ nói lãi/lỗ; (2) VipTrend: tổng điểm các lệnh đã đóng trong tuần, số lệnh '
        'chạm TP / SL, lệnh tốt nhất và kém nhất; lệnh còn mở qua cuối tuần: liệt kê (bot, hướng, giá vào, lúc vào, TẠM TÍNH theo '
        'điểm giá tại giá đóng cửa thứ Sáu — ghi rõ tạm tính, chưa chốt).\n'
        'Cuối bài: "Kết quả quá khứ không bảo đảm kết quả tương lai." và link: 10 mã signal SMART DCA '
        + LIEN_KET + '/pages/dca_signals, VipTrend ' + LIEN_KET + '/pages/signal_stream.\n'
    )


def cau_dan_b() -> str:
    return (
        'Em là biên tập viên của TradingAuto, viết bài KẾT QUẢ BOT hằng ngày, MINH BẠCH: ngày lãi hay ngày lỗ đều nói '
        'thẳng con số, không tô vẽ, không bào chữa, không xin lỗi. Nội dung: (1) SMART DCA: lãi/lỗ trong ngày của 10 mã signal '
        'theo kết quả perday (sổ tiêu chuẩn, vốn danh nghĩa $10.000 mỗi mã) — ghi rõ đây là sổ tiêu chuẩn, KHÔNG phải tài '
        'khoản thật; nêu tổng, số mã lãi / lỗ, mã lãi nhiều nhất và lỗ nhiều nhất; CHỈ nói lãi/lỗ, không nêu số lệnh, sụt '
        'vốn hay chỉ số khác; (2) VipTrend: từng lệnh đã đóng (bot, hướng, vào → ra, kết quả theo ĐIỂM GIÁ, lý do); không có '
        'lệnh đóng thì nói ngắn; lệnh còn mở cuối ngày: liệt kê từng lệnh (bot, hướng, giá vào, lúc vào, TẠM TÍNH theo điểm giá tại '
        'giá đóng cửa ngày — ghi rõ là tạm tính, chưa chốt); (3) nếu có ngày lỗ đáng kể, MỘT câu nối với diễn biến thị trường (dùng thi_truong_hom_qua '
        'nếu có), không phỏng đoán thêm. Ngày = ngày giao dịch tính theo giờ sàn 0h–24h.\n'
        'Cuối bài: "Kết quả quá khứ không bảo đảm kết quả tương lai." và link: 10 mã signal SMART DCA '
        + LIEN_KET + '/pages/dca_signals, VipTrend ' + LIEN_KET + '/pages/signal_stream.\n'
    )


def doc_json(van_ban: str) -> Dict[str, Any]:
    s = str(van_ban or '')
    i, j = s.find('{'), s.rfind('}')
    try:
        d = json.loads(s[i:j + 1]) if i >= 0 and j > i else {}
    except ValueError:
        d = {}
    return d if isinstance(d, dict) else {}


def kiem(b: Dict[str, Any], phan: Optional[tuple] = _PHAN_WEB) -> List[str]:
    loi = []
    for k in ('fb', 'tele', 'zalo'):
        if not b[k]:
            loi.append(f'thiếu bản {k}')
        elif '**' in b[k]:
            loi.append(f'{k} có markdown')
    if not b['web']['noi_dung']:
        loi.append('thiếu bản web')
    elif phan and b['web']['noi_dung'].count('## ') < len(phan):
        loi.append(f"bản web thiếu phần ({b['web']['noi_dung'].count('## ')}/{len(phan)})")
    tat_ca = ' '.join([b['fb'], b['tele'], b['zalo'], b['web']['noi_dung']])
    m = _CAM.search(tat_ca)
    if m:
        loi.append('chữ cấm: ' + m.group(0))
    return loi


def _mot_luot(goi, he_thong: str, noi: str, khoa, lan_thu: int) -> Dict[str, str]:
    d, thieu = {}, list(khoa)
    for lan in range(lan_thu):
        d = doc_json(goi(he_thong, noi + (f'\n\nBẢN TRƯỚC THIẾU / HỎNG: {", ".join(thieu)}. Viết lại, NGẮN GỌN hơn.'
                                         if lan else '')) or '')
        d = {k: str(d.get(k) or '').strip() for k in khoa}
        thieu = [k for k in khoa if not d[k] or '**' in d[k] or _CAM.search(d[k])]
        if not thieu:
            break
    return d


def de_bai(cau_dan: str, du_kien: Dict[str, Any], loai: str = 'A') -> Dict[str, Any]:
    """Đề bài hai lượt viết, KHÔNG gọi mô hình — để người / agent khác viết (chủ dự án 03/10: diễn tập viết bằng agent
    trên máy làm việc, không gọi API). Ghép kết quả bằng ghep()."""
    kho_mxh, phan = LOAI[loai]
    noi = 'SỐ LIỆU (JSON):\n' + json.dumps(du_kien, ensure_ascii=False, indent=1)
    return {'loai': loai, 'mxh': {'he_thong': cau_dan + kho_mxh, 'noi_dung': noi, 'khoa': ['fb', 'tele', 'zalo']},
            'web': {'he_thong': cau_dan + _kho_web(phan), 'noi_dung': noi, 'khoa': ['tieu_de', 'mo_ta', 'noi_dung']}}


def ghep(loai: str, mxh: Dict[str, Any], web: Dict[str, Any]) -> Dict[str, Any]:
    _kho, phan = LOAI[loai]
    bai = {k: str(mxh.get(k) or '').strip() for k in ('fb', 'tele', 'zalo')}
    bai['web'] = {k: str(web.get(k) or '').strip() for k in ('tieu_de', 'mo_ta', 'noi_dung')}
    bai['loi'] = kiem(bai, phan)
    return bai


def viet(cau_dan: str, du_kien: Dict[str, Any], loai: str = 'A',
         _goi: Optional[Callable[[str, str], Optional[str]]] = None, lan_thu: int = 3) -> Dict[str, Any]:
    """Hai lượt: mạng xã hội (fb, tele, zalo) rồi website. loai: 'A' bản tin sáng 11 phần, 'T' tổng kết tuần (sáng thứ
    Hai), 'B' kết quả bot. Trả {'fb','tele','zalo','web':{…},'loi':[…]}."""
    kho_mxh, phan = LOAI[loai]
    if _goi is None:
        from nao.compact import _goi_mo_hinh
        _goi = lambda h, n: _goi_mo_hinh(h, n, 'tong_hop')  # noqa: E731
    noi = 'SỐ LIỆU (JSON):\n' + json.dumps(du_kien, ensure_ascii=False, indent=1)
    mxh = _mot_luot(_goi, cau_dan + kho_mxh, noi, ('fb', 'tele', 'zalo'), lan_thu)
    web = _mot_luot(_goi, cau_dan + _kho_web(phan), noi, ('tieu_de', 'mo_ta', 'noi_dung'), lan_thu)
    bai = dict(mxh, web=web)
    bai['loi'] = kiem(bai, phan)
    return bai


if __name__ == '__main__':
    assert so(4177.26) == '4.177,26' and so(-170.55, dau=True) == '-170,55' and so(20.5, dau=True) == '+20,50'
    assert so(1705.0, 0, dau=True) == '+1.705' and so(None) == '—'
    nd = '\n'.join('## ' + p for p in _PHAN_WEB)
    tra = iter(['rác', '{"fb": "a", "tele": "b", "zalo": "c"}', json.dumps({'tieu_de': 't', 'mo_ta': 'm', 'noi_dung': nd})])
    b = viet('X', {'a': 1}, 'A', _goi=lambda h, n: next(tra))
    assert b == {'fb': 'a', 'tele': 'b', 'zalo': 'c', 'web': {'tieu_de': 't', 'mo_ta': 'm', 'noi_dung': nd}, 'loi': []}, b
    assert kiem(dict(b, web={'noi_dung': '## 1. Tóm tắt'}))[0].startswith('bản web thiếu phần')
    assert kiem(dict(b, fb='**a**'))[0] == 'fb có markdown'
    assert any('chữ cấm' in x for x in kiem(dict(b, tele='giá chắc chắn tăng')))
    tra = iter(['{"fb": "a", "tele": "b", "zalo": "c"}', '{"tieu_de": "t", "mo_ta": "m", "noi_dung": "n"}'])
    assert viet('X', {'a': 1}, 'B', _goi=lambda h, n: next(tra))['loi'] == []          # bài B không bắt 11 phần
    print('TỰ KIỂM ban_tin/viet.py QUA.')
