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>
80 lines
2.5 KiB
Python
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()
|