""" 家庭阅读激励工具 - 种子数据脚本 ================================ 提供演示/测试用种子数据,覆盖所有6张业务表。 数据设计符合典型家庭场景:2个孩子、5本书、打卡记录、积分流水、 3个奖品、兑换记录。 使用方式: # 直接运行 python 06-seed_data.py # 或在代码中调用 from seed_data import seed_all seed_all() """ import sqlite3 import os import sys import logging from datetime import date, timedelta from typing import Optional logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) # 确保可以导入项目模块 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from db_config import init_db, get_db, transaction # ============================================================ # 种子数据定义 # ============================================================ def seed_all(db_path: str = None) -> None: """ 执行全部种子数据写入(幂等:使用INSERT OR IGNORE跳过重复数据)。 包含:2个孩子、5本书、若干打卡记录、积分流水、3个奖品、兑换记录。 """ if db_path: os.environ["READING_APP_DB_PATH"] = db_path # 先确保Schema已初始化 init_db() db = get_db() try: with transaction(db) as conn: _seed_children(conn) _seed_books(conn) # 以上必须先于打卡记录(外键依赖) _seed_reading_records(conn) _seed_rewards(conn) _seed_redemptions(conn) _seed_app_settings(conn) logger.info("✅ 种子数据写入完成") except Exception as e: logger.error("❌ 种子数据写入失败: %s", e) raise finally: db.close() def _seed_children(conn: sqlite3.Connection) -> None: """写入2个孩子""" children = [ ("小明", None, 8, 85, 1), ("小红", None, 6, 60, 1), ] for name, avatar, age, points, active in children: conn.execute( "INSERT OR IGNORE INTO children (name, avatar_path, age, total_points, is_active) VALUES (?, ?, ?, ?, ?)", (name, avatar, age, points, active), ) logger.info(" 孩子: 2条") def _seed_books(conn: sqlite3.Connection) -> None: """写入5本书""" books = [ ("小王子", "安托万·德·圣-埃克苏佩里", None, None, 120), ("夏洛的网", "E·B·怀特", None, None, 184), ("西游记(少儿版)", "吴承恩", None, None, 96), ("猜猜我有多爱你", "山姆·麦克布雷尼", None, None, 32), ("了不起的狐狸爸爸", "罗尔德·达尔", None, None, 96), ] for title, author, cover, thumb, pages in books: conn.execute( "INSERT OR IGNORE INTO books (title, author, cover_image_path, thumbnail_path, page_count) VALUES (?, ?, ?, ?, ?)", (title, author, cover, thumb, pages), ) logger.info(" 书籍: 5条") def _seed_reading_records(conn: sqlite3.Connection) -> None: """写入最近7天的打卡记录""" today = date.today() children_ids = [1, 2] # 小明、小红 book_ids = [1, 2, 3, 4, 5] records = [] # 小明:最近7天,每天1-2本书 for days_ago in range(7): d = (today - timedelta(days=days_ago)).isoformat() for book_id in book_ids[:2 + (days_ago % 2)]: # 交替1-2本 records.append((children_ids[0], book_id, d, 20 + (days_ago % 3) * 5, 5 + days_ago * 2, "小明阅读笔记")) # 小红:最近5天,每天1本书 for days_ago in range(5): d = (today - timedelta(days=days_ago)).isoformat() records.append((children_ids[1], book_ids[2 + (days_ago % 3)], d, 15 + days_ago * 2, 3 + days_ago, "小红阅读笔记")) for child_id, book_id, read_date, duration, pages, notes in records: try: conn.execute( "INSERT INTO reading_records (child_id, book_id, read_date, duration_minutes, pages_read, notes) VALUES (?, ?, ?, ?, ?, ?)", (child_id, book_id, read_date, duration, pages, notes), ) except sqlite3.IntegrityError: pass # 已存在则跳过(UNIQUE约束) logger.info(" 打卡记录: %d条", len(records)) # 更新孩子积分(每次打卡+10分,与ReadingRecordDAO一致) for child_id in children_ids: count = conn.execute( "SELECT COUNT(*) as cnt FROM reading_records WHERE child_id=?", (child_id,) ).fetchone()["cnt"] conn.execute( "UPDATE children SET total_points = total_points + ?, updated_at = datetime('now','localtime') WHERE id=?", (count * 10, child_id), ) # 写入积分流水 for child_id, book_id, read_date, duration, pages, notes in records: record_row = conn.execute( "SELECT id FROM reading_records WHERE child_id=? AND book_id=? AND read_date=?", (child_id, book_id, read_date), ).fetchone() if record_row: bal = conn.execute( "SELECT total_points FROM children WHERE id=?", (child_id,) ).fetchone()["total_points"] conn.execute( "INSERT OR IGNORE INTO points (child_id, points_change, reason, reference_type, reference_id, balance_after) VALUES (?, 10, 'reading', 'reading_record', ?, ?)", (child_id, record_row["id"], bal), ) logger.info(" 积分流水: %d条", len(records)) def _seed_rewards(conn: sqlite3.Connection) -> None: """写入3个奖品""" rewards = [ ("多看30分钟动画片", "兑换后可多看30分钟动画片", None, 30, -1, 1), ("周末去游乐园", "本周六/日任选一天去游乐园", None, 80, -1, 1), ("新绘本一本", "可挑选一本新的绘本", None, 50, 10, 1), ] for name, desc, img, points, stock, active in rewards: conn.execute( "INSERT OR IGNORE INTO rewards (name, description, image_path, points_required, stock, is_active) VALUES (?, ?, ?, ?, ?, ?)", (name, desc, img, points, stock, active), ) logger.info(" 奖品: 3条") def _seed_redemptions(conn: sqlite3.Connection) -> None: """写入2条兑换记录(演示数据)""" redemptions = [ (1, 1, 30, "fulfilled"), # 小明兑换了"多看30分钟动画片" (2, 2, 80, "fulfilled"), # 小红兑换了"周末去游乐园" ] for child_id, reward_id, points, status in redemptions: conn.execute( "INSERT OR IGNORE INTO redemption_records (child_id, reward_id, points_spent, status) VALUES (?, ?, ?, ?)", (child_id, reward_id, points, status), ) # 对应的积分扣减流水 bal = conn.execute( "SELECT total_points FROM children WHERE id=?", (child_id,) ).fetchone()["total_points"] conn.execute( "INSERT OR IGNORE INTO points (child_id, points_change, reason, reference_type, reference_id, balance_after) VALUES (?, ?, 'redemption', 'redemption_record', ?, ?)", (child_id, -points, child_id, bal), ) logger.info(" 兑换记录: 2条") def _seed_app_settings(conn: sqlite3.Connection) -> None: """写入默认应用配置""" settings = { "points_per_reading": "10", "app_name": "家庭阅读激励工具", "max_children": "5", } for key, value in settings.items(): conn.execute( "INSERT OR IGNORE INTO app_settings (key, value) VALUES (?, ?)", (key, value), ) logger.info(" 应用配置: %d条", len(settings)) # ============================================================ # CLI入口 # ============================================================ if __name__ == "__main__": print("=" * 50) print("家庭阅读激励工具 - 种子数据写入") print("=" * 50) seed_all() print("\n种子数据写入完成!") print(" - 2个孩子: 小明(8岁), 小红(6岁)") print(" - 5本书: 小王子, 夏洛的网, 西游记, 猜猜我有多爱你, 了不起的狐狸爸爸") print(" - 最近7天打卡记录 + 积分流水") print(" - 3个奖品: 动画片30分钟, 游乐园, 新绘本") print(" - 2条兑换记录")