Spaces:
Runtime error
Runtime error
File size: 6,648 Bytes
dc610d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
Database setup and models for ZenVolt asset management.
Uses SQLAlchemy with SQLite for simplicity.
"""
from sqlalchemy import create_engine, Column, String, Float, Integer, DateTime, Text, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from datetime import datetime
import json
from typing import Optional, List, Dict
import os
# Database URL (SQLite for local dev, PostgreSQL for Production)
DATABASE_URL = os.getenv("DATABASE_URL")
# Detect Container environment (Docker/Hugging Face)
IS_DOCKER = os.path.exists("/.dockerenv") or os.environ.get("HOSTNAME", "").endswith("-space")
if not DATABASE_URL:
if IS_DOCKER:
# Check if we have a persistent volume mount
if os.path.exists("/data"):
DATABASE_URL = "sqlite:////data/zenvolt_assets.db"
else:
# Fallback to /tmp for writable storage in ephemeral containers
DATABASE_URL = "sqlite:////tmp/zenvolt_assets.db"
else:
# Local development
DATABASE_URL = "sqlite:///./zenvolt_assets.db"
# SQLite specific config
is_sqlite = DATABASE_URL.startswith("sqlite")
connect_args = {"check_same_thread": False} if is_sqlite else {}
engine = create_engine(DATABASE_URL, connect_args=connect_args)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Database Models
class Asset(Base):
"""Battery Asset model"""
__tablename__ = "assets"
id = Column(String, primary_key=True, index=True)
owner_id = Column(String, index=True) # User who owns this asset
project_name = Column(String, nullable=False)
asset_id = Column(String, unique=True, nullable=False, index=True) # e.g., "BESS_01"
location = Column(String)
capacity = Column(Float) # kWh
bms_provider = Column(String)
category = Column(String) # JSON array as string: ["Grid-Scale", "Certified"]
project_stage = Column(String) # Certified, Monitoring, Under Review, Pending
# Health metrics (latest)
health_score = Column(Float) # 0-100
remaining_life = Column(Float) # years
degradation_rate = Column(Float) # % per year
confidence = Column(Float) # 0-1
# Operational data
state_of_charge = Column(Float) # percentage
voltage = Column(Float) # V
temperature = Column(Float) # °C
is_online = Column(Integer, default=1) # Boolean as int
# NFT/Blockchain (optional)
nft_hash = Column(String)
nft_mint_date = Column(DateTime)
block_explorer_url = Column(String)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
last_prediction_date = Column(DateTime)
# Relationships
predictions = relationship("HealthPrediction", back_populates="asset", cascade="all, delete-orphan")
def to_dict(self) -> Dict:
"""Convert to dictionary for API response"""
return {
"id": self.id,
"owner_id": self.owner_id,
"projectName": self.project_name,
"assetId": self.asset_id,
"location": self.location,
"capacity": self.capacity,
"bmsProvider": self.bms_provider,
"category": json.loads(self.category) if self.category else [],
"projectStage": self.project_stage,
"healthScore": self.health_score,
"remainingLife": self.remaining_life,
"degradationRate": self.degradation_rate,
"confidence": self.confidence,
"stateOfCharge": self.state_of_charge,
"voltage": self.voltage,
"temperature": self.temperature,
"isOnline": bool(self.is_online),
"nftHash": self.nft_hash,
"nftMintDate": self.nft_mint_date.isoformat() if self.nft_mint_date else None,
"blockExplorerUrl": self.block_explorer_url,
"createdAt": self.created_at.isoformat() if self.created_at else None,
"updatedAt": self.updated_at.isoformat() if self.updated_at else None,
"lastUpdated": self.last_prediction_date.isoformat() if self.last_prediction_date else None,
}
class HealthPrediction(Base):
"""Historical health predictions for assets"""
__tablename__ = "health_predictions"
id = Column(Integer, primary_key=True, autoincrement=True)
asset_id = Column(String, ForeignKey("assets.id"), nullable=False, index=True)
# Prediction timestamp
prediction_date = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
cycle_index = Column(Integer) # Cycle number at time of prediction
# Health metrics
health_score = Column(Float, nullable=False) # 0-100
current_soh = Column(Float) # 0-1
soh_lower_95 = Column(Float) # Confidence interval
soh_upper_95 = Column(Float)
remaining_life_years = Column(Float)
remaining_life_cycles = Column(Float)
degradation_rate = Column(Float) # % per year
confidence = Column(Float) # 0-1
data_quality = Column(Float) # 0-1
# SHAP explanation (top features as JSON)
shap_top_features = Column(Text) # JSON string
# Relationship
asset = relationship("Asset", back_populates="predictions")
def to_dict(self) -> Dict:
"""Convert to dictionary for API response"""
return {
"id": self.id,
"asset_id": self.asset_id,
"prediction_date": self.prediction_date.isoformat() if self.prediction_date else None,
"cycle_index": self.cycle_index,
"health_score": self.health_score,
"current_soh": self.current_soh,
"soh_lower_95": self.soh_lower_95,
"soh_upper_95": self.soh_upper_95,
"remaining_life_years": self.remaining_life_years,
"remaining_life_cycles": self.remaining_life_cycles,
"degradation_rate": self.degradation_rate,
"confidence": self.confidence,
"data_quality": self.data_quality,
"shap_top_features": json.loads(self.shap_top_features) if self.shap_top_features else None,
}
# Database session dependency for FastAPI
def get_db():
"""Dependency to get database session"""
db = SessionLocal()
try:
yield db
finally:
db.close()
# Initialize database
def init_db():
"""Create all tables"""
Base.metadata.create_all(bind=engine)
print("✓ Database initialized")
if __name__ == "__main__":
init_db()
|