first commit
This commit is contained in:
97
backend/app/application/admin/variants.py
Normal file
97
backend/app/application/admin/variants.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.domain.keys import generate_variant_key
|
||||
from app.domain.mappers import to_variant
|
||||
from app.repositories.directus.variants import VariantRepository
|
||||
from app.schemas.admin import VariantCreate, VariantRead, VariantUpdate, variant_to_read_model
|
||||
|
||||
|
||||
class VariantService:
|
||||
"""Application service for variant management."""
|
||||
|
||||
def __init__(self, repository: VariantRepository | None = None) -> None:
|
||||
self.repository = repository or VariantRepository()
|
||||
|
||||
async def _assert_weights_sum_100(
|
||||
self,
|
||||
experiment_id: str,
|
||||
new_weight: int,
|
||||
exclude_variant_id: str | None = None,
|
||||
access_token: str | None = None,
|
||||
) -> None:
|
||||
items = await self.repository.list(
|
||||
params={"filter[experiment_id][_eq]": experiment_id},
|
||||
access_token=access_token,
|
||||
)
|
||||
existing_sum = sum(
|
||||
int(item.get("traffic_weight", 0))
|
||||
for item in items
|
||||
if str(item["id"]) != exclude_variant_id
|
||||
)
|
||||
if existing_sum + new_weight != 100:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"所有變體的流量權重加總必須等於 100(目前其他變體合計 {existing_sum},本次輸入 {new_weight})",
|
||||
)
|
||||
|
||||
async def list_variants(
|
||||
self,
|
||||
experiment_id: str | None = None,
|
||||
access_token: str | None = None,
|
||||
) -> list[VariantRead]:
|
||||
params: dict[str, Any] | None = None
|
||||
if experiment_id:
|
||||
params = {"filter[experiment_id][_eq]": experiment_id}
|
||||
|
||||
items = await self.repository.list(params=params, access_token=access_token)
|
||||
return [variant_to_read_model(to_variant(item)) for item in items]
|
||||
|
||||
async def get_variant(
|
||||
self,
|
||||
variant_id: str,
|
||||
access_token: str | None = None,
|
||||
) -> VariantRead | None:
|
||||
item = await self.repository.get(variant_id, access_token=access_token)
|
||||
if not item:
|
||||
return None
|
||||
return variant_to_read_model(to_variant(item))
|
||||
|
||||
async def create_variant(
|
||||
self,
|
||||
payload: VariantCreate,
|
||||
access_token: str | None = None,
|
||||
) -> VariantRead:
|
||||
await self._assert_weights_sum_100(
|
||||
experiment_id=payload.experiment_id,
|
||||
new_weight=payload.traffic_weight,
|
||||
access_token=access_token,
|
||||
)
|
||||
data: dict[str, Any] = payload.model_dump(exclude_none=True)
|
||||
data["variant_key"] = generate_variant_key()
|
||||
item = await self.repository.create(data, access_token=access_token)
|
||||
return variant_to_read_model(to_variant(item))
|
||||
|
||||
async def update_variant(
|
||||
self,
|
||||
variant_id: str,
|
||||
payload: VariantUpdate,
|
||||
access_token: str | None = None,
|
||||
) -> VariantRead | None:
|
||||
data: dict[str, Any] = payload.model_dump(exclude_unset=True)
|
||||
if "traffic_weight" in data:
|
||||
existing = await self.repository.get(variant_id, access_token=access_token)
|
||||
if existing:
|
||||
await self._assert_weights_sum_100(
|
||||
experiment_id=str(existing["experiment_id"]),
|
||||
new_weight=data["traffic_weight"],
|
||||
exclude_variant_id=variant_id,
|
||||
access_token=access_token,
|
||||
)
|
||||
item = await self.repository.update(variant_id, data, access_token=access_token)
|
||||
if not item:
|
||||
return None
|
||||
return variant_to_read_model(to_variant(item))
|
||||
Reference in New Issue
Block a user