cotienbot / src /lottery_core.py
Anothervin1's picture
Update src/lottery_core.py
e5e4931 verified
import asyncio
from collections import Counter
from datetime import datetime, timedelta
import re
import os
from itertools import permutations
import json
from src.firestore_db import FirestoreDB
from src.cache import Cache
from src.logger import logger
from config.settings import Settings
class LotteryCore:
def __init__(self, db: FirestoreDB, cache: Cache):
self.db = db
self.cache = cache
self.initialized = False
logger.info("[LotteryCore] Khởi tạo với Firestore")
self.prize_mapping = {
"đặc biệt": "DB",
"giải nhất": "Nhat",
"giải nhì": "Nhi",
"giải ba": "Ba",
"giải tư": "Tu",
"giải năm": "Nam",
"giải sáu": "Sau",
"giải bảy": "Bay",
"giải tám": "Tam"
}
async def initialize(self):
if self.initialized:
return
try:
logger.info("[initialize] Khởi tạo cơ bản, không dùng vector")
self.initialized = True
except Exception as e:
logger.error(f"[initialize] Lỗi khi khởi tạo: {str(e)}")
raise
def _get_day_name(self, weekday: int) -> str:
days = {0: "Hai", 1: "Ba", 2: "Tư", 3: "Năm", 4: "Sáu", 5: "Bảy", 6: "Chủ Nhật"}
return days.get(weekday, "Không xác định")
async def parse_date(self, date_str: str) -> tuple[str, datetime] | None:
formats = [
"%d-%m-%Y", "%Y-%m-%d", "%d/%m/%Y", "%Y/%m/%d",
"%d-%m-%y", "%y-%m-%d", "%d/%m/%y", "%y/%m/%d",
"%d.%m.%Y", "%d-%m-%Y", "%d/%m/%Y", "%Y.%m.%d",
"%m/%d/%Y", "%m-%d-%Y", "%d%m%Y", "%Y%m%d"
]
for fmt in formats:
try:
parsed = datetime.strptime(date_str, fmt)
if parsed.year < 100:
parsed = parsed.replace(year=parsed.year + 2000)
return parsed.strftime("%d-%m-%Y"), parsed
except ValueError:
continue
logger.warning(f"[parse_date] Invalid date format: {date_str}")
return None
def tach_hang(self, so):
so_clean = so.replace('@', '')
so = "@" * (6 - len(so_clean)) + so_clean
return {
"trieu": so[0] if so[0] != '@' else '@',
"tramnghin": so[1] if so[1] != '@' else '@',
"chucnghin": so[2] if so[2] != '@' else '@',
"nghin": so[3] if so[3] != '@' else '@',
"chuc": so[4] if so[4] != '@' else '@',
"donvi": so[5] if so[5] != '@' else '@'
}
def create_longs(self, data):
if not isinstance(data.get("giai"), dict):
logger.warning(f"[create_longs] 'giai' không phải map")
return {f"L{i}": ["@"] * 18 for i in range(1, 7)}
lo = []
for giai in ["DB", "Nhat", "Nhi", "Ba", "Tu", "Nam", "Sau", "Bay", "Tam"]:
if giai in data["giai"]:
for so in data["giai"][giai]:
if not so.isdigit():
logger.warning(f"[create_longs] Số không hợp lệ trong {giai}: {so}")
lo.append("@@@@@@")
continue
so_clean = so.replace('@', '')
so = "@" * (6 - len(so_clean)) + so_clean
lo.append(so)
logger.debug(f"[create_longs] Số lô thu thập: {len(lo)}")
while len(lo) < 18:
lo.append("@@@@@@")
lo = lo[:18]
longs = {f"L{i}": [] for i in range(1, 7)}
for so in lo:
hang = self.tach_hang(so)
longs["L1"].append(str(hang["donvi"]))
longs["L2"].append(str(hang["chuc"]))
longs["L3"].append(str(hang["nghin"]))
longs["L4"].append(str(hang["chucnghin"]))
longs["L5"].append(str(hang["tramnghin"]))
longs["L6"].append(str(hang["trieu"]))
return longs
def _map_dai_name(self, dai_name: str) -> str:
dai_name_clean = dai_name.lower().replace(" ", "").replace("chủnhật", "").replace("chu", "")
dai_mapping = {
"xskh": "xskh",
"khánhhòa": "xskh",
"khanhhoa": "xskh",
"khánhhoà": "xskh",
"khanhhoà": "xskh",
"xổsốkhánhhòa": "xskh",
"xosokhanhhoa": "xskh",
"kshoa": "xskh",
"khhoa": "xskh",
"xsdl": "xsdl",
"đắklắk": "xsdl",
"daklak": "xsdl",
"đăklăk": "xsdl",
"daklăk": "xsdl",
"xổsốđắklắk": "xsdl",
"xosodaklak": "xsdl",
"daklac": "xsdl",
"đăklăc": "xsdl",
"xspy": "xspy",
"phúyên": "xspy",
"phuyen": "xspy",
"phuyén": "xspy",
"phúyền": "xspy",
"phuyeen": "xspy",
"xổsốphúyên": "xspy",
"xosophuyen": "xspy",
"xsqng": "xsqng",
"quảngngãi": "xsqng",
"quangngai": "xsqng",
"quảngngai": "xsqng",
"quangngãi": "xsqng",
"xổsốquảngngãi": "xsqng",
"xosoquangngai": "xsqng",
"qngai": "xsqng",
"xsgl": "xsgl",
"gialai": "xsgl",
"gialay": "xsgl",
"xổsốgialai": "xsgl",
"xosogialai": "xsgl",
"xsbd": "xsbd",
"bìnhđịnh": "xsbd",
"binhdinh": "xsbd",
"bìnhđinh": "xsbd",
"binhđinh": "xsbd",
"binhdịnh": "xsbd",
"xổsốbìnhđịnh": "xsbd",
"xosobinhdinh": "xsbd",
"bdinh": "xsbd",
}
mapped_dai = dai_mapping.get(dai_name_clean, dai_name_clean)
if mapped_dai == dai_name_clean and mapped_dai not in Settings.DAI_CONFIG:
logger.warning(f"[_map_dai_name] Tên đài không được ánh xạ: {dai_name}")
else:
logger.info(f"[_map_dai_name] Ánh xạ đài: {dai_name} -> {mapped_dai}")
return mapped_dai
def _validate_number(self, prize: str, number: str) -> bool:
expected_lengths = {
"DB": 6, "Nhat": 5, "Nhi": 5, "Ba": 5, "Tu": 5,
"Nam": 4, "Sau": 4, "Bay": 3, "Tam": 2
}
expected_length = expected_lengths.get(prize)
if not expected_length:
logger.warning(f"[_validate_number] Unknown prize: {prize}")
return False
if not number.isdigit():
logger.warning(f"[_validate_number] Number is not numeric: {number}")
return False
if len(number) != expected_length:
logger.warning(f"[_validate_number] Number length {len(number)} does not match expected {expected_length} for prize {prize}")
return False
return True
async def add_lottery_result(self, text: str) -> str:
try:
# Chuẩn hóa toàn bộ văn bản: thay thế tab và khoảng trắng liên tiếp
text = re.sub(r'[\t\s]+', ' ', text.strip())
# Khôi phục xuống dòng trước tên giải
for prize in self.prize_mapping:
text = re.sub(rf'({prize}\s)', f'\n\\1', text, flags=re.IGNORECASE)
lines = text.split('\n')
if not lines:
logger.debug("[add_lottery_result] Empty input text")
return "Lỗi: Văn bản rỗng."
header_match = re.match(
r"Xổ số\s+(.+?)(?:\s+Thứ\s+\w+|Chủ\s+Nhật)?\s*(\d{2}[-./]\d{2}[-./]\d{4})\b",
lines[0],
re.IGNORECASE
)
if not header_match:
logger.debug(f"[add_lottery_result] Invalid header format: {lines[0]}")
return "Lỗi: Dòng đầu phải có định dạng 'Xổ số <Đài> [Thứ X/Chủ Nhật] DD-MM-YYYY'. Ví dụ: 'Xổ số Đắk Lắk 17-06-2025'."
dai_name = header_match.group(1).strip()
ngay = header_match.group(2).replace("/", "-").replace(".", "-")
dai = self._map_dai_name(dai_name)
parsed_date = await self.parse_date(ngay)
if not parsed_date:
logger.debug(f"[add_lottery_result] Invalid date format: {ngay}")
return f"Lỗi: Định dạng ngày không hợp lệ: {ngay}."
ngay_normalized, date = parsed_date
if dai in Settings.DAI_CONFIG and date.weekday() not in Settings.DAI_CONFIG[dai]["days"]:
day_name = date.strftime('%A')
expected_days = ', '.join([self._get_day_name(d) for d in Settings.DAI_CONFIG[dai]["days"]])
logger.debug(f"[add_lottery_result] Invalid day: {ngay} is {day_name}, expected {expected_days} for {dai}")
return f"Lỗi: Đài {dai} chỉ quay vào {expected_days}. Ngày {ngay}{day_name}."
giai_map = self.prize_mapping
expected_counts = {"DB": 1, "Nhat": 1, "Nhi": 1, "Ba": 2, "Tu": 7, "Nam": 1, "Sau": 3, "Bay": 1, "Tam": 1}
expected_lengths = {"DB": 6, "Nhat": 5, "Nhi": 5, "Ba": 5, "Tu": 5, "Nam": 4, "Sau": 4, "Bay": 3, "Tam": 2}
giai = {}
current_giai = None
numbers = []
error_lines = []
for i, line in enumerate(lines[1:], 1):
line = re.sub(r'[\t\s]+', ' ', line.strip())
line = re.sub(r'[.\-,;\s]+', ' ', line).strip()
logger.debug(f"[add_lottery_result] Processing line {i}: '{line}'")
if not line or "xổ số miền" in line.lower():
continue
for name, key in giai_map.items():
if re.match(rf"^{name}\b", line, re.IGNORECASE):
if current_giai and numbers:
giai[current_giai] = numbers
numbers = []
current_giai = key
line = re.sub(rf"^{name}\b", "", line, flags=re.IGNORECASE).strip()
break
if current_giai:
number_list = [num.strip() for num in line.split() if num.strip().isdigit()]
if not number_list:
error_lines.append(f"Dòng {i}: '{line}' (không tìm thấy số hợp lệ)")
numbers.extend(number_list)
logger.debug(f"[add_lottery_result] Extracted numbers for {current_giai}: {number_list}")
if current_giai and numbers:
giai[current_giai] = numbers
if not giai:
error_msg = "Lỗi: Không tìm thấy giải nào. Vui lòng kiểm tra định dạng kết quả, ví dụ: '/load_lottery Xổ số Đắk Lắk 17-06-2025\nĐặc biệt 123456\nGiải nhất 12345'."
if error_lines:
error_msg += "\nCác dòng lỗi:\n" + "\n".join(error_lines)
logger.debug(f"[add_lottery_result] No results parsed from input: {text}")
return error_msg
for key in expected_counts:
if key not in giai:
logger.debug(f"[add_lottery_result] Missing prize: {key}")
return f"Lỗi: Thiếu giải {key}. Vui lòng kiểm tra đầu vào."
if len(giai[key]) != expected_counts[key]:
logger.debug(f"[add_lottery_result] Incorrect number count for {key}: expected {expected_counts[key]}, got {len(giai[key])}")
return f"Lỗi: Giải {key} phải có {expected_counts[key]} số, nhận được {len(giai[key])} số."
for num in giai[key]:
if not self._validate_number(key, num):
logger.debug(f"[add_lottery_result] Invalid number for {key}: {num}")
return f"Lỗi: Số trong giải {key} phải có {expected_lengths[key]} chữ số và chỉ chứa số, nhận được '{num}'."
doc_id = f"lottery-{ngay_normalized}_{dai}"
if (await self.db.lottery_results.document(doc_id).get()).exists:
logger.debug(f"[add_lottery_result] Data already exists: {doc_id}")
return f"Dữ liệu cho {dai} ngày {ngay} đã tồn tại."
data = {"ngay": ngay_normalized, "dai": dai, "giai": giai}
if await self.db.save_lottery(data, is_vector=False):
logger.info(f"[add_lottery_result] Lưu kết quả thành công cho {dai} ngày {ngay}")
await self.cache.clear(type="lottery") # Sửa từ delete_all thành clear
return f"Đã thêm kết quả xổ số cho {dai} ngày {ngay}."
logger.error(f"[add_lottery_result] Failed to save data for {dai} ngày {ngay}")
return f"Lỗi khi lưu kết quả xổ số cho {dai} ngày {ngay}."
except Exception as e:
logger.error(f"[add_lottery_result] Lỗi: {str(e)}")
return f"Lỗi: {str(e)}"
async def load_lottery(self, text: str, override: bool = False) -> str:
try:
# Chuẩn hóa toàn bộ văn bản: thay thế tab và khoảng trắng liên tiếp
text = re.sub(r'[\t\s]+', ' ', text.strip())
# Khôi phục xuống dòng trước tên giải
for prize in self.prize_mapping:
text = re.sub(rf'({prize}\s)', f'\n\\1', text, flags=re.IGNORECASE)
lines = text.split('\n')
if not lines:
logger.debug("[load_lottery] Empty input text")
return "Lỗi: Văn bản rỗng."
if lines[0].lower().startswith(tuple(Settings.DAI_CONFIG.keys())):
parts = lines[0].split(" ", 2)
if len(parts) >= 2 and parts[0].lower() in Settings.DAI_CONFIG:
dai = parts[0].lower()
ngay = parts[1].replace("/", "-").replace(".", "-")
lines = lines[1:] if len(parts) == 2 else [parts[2]] + lines[1:]
else:
logger.debug(f"[load_lottery] Invalid format: {lines[0]}")
return "Lỗi: Định dạng không đúng. Vui lòng dùng: '<mã đài> DD-MM-YYYY' hoặc 'Xổ số <Đài> [Thứ X] DD-MM-YYYY'. Ví dụ: 'Xổ số Đắk Lắk 17-06-2025'."
else:
header_match = re.match(
r"Xổ số\s+(.+?)(?:\s+Thứ\s+\w+|Chủ\s+Nhật)?\s*(\d{2}[-./]\d{2}[-./]\d{4})\b",
lines[0],
re.IGNORECASE
)
if not header_match:
logger.debug(f"[load_lottery] Invalid header format: {lines[0]}")
return "Lỗi: Dòng đầu phải có định dạng 'Xổ số <Đài> [Thứ X/Chủ Nhật] DD-MM-YYYY'. Ví dụ: 'Xổ số Đắk Lắk 17-06-2025'."
dai_name = header_match.group(1).strip()
ngay = header_match.group(2).replace("/", "-").replace(".", "-")
dai = self._map_dai_name(dai_name)
parsed_date = await self.parse_date(ngay)
if not parsed_date:
logger.debug(f"[load_lottery] Invalid date format: {ngay}")
return f"Lỗi: Định dạng ngày không hợp lệ: {ngay}."
ngay_normalized, date = parsed_date
if dai in Settings.DAI_CONFIG and date.weekday() not in Settings.DAI_CONFIG[dai]["days"]:
day_name = date.strftime('%A')
expected_days = ', '.join([self._get_day_name(d) for d in Settings.DAI_CONFIG[dai]["days"]])
logger.debug(f"[load_lottery] Invalid day: {ngay} is {day_name}, expected {expected_days} for {dai}")
return f"Lỗi: Đài {dai} chỉ quay vào {expected_days}. Ngày {ngay}{day_name}."
giai_map = self.prize_mapping
expected_counts = {"DB": 1, "Nhat": 1, "Nhi": 1, "Ba": 2, "Tu": 7, "Nam": 1, "Sau": 3, "Bay": 1, "Tam": 1}
expected_lengths = {"DB": 6, "Nhat": 5, "Nhi": 5, "Ba": 5, "Tu": 5, "Nam": 4, "Sau": 4, "Bay": 3, "Tam": 2}
giai = {}
current_giai = None
numbers = []
error_lines = []
for i, line in enumerate(lines[1:], 1):
line = re.sub(r'[\t\s]+', ' ', line.strip())
line = re.sub(r'[.\-,;\s]+', ' ', line).strip()
logger.debug(f"[load_lottery] Processing line {i}: '{line}'")
if not line or "xổ số miền" in line.lower():
continue
for name, key in giai_map.items():
if re.match(rf"^{name}\b", line, re.IGNORECASE):
if current_giai and numbers:
giai[current_giai] = numbers
numbers = []
current_giai = key
line = re.sub(rf"^{name}\b", "", line, flags=re.IGNORECASE).strip()
break
if current_giai:
number_list = [num.strip() for num in line.split() if num.strip().isdigit()]
if not number_list:
error_lines.append(f"Dòng {i}: '{line}' (không tìm thấy số hợp lệ)")
numbers.extend(number_list)
logger.debug(f"[load_lottery] Extracted numbers for {current_giai}: {number_list}")
if current_giai and numbers:
giai[current_giai] = numbers
if not giai:
error_msg = "Lỗi: Không tìm thấy giải nào. Vui lòng kiểm tra định dạng kết quả, ví dụ: '/load_lottery Xổ số Đắk Lắk 17-06-2025\nĐặc biệt 123456\nGiải nhất 12345'."
if error_lines:
error_msg += "\nCác dòng lỗi:\n" + "\n".join(error_lines)
logger.debug(f"[load_lottery] No results parsed from input: {text}")
return error_msg
for key in expected_counts:
if key not in giai:
logger.debug(f"[load_lottery] Missing prize: {key}")
return f"Lỗi: Thiếu giải {key}. Vui lòng kiểm tra đầu vào."
if len(giai[key]) != expected_counts[key]:
logger.debug(f"[load_lottery] Incorrect number count for {key}: expected {expected_counts[key]}, got {len(giai[key])}")
return f"Lỗi: Giải {key} phải có {expected_counts[key]} số, nhận được {len(giai[key])} số."
for num in giai[key]:
if not self._validate_number(key, num):
logger.debug(f"[load_lottery] Invalid number for {key}: {num}")
return f"Lỗi: Số trong giải {key} phải có {expected_lengths[key]} chữ số và chỉ chứa số, nhận được '{num}'."
doc_id = f"lottery-{ngay_normalized}_{dai}"
if not override and (await self.db.lottery_results.document(doc_id).get()).exists:
logger.debug(f"[load_lottery] Data already exists: {doc_id}")
return f"Dữ liệu cho {dai} ngày {ngay} đã tồn tại."
data = {"ngay": ngay_normalized, "dai": dai, "giai": giai}
if await self.db.save_lottery(data, is_vector=False):
logger.info(f"[load_lottery] Lưu kết quả thành công cho {dai} ngày {ngay}")
await self.cache.clear(type="lottery") # Sửa từ delete_all thành clear
return f"Kết quả xổ số đã được nạp thành công: Đã nạp kết quả xổ số cho {dai} ngày {ngay}."
logger.error(f"[load_lottery] Failed to save data for {dai} ngày {ngay}")
return f"Lỗi khi lưu kết quả xổ số cho {dai} ngày {ngay}."
except Exception as e:
logger.error(f"[load_lottery] Lỗi: {str(e)}")
return f"Lỗi: {str(e)}"
async def load_from_json(self, json_file_path: str) -> str:
try:
if not os.path.exists(json_file_path) or os.path.getsize(json_file_path) == 0:
logger.debug(f"[load_from_json] File not found or empty: {json_file_path}")
return f"Tệp {json_file_path} không tồn tại hoặc rỗng."
with open(json_file_path, 'r', encoding='utf-8') as f:
data_list = json.load(f)
if not isinstance(data_list, list):
logger.debug("[load_from_json] JSON data is not a list")
return "Dữ liệu JSON không phải là danh sách."
batch_size = 100
success_count = 0
processed_keys = set()
batch = []
for data in data_list:
ngay = data.get("ngay")
dai = data.get("dai")
giai = data.get("giai")
if not all([ngay, dai, giai]):
logger.warning(f"[load_from_json] Bỏ qua bản ghi thiếu dữ liệu: ngay={ngay}, dai={dai}")
continue
parsed_date = await self.parse_date(ngay)
if not parsed_date:
logger.warning(f"[load_from_json] Ngày không hợp lệ: {ngay}")
continue
ngay_normalized, date = parsed_date
dai = self._map_dai_name(dai)
if dai not in Settings.DAI_CONFIG:
logger.error(f"[load_from_json] Đài không hợp lệ: {dai}, bỏ qua bản ghi.")
continue
if date.weekday() not in Settings.DAI_CONFIG[dai]["days"]:
logger.warning(f"[load_from_json] Ngày {ngay} không hợp lệ cho đài {dai}")
continue
key = f"lottery-{ngay_normalized}_{dai}"
if key in processed_keys:
continue
processed_keys.add(key)
data["ngay"] = ngay_normalized
data["dai"] = dai
batch.append(data)
if len(batch) >= batch_size:
for data in batch:
try:
async with asyncio.timeout(10):
if await self.db.save_lottery(data, is_vector=False):
success_count += 1
except asyncio.TimeoutError:
logger.error(f"[load_from_json] Timeout: {data['ngay']}_{data['dai']}")
except Exception as e:
logger.error(f"[load_from_json] Lỗi khi lưu: {str(e)}")
batch = []
if batch:
for data in batch:
try:
async with asyncio.timeout(10):
if await self.db.save_lottery(data, is_vector=False):
success_count += 1
except asyncio.TimeoutError:
logger.error(f"[load_from_json] Timeout: {data['ngay']}_{data['dai']}")
except Exception as e:
logger.error(f"[load_from_json] Lỗi khi lưu: {str(e)}")
logger.info(f"[load_from_json] Đã lưu {success_count} bản ghi")
return f"Đã tải và lưu {success_count} bản ghi từ {json_file_path}."
except json.JSONDecodeError:
logger.debug(f"[load_from_json] Invalid JSON in file: {json_file_path}")
return f"Lỗi: Tệp {json_file_path} không chứa JSON hợp lệ."
except Exception as e:
logger.error(f"[load_from_json] Lỗi: {str(e)}")
return f"Lỗi: {str(e)}"
async def migrate_lottery_data(self) -> str:
try:
logger.info("[migrate_lottery_data] Bắt đầu chuẩn hóa dữ liệu")
moved_results = 0
deleted_count = 0
async for doc in await self.db.stream(data_type="lottery-results"):
doc_id = doc.id
data = doc.to_dict()
ngay = data.get("ngay")
dai = data.get("dai")
if not ngay or not dai:
logger.warning(f"Skipping invalid document: {doc_id}, ngay={ngay}, dai={dai}")
continue
parsed_date = await self.parse_date(ngay)
if not parsed_date:
logger.warning(f"Skipping invalid date in {doc_id}: {ngay}")
continue
ngay_normalized, _ = parsed_date
new_dai = self._map_dai_name(dai)
data["ngay"] = ngay_normalized
data["dai"] = new_dai
new_doc_id = f"lottery-{ngay_normalized}_{new_dai}"
if doc_id != new_doc_id:
await self.db.lottery_results.document(new_doc_id).set(data, merge=True)
await self.db.lottery_results.document(doc_id).delete()
moved_results += 1
deleted_count += 1
else:
await self.db.lottery_results.document(doc_id).set(data, merge=True)
moved_results += 1
logger.info(f"[migrate_lottery_data] Hoàn tất: {moved_results} bản ghi, {deleted_count} bản xóa")
return f"Hoàn tất: {moved_results} bản ghi, {deleted_count} bản xóa."
except Exception as e:
logger.error(f"[migrate_lottery_data] Lỗi: {str(e)}")
return f"Lỗi: {str(e)}"
async def analyze(self, dai: str, start_date: str, end_date: str) -> dict:
try:
cache_key = f"lottery:{dai}:{start_date}:{end_date}"
cached = await self.cache.get(cache_key, type="lottery")
if cached:
return cached
parsed_start = await self.parse_date(start_date)
parsed_end = await self.parse_date(end_date)
if not parsed_start or not parsed_end:
logger.debug(f"[analyze] Invalid date format: start={start_date}, end={end_date}")
return {"frequencies": {}, "triplets": [], "error": "Định dạng ngày không hợp lệ."}
start_date_str, _ = parsed_start
end_date_str, _ = parsed_end
data = await self.db.get_lottery(dai, start_date_str, end_date_str, data_type="results", select_fields=["ngay", "dai", "giai"])
if not data:
logger.debug(f"[analyze] No data found for {dai} from {start_date} to {end_date}")
return {"frequencies": {}, "triplets": [], "error": f"Không tìm thấy dữ liệu cho {dai} từ {start_date} đến {end_date}."}
hang_data = {"trieu": [], "tramnghin": [], "chucnghin": [], "nghin": [], "chuc": [], "donvi": []}
for entry in data:
for giai, so_list in entry["giai"].items():
for so in so_list:
hang = self.tach_hang(so)
for key in hang_data:
hang_data[key].append(hang[key])
stats = {}
top_numbers = {}
for key in hang_data:
counter = Counter(x for x in hang_data[key] if x != '@')
stats[key] = [{"number": num, "count": count} for num, count in counter.items()]
top_numbers[key] = counter.most_common(1)[0][0] if counter else '@'
numbers = [top_numbers[key] for key in ["trieu", "tramnghin", "chucnghin", "nghin", "chuc", "donvi"]]
triplets = [''.join(t) for t in permutations(numbers, 3)]
counter = Counter(triplets)
top_triplets = [triplet for triplet, _ in counter.most_common(5)]
result = {
"frequencies": stats,
"triplets": top_triplets
}
await self.cache.set(cache_key, result, type="lottery")
return result
except Exception as e:
logger.error(f"[analyze] Lỗi: {str(e)}")
return {"frequencies": {}, "triplets": [], "error": f"Lỗi: {str(e)}"}
async def get_schedule(self, dai: str = None) -> str:
if dai:
dai = dai.lower()
if dai in Settings.DAI_CONFIG:
days = ', '.join([self._get_day_name(d) for d in Settings.DAI_CONFIG[dai]["days"]])
return f"Xổ số {Settings.DAI_CONFIG[dai]['name']} ({dai.upper()}) quay vào {days} hàng tuần."
logger.debug(f"[get_schedule] Unknown dai: {dai}")
return f"Lỗi: Không tìm thấy lịch cho đài {dai}."
result = "Lịch xổ số các đài:\n"
for dai_code, config in Settings.DAI_CONFIG.items():
days = ', '.join([self._get_day_name(d) for d in config["days"]])
result += f"- {dai_code.upper()} ({config['name']}): {days}\n"
result += "Gửi tên đài để xem chi tiết, ví dụ: 'Lịch XSKH'."
return result
async def add_lottery_vectors(self, json_file_path: str) -> str:
logger.info("[add_lottery_vectors] Tính năng vector đã bị tắt")
return "Tính năng lưu vector xổ số đã bị tắt."