"""Pydantic schemas for the Bailian admin API. Read endpoints return rows with denormalised display fields (workspace name on each app, integration name on each workspace) so the admin console can avoid client-side joins. """ from __future__ import annotations from datetime import date, datetime from decimal import Decimal from uuid import UUID from pydantic import BaseModel, Field class BailianWorkspaceResponse(BaseModel): id: UUID integration_id: str integration_name: str | None = None workspace_id: str name: str description: str | None = None region: str | None = None status: str is_default: bool owner_person_id: str | None = None owner_label: str | None = None last_synced_at: datetime deleted_at: datetime | None = None created_at: datetime updated_at: datetime api_key_count: int | None = None model_config = {"from_attributes": True} class BailianApiKeyResponse(BaseModel): id: UUID integration_id: str integration_name: str | None = None workspace_pk: UUID | None = None workspace_name: str | None = None api_key_id: str display_name: str owner_person_id: str | None = None owner_label: str | None = None ram_user_name: str | None = None assignment_id: UUID | None = None status: str expires_at: datetime | None = None last_used_at: datetime | None = None created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class BailianUsageDailyResponse(BaseModel): id: UUID integration_id: str # Nullable: BSS billing rows for the intl tenant are # account-scoped (no workspace_pk). The dashboard renders these # under "(account aggregate)". workspace_pk: UUID | None = None workspace_name: str | None = None api_key_pk: UUID | None = None api_key_display_name: str | None = None # Raw ModelStudio key / workspace id parsed from the BSS bill # composite (migration 298). Present even when the key/workspace # isn't synced locally (member accounts → api_key_pk/workspace_pk # stay NULL but these carry the upstream id for display / drill-down). bailian_api_key_id: str | None = None workspace_id_raw: str | None = None # Aliyun member-account ID — Migration 085 promoted this to the # primary chargeback dimension. Always set on BSS-derived rows. aliyun_account_id: str | None = None # Display name from cmdb_cloud_scopes (matched on external_id) # and the scope owner enrichment, populated by list_usage_daily. aliyun_account_name: str | None = None owner_person_id: str | None = None owner_display_name: str | None = None owner_employee_no: str | None = None owner_employment_status: str | None = None usage_date: date model_name: str input_tokens: int output_tokens: int request_count: int cost_usd: Decimal cost_cny: Decimal synced_at: datetime created_at: datetime model_config = {"from_attributes": True} class BailianUsageSummary(BaseModel): """Aggregate view for the cost dashboard.""" integration_id: str integration_name: str | None = None period_start: date period_end: date total_input_tokens: int total_output_tokens: int total_request_count: int total_cost_usd: Decimal total_cost_cny: Decimal by_model: list[dict] = Field(default_factory=list) by_workspace: list[dict] = Field(default_factory=list) by_owner: list[dict] = Field(default_factory=list) # Per-API-key chargeback breakdown (migration 298). Each item: # {bailian_api_key_id, api_key_display_name, owner_person_id, # owner_label, cost_usd}. by_api_key: list[dict] = Field(default_factory=list) class BailianSyncTriggerRequest(BaseModel): """Body for ``POST /bailian/sync`` — admin re-runs a sync immediately. ``capabilities`` are namespaced under ``bailian.``; valid values are ``workspaces``, ``api_keys``, ``usage``. Empty list = sync everything. (``applications`` was removed when the international ModelStudio surface — which has no application catalog API — became Helix's only target.) """ integration_id: str capabilities: list[str] = Field(default_factory=list) class BailianWorkspaceOwnerUpdate(BaseModel): """Re-assign workspace owner_person_id (admin only).""" owner_person_id: UUID | None = None reason: str = Field(..., min_length=4, max_length=500) class BailianWorkspaceDefaultUpdate(BaseModel): """Mark / unmark a workspace as the auto-grant default (admin only). ``is_default`` is Helix-owned, not synced: intl ModelStudio's ``ListWorkspaces`` does not return an ``isDefault`` field at all, so nothing upstream can ever set it. Before this endpoint existed the flag was only reachable by hand-editing the DB — which is why the auto-grant error telling admins to "mark exactly one BailianWorkspace.is_default=True" was, in practice, unactionable. """ is_default: bool = True reason: str = Field(..., min_length=4, max_length=500) class BailianApiKeyOwnerUpdate(BaseModel): """Re-assign API-key owner_person_id (admin only). Pass ``owner_person_id=None`` to clear the binding (e.g. when the previous owner left and the key needs to be re-attributed later). """ owner_person_id: UUID | None = None reason: str = Field(..., min_length=4, max_length=500)