scorecardapi / rule_repository.py
zombee11's picture
Upload 34 files
7872230 verified
Raw
History Blame Contribute Delete
4.41 kB
import json
from decimal import Decimal
from typing import Any
from redis import Redis
from sqlalchemy import and_, or_, select
from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.models.rule import RuleMaster
def _serialize_rule(rule: RuleMaster) -> dict[str, Any]:
return {
"id": rule.id,
"tenant_id": rule.tenant_id,
"product_type": rule.product_type,
"dealer_id": rule.dealer_id,
"rule_code": rule.rule_code,
"rule_name": rule.rule_name,
"rule_type": rule.rule_type,
"field_name": rule.field_name,
"operator": rule.operator,
"field_value": rule.field_value,
"score_value": str(rule.score_value) if rule.score_value is not None else None,
"score_component": rule.score_component,
"decision_action": rule.decision_action,
"reason_code": rule.reason_code,
"priority": rule.priority,
"version": rule.version,
"is_active": rule.is_active,
"created_by": rule.created_by,
"created_at": rule.created_at.isoformat(),
"updated_at": rule.updated_at.isoformat(),
}
def _hydrate_rule(data: dict[str, Any]) -> RuleMaster:
rule = RuleMaster(**{k: v for k, v in data.items() if k not in {"created_at", "updated_at"}})
if data.get("score_value") is not None:
rule.score_value = Decimal(data["score_value"])
return rule
class RuleRepository:
def __init__(self) -> None:
self.settings = get_settings()
self.redis: Redis | None = None
if self.settings.redis_url:
try:
self.redis = Redis.from_url(self.settings.redis_url, decode_responses=True)
except Exception:
self.redis = None
def cache_key(self, tenant_id: str, product_type: str, dealer_id: str | None) -> str:
dealer = dealer_id or "ALL"
return f"rules:{tenant_id}:{product_type}:{dealer}"
def get_active_rules(
self, db: Session, tenant_id: str, product_type: str, dealer_id: str | None
) -> list[RuleMaster]:
key = self.cache_key(tenant_id, product_type, dealer_id)
if self.redis:
try:
cached = self.redis.get(key)
if cached:
return [_hydrate_rule(item) for item in json.loads(cached)]
except Exception:
pass
stmt = (
select(RuleMaster)
.where(
RuleMaster.is_active.is_(True),
RuleMaster.tenant_id == tenant_id,
RuleMaster.product_type == product_type,
or_(RuleMaster.dealer_id.is_(None), RuleMaster.dealer_id == dealer_id),
)
.order_by(RuleMaster.priority.asc(), RuleMaster.id.asc())
)
rules = list(db.scalars(stmt).all())
if self.redis:
try:
self.redis.setex(key, 300, json.dumps([_serialize_rule(rule) for rule in rules]))
except Exception:
pass
return rules
def invalidate(self, tenant_id: str, product_type: str, dealer_id: str | None = None) -> None:
if not self.redis:
return
keys = [
self.cache_key(tenant_id, product_type, dealer_id),
self.cache_key(tenant_id, product_type, None),
]
try:
self.redis.delete(*set(keys))
except Exception:
pass
def list_rules(
self,
db: Session,
tenant_id: str | None = None,
product_type: str | None = None,
dealer_id: str | None = None,
rule_type: str | None = None,
is_active: bool | None = None,
) -> list[RuleMaster]:
filters = []
if tenant_id:
filters.append(RuleMaster.tenant_id == tenant_id)
if product_type:
filters.append(RuleMaster.product_type == product_type)
if dealer_id:
filters.append(RuleMaster.dealer_id == dealer_id)
if rule_type:
filters.append(RuleMaster.rule_type == rule_type)
if is_active is not None:
filters.append(RuleMaster.is_active.is_(is_active))
stmt = select(RuleMaster).order_by(RuleMaster.priority.asc(), RuleMaster.id.asc())
if filters:
stmt = stmt.where(and_(*filters))
return list(db.scalars(stmt).all())