Files
tianpu-ems/backend/app/core/config.py
Du Wenbo 02c4698b59 feat: multi-customer architecture + Z-Park support + Gitea migration scripts
Multi-customer config system:
- CUSTOMER env var selects customer (tianpu/zpark)
- customers/tianpu/config.yaml — Tianpu branding, collectors, features
- customers/zpark/config.yaml — Z-Park branding, Sungrow collector
- GET /api/v1/branding endpoint for customer-specific branding
- main.py loads customer config for app title, CORS, logging
- Collector manager filters by customer's enabled collectors

Z-Park (中关村医疗器械园) support:
- Sungrow iSolarCloud API collector (sungrow_collector.py)
- Z-Park device definitions (10 inverters, 8 combiner boxes, 22+ buildings)
- Z-Park TOU pricing config (Beijing 2026 rates)
- Z-Park seed script (seed_zpark.py)

Gitea migration scripts (Mac Studio → labmac3):
- 5 migration scripts + README in scripts/gitea-migration/
- Creates 3-repo structure: ems-core, tp-ems, zpark-ems

Version: v1.0.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 16:23:33 +08:00

80 lines
2.5 KiB
Python

from pydantic_settings import BaseSettings
from functools import lru_cache
import os
import yaml
class Settings(BaseSettings):
APP_NAME: str = "TianpuEMS"
DEBUG: bool = True
API_V1_PREFIX: str = "/api/v1"
# Customer configuration
CUSTOMER: str = "tianpu" # tianpu, zpark, etc.
CUSTOMER_DISPLAY_NAME: str = "" # Loaded from customer config
# 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
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
CELERY_ENABLED: bool = False # Set True when Celery worker is running
USE_SIMULATOR: bool = True # True=simulator mode, False=real IoT collectors
# Infrastructure flags
TIMESCALE_ENABLED: bool = False
REDIS_ENABLED: bool = True
INGESTION_QUEUE_ENABLED: bool = False
AGGREGATION_ENABLED: bool = True
# 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"
@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
@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 {}
class Config:
env_file = ".env"
extra = "ignore"
@lru_cache
def get_settings() -> Settings:
return Settings()