2026-04-01 13:36:06 +08:00
|
|
|
from pydantic_settings import BaseSettings
|
|
|
|
|
from functools import lru_cache
|
|
|
|
|
import os
|
|
|
|
|
|
2026-04-04 16:23:33 +08:00
|
|
|
import yaml
|
|
|
|
|
|
2026-04-01 13:36:06 +08:00
|
|
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
|
|
|
APP_NAME: str = "TianpuEMS"
|
|
|
|
|
DEBUG: bool = True
|
|
|
|
|
API_V1_PREFIX: str = "/api/v1"
|
|
|
|
|
|
2026-04-04 16:23:33 +08:00
|
|
|
# Customer configuration
|
|
|
|
|
CUSTOMER: str = "tianpu" # tianpu, zpark, etc.
|
|
|
|
|
CUSTOMER_DISPLAY_NAME: str = "" # Loaded from customer config
|
|
|
|
|
|
2026-04-02 18:46:42 +08:00
|
|
|
# Database: set DATABASE_URL in .env to override.
|
|
|
|
|
# Default: SQLite for local dev. Docker sets PostgreSQL via env var.
|
|
|
|
|
# Examples:
|
|
|
|
|
# SQLite: sqlite+aiosqlite:///./tianpu_ems.db
|
|
|
|
|
# PostgreSQL: postgresql+asyncpg://tianpu:tianpu2026@localhost:5432/tianpu_ems
|
2026-04-01 13:36:06 +08:00
|
|
|
DATABASE_URL: str = "sqlite+aiosqlite:///./tianpu_ems.db"
|
|
|
|
|
REDIS_URL: str = "redis://localhost:6379/0"
|
|
|
|
|
|
|
|
|
|
SECRET_KEY: str = "tianpu-ems-secret-key-change-in-production-2026"
|
|
|
|
|
ALGORITHM: str = "HS256"
|
|
|
|
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 480
|
|
|
|
|
|
2026-04-02 18:46:42 +08:00
|
|
|
CELERY_ENABLED: bool = False # Set True when Celery worker is running
|
|
|
|
|
USE_SIMULATOR: bool = True # True=simulator mode, False=real IoT collectors
|
|
|
|
|
|
feat: enterprise-level enhancement — 12 modules complete
New modules:
- Energy Quota Management (定额管理)
- Cost/Expense Analysis with TOU pricing (费用分析)
- Sub-item Energy Analysis (分项分析)
- EV Charging Station Management (充电桩管理) — 8 models, 6 pages
- Enhanced Energy Analysis — loss, YoY, MoM comparison
- Alarm Analytics — trends, MTTR, top devices, rule toggle
- Maintenance & Work Orders (运维管理) — inspections, repair orders, duty
- Data Query Module (数据查询)
- Equipment Topology (设备拓扑)
- Management System (管理体系) — regulations, standards, processes
Infrastructure:
- Redis caching layer with decorator
- Redis Streams data ingestion buffer
- Hourly/daily/monthly aggregation engine
- Rate limiting & request ID middleware
- 6 Alembic migrations (003-008), 21 new tables
- Extended seed data for all modules
Stats: 120+ API routes, 12 pages, 27 tabs, 37 database tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:06:16 +08:00
|
|
|
# Infrastructure flags
|
|
|
|
|
TIMESCALE_ENABLED: bool = False
|
|
|
|
|
REDIS_ENABLED: bool = True
|
|
|
|
|
INGESTION_QUEUE_ENABLED: bool = False
|
|
|
|
|
AGGREGATION_ENABLED: bool = True
|
|
|
|
|
|
feat: add system settings, audit log, device detail, dark mode, i18n, email notifications
System Management:
- System Settings page with 8 configurable parameters (admin only)
- Audit Log page with filterable table (user, action, resource, date range)
- Audit logging wired into auth, devices, users, alarms, reports API handlers
- SystemSetting model + migration (002)
Device Detail:
- Dedicated /devices/:id page with 4 tabs (realtime, historical trends, alarm history, device info)
- ECharts historical charts with granularity/time range selectors
- Device name clickable in Devices and Monitoring tables → navigates to detail
Email & Scheduling:
- Email service with SMTP support (STARTTLS/SSL/plain)
- Alarm email notification with professional HTML template
- Report scheduler using APScheduler for cron-based auto-generation
- Scheduled report task seeded (daily at 8am)
UI Enhancements:
- Dark mode toggle (persisted to localStorage, Ant Design darkAlgorithm)
- Data comparison view in Analysis page (dual date range, side-by-side metrics)
- i18n framework (i18next) with zh/en translations for menu and common UI
- Language switcher in header (中文/English)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:42:22 +08:00
|
|
|
# SMTP Email settings
|
|
|
|
|
SMTP_HOST: str = ""
|
|
|
|
|
SMTP_PORT: int = 587
|
|
|
|
|
SMTP_USER: str = ""
|
|
|
|
|
SMTP_PASSWORD: str = ""
|
|
|
|
|
SMTP_FROM: str = "noreply@tianpu-ems.com"
|
|
|
|
|
SMTP_ENABLED: bool = False
|
|
|
|
|
|
|
|
|
|
# Platform URL for links in emails
|
|
|
|
|
PLATFORM_URL: str = "http://localhost:3000"
|
|
|
|
|
|
2026-04-02 18:46:42 +08:00
|
|
|
@property
|
|
|
|
|
def DATABASE_URL_SYNC(self) -> str:
|
|
|
|
|
"""Derive synchronous URL from async DATABASE_URL for Alembic."""
|
|
|
|
|
url = self.DATABASE_URL
|
|
|
|
|
return url.replace("+aiosqlite", "").replace("+asyncpg", "+psycopg2")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_sqlite(self) -> bool:
|
|
|
|
|
return "sqlite" in self.DATABASE_URL
|
|
|
|
|
|
2026-04-04 16:23:33 +08:00
|
|
|
@property
|
|
|
|
|
def customer_config_path(self) -> str:
|
|
|
|
|
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
|
|
|
|
"..", "customers", self.CUSTOMER)
|
|
|
|
|
|
|
|
|
|
def load_customer_config(self) -> dict:
|
|
|
|
|
"""Load customer-specific config from customers/{CUSTOMER}/config.yaml"""
|
|
|
|
|
config_file = os.path.join(self.customer_config_path, "config.yaml")
|
|
|
|
|
if os.path.exists(config_file):
|
|
|
|
|
with open(config_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
return yaml.safe_load(f) or {}
|
|
|
|
|
return {}
|
|
|
|
|
|
2026-04-01 13:36:06 +08:00
|
|
|
class Config:
|
|
|
|
|
env_file = ".env"
|
|
|
|
|
extra = "ignore"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@lru_cache
|
|
|
|
|
def get_settings() -> Settings:
|
|
|
|
|
return Settings()
|