Files
aiagent/team_projects/b9b14925-f054-43d0-b2f0-6ed6b38dbe4b/Company-context-SQLiteFlutter-React-Nati/08-points_config.py
renjianbo f2e65a8fbb feat: virtual company module, team projects, PWA dishes app, and startup scripts overhaul
- 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>
2026-07-10 22:37:56 +08:00

171 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ============================================================================
# 家庭阅读激励工具 - 积分规则配置
# ============================================================================
"""
可配置的积分规则引擎配置模块。
设计原则:
1. 所有积分参数可调,家长端可通过 API 修改
2. 支持多种计分维度:基础打卡、连续天数加成、时长加成、页数加成
3. 配置持久化在 points_config 表中(首次运行时自动创建)
4. 默认配置开箱即用,无需手动设置
积分公式:
total = base_points
+ streak_bonus(consecutive_days)
+ duration_bonus(minutes)
+ pages_bonus(pages)
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
from db_config import get_connection, transaction
# ============================================================================
# 配置表 DDL(自动迁移)
# ============================================================================
CONFIG_TABLE_DDL = """
CREATE TABLE IF NOT EXISTS points_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
description TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
)
"""
def _ensure_config_table() -> None:
"""确保配置表存在(幂等操作)。"""
conn = get_connection()
conn.execute(CONFIG_TABLE_DDL)
conn.execute("""
INSERT OR IGNORE INTO points_config (key, value, description)
VALUES
('base_points', '10', '每日基础打卡积分'),
('streak_enabled', 'true', '是否启用连续打卡加成'),
('streak_threshold_7', '5', '连续7天额外加分'),
('streak_threshold_14', '10', '连续14天额外加分'),
('streak_threshold_30', '20', '连续30天额外加分'),
('duration_enabled', 'true', '是否启用阅读时长加成'),
('duration_unit_minutes', '10', '每多少分钟加一次分'),
('duration_points_per_unit', '2', '每单位时长加多少分'),
('duration_max_bonus', '20', '时长加成每日上限'),
('pages_enabled', 'true', '是否启用页数加成'),
('pages_per_point', '20', '每多少页加1分'),
('pages_max_bonus', '10', '页数加成每日上限'),
('max_daily_points', '50', '每日积分获取硬上限(含所有加成)')
""")
conn.commit()
# ============================================================================
# 积分配置数据类
# ============================================================================
@dataclass
class PointsConfig:
"""积分规则配置(从数据库读取,提供默认值)。"""
base_points: int = 10
streak_enabled: bool = True
streak_threshold_7: int = 5 # 连续7天额外 +5
streak_threshold_14: int = 10 # 连续14天额外 +10
streak_threshold_30: int = 20 # 连续30天额外 +20
duration_enabled: bool = True
duration_unit_minutes: int = 10 # 每10分钟
duration_points_per_unit: int = 2 # 得2分
duration_max_bonus: int = 20 # 每日上限
pages_enabled: bool = True
pages_per_point: int = 20 # 每20页1分
pages_max_bonus: int = 10 # 每日上限
max_daily_points: int = 50 # 每日硬上限
@classmethod
def load(cls) -> "PointsConfig":
"""
从数据库加载配置,合并默认值。
配置表中存在的 key 覆盖默认值,不存在的保留默认值。
"""
_ensure_config_table()
rows = get_connection().execute(
"SELECT key, value FROM points_config"
).fetchall()
db_config: Dict[str, str] = {r["key"]: r["value"] for r in rows}
def _get_int(key: str, default: int) -> int:
return int(db_config.get(key, str(default)))
def _get_bool(key: str, default: bool) -> bool:
val = db_config.get(key, str(default).lower())
return val.lower() in ("true", "1", "yes")
return cls(
base_points=_get_int("base_points", 10),
streak_enabled=_get_bool("streak_enabled", True),
streak_threshold_7=_get_int("streak_threshold_7", 5),
streak_threshold_14=_get_int("streak_threshold_14", 10),
streak_threshold_30=_get_int("streak_threshold_30", 20),
duration_enabled=_get_bool("duration_enabled", True),
duration_unit_minutes=_get_int("duration_unit_minutes", 10),
duration_points_per_unit=_get_int("duration_points_per_unit", 2),
duration_max_bonus=_get_int("duration_max_bonus", 20),
pages_enabled=_get_bool("pages_enabled", True),
pages_per_point=_get_int("pages_per_point", 20),
pages_max_bonus=_get_int("pages_max_bonus", 10),
max_daily_points=_get_int("max_daily_points", 50),
)
def save(self) -> None:
"""将当前配置持久化到数据库。"""
_ensure_config_table()
fields = {
"base_points": str(self.base_points),
"streak_enabled": str(self.streak_enabled).lower(),
"streak_threshold_7": str(self.streak_threshold_7),
"streak_threshold_14": str(self.streak_threshold_14),
"streak_threshold_30": str(self.streak_threshold_30),
"duration_enabled": str(self.duration_enabled).lower(),
"duration_unit_minutes": str(self.duration_unit_minutes),
"duration_points_per_unit": str(self.duration_points_per_unit),
"duration_max_bonus": str(self.duration_max_bonus),
"pages_enabled": str(self.pages_enabled).lower(),
"pages_per_point": str(self.pages_per_point),
"pages_max_bonus": str(self.pages_max_bonus),
"max_daily_points": str(self.max_daily_points),
}
with transaction() as conn:
for key, value in fields.items():
conn.execute(
"""INSERT INTO points_config (key, value, updated_at)
VALUES (?, ?, datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at""",
(key, value),
)
def to_dict(self) -> Dict[str, Any]:
"""导出为字典,供前端展示。"""
return {
"base_points": self.base_points,
"streak_enabled": self.streak_enabled,
"streak_threshold_7": self.streak_threshold_7,
"streak_threshold_14": self.streak_threshold_14,
"streak_threshold_30": self.streak_threshold_30,
"duration_enabled": self.duration_enabled,
"duration_unit_minutes": self.duration_unit_minutes,
"duration_points_per_unit": self.duration_points_per_unit,
"duration_max_bonus": self.duration_max_bonus,
"pages_enabled": self.pages_enabled,
"pages_per_point": self.pages_per_point,
"pages_max_bonus": self.pages_max_bonus,
"max_daily_points": self.max_daily_points,
}