- Add company module (3-tier org, CEO planning, parallel departments) - Add company orchestrator, knowledge extractor, presets, scheduler - Add company API endpoints, models, and frontend views - Add 今天吃啥 PWA app (69 dishes, real images, offline support) - Add team_projects output directory structure - Add unified manage.ps1 for service lifecycle - Add Windows startup guide v1.0 - Add TTS troubleshooting doc - Update frontend (AgentChat UX overhaul, new views) - Update backend (voice engine fix, multi-tenant, RBAC) - Remove deprecated startup scripts and old docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
218 lines
8.2 KiB
Python
218 lines
8.2 KiB
Python
"""
|
||
家庭阅读激励工具 - 种子数据脚本
|
||
================================
|
||
提供演示/测试用种子数据,覆盖所有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条兑换记录")
|