## Technical Design

### 1. SQLite → aiosqlite

\\\python
# room_store.py
import aiosqlite

class RoomStore:
    async def _get_conn(self) -> aiosqlite.Connection:
        db_path = Path(self.config.DB_PATH)
        db_path.parent.mkdir(parents=True, exist_ok=True)
        conn = await aiosqlite.connect(str(db_path))
        conn.row_factory = aiosqlite.Row
        await conn.execute('''CREATE TABLE IF NOT EXISTS race_history (...)''')
        await conn.commit()
        return conn

    async def get_last_horse_name(self, user_id: str) -> Optional[str]:
        conn = await self._get_conn()
        try:
            cursor = await conn.execute("SELECT horse_name FROM ...", (user_id,))
            row = await cursor.fetchone()
            return row[0] if row else None
        finally:
            await conn.close()
\\\

> 每次操作独立连接和关闭保持与原逻辑一致，仅将同步调用替换为 async。

### 2. 积分重试加延时 + 区分失败原因

\\\python
# points_service.py
async def spend_bet_points(self, user_id: str, amount: int, reason: str = "赛马下注"):
    success, balance = await points_api.spend_points(user_id, amount, "horse_race", reason)
    if success:
        return True, balance
    # 余额不足不再重试
    if balance < amount:
        return False, balance
    # 网络/其他失败，短暂等待后重试一次
    await asyncio.sleep(1.0)
    success, balance = await points_api.spend_points(user_id, amount, "horse_race", reason)
    return success, balance
\\\

### 3. 消息发送加日志

\\\python
# commands.py
import logging
logger = logging("group_horse_racing")

async def _send_to_scope(bot: Bot, scope: str, message: ...):
    try:
        if scope.startswith("group_"):
            await bot.send_group_msg(...)
        elif scope.startswith("test_"):
            await bot.send_private_msg(...)
    except Exception:
        logger.warning(f"发送消息到 {scope} 失败", exc_info=True)
\\\

### 4. 赔率快照（结算时锁内固定）

在比赛结束结算时，将赔率在 Room Lock 内计算后传入结算函数，保证赔率与下注分布一致：
\\\python
# 在 start_race 的 lock 内计算赔率快照
odds_snapshot = {}
for horse_name, horse in room.horses.items():
    odds_snapshot[horse_name] = room.calculate_odds(horse_name)  # 锁内计算
# 传入结算函数使用快照赔率
await _settle_race(bot, event, room, config, odds_snapshot)
\\\

### 5. 补存积分变化

\\\python
# room_store.py - save_race_result
await conn.execute(
    "INSERT INTO race_history (...point_changes, point_change_summaries) VALUES (..., ?, ?)",
    (...,
     json.dumps(result.point_changes),
     json.dumps(result.point_change_summaries))
)
\\\

需在 CREATE TABLE 中新增两个 TEXT 字段，使用 \IF NOT EXISTS\ 保证兼容。

### 6. 测试数据隔离

\\\python
# test_commands.py
from .commands import get_scope, check_access, race_engine, _rooms as prod_rooms
from .room_store import RoomStore as _RealRoomStore

# 测试命令用独立 store
_test_store = _RealRoomStore(config)  # 或用内存 store
\\\

> 不再共享生产 room_store 实例，测试操作独立数据。

### 7. 马名去重统一

将 dict key 统一用 normalized name（casefold），或在注册时统一用原始名但比较用 casefold。
