Feat: Convert data_models.py cloud/billing models

This commit is contained in:
2025-12-05 13:19:26 -05:00
parent 407b7ec53e
commit 5ed288ff5a
8 changed files with 712 additions and 5 deletions
+36
View File
@@ -104,3 +104,39 @@ Feature: Domain Models Validation
Given I have summary counts for context updates
When I create a SummaryForUpdateContextParams model
Then the summary counts should match the source data
Scenario: Create cloud billing fields from camelCase payload
Given I have sample cloud billing data
When I create a CloudBillingFields model
Then the billing fields should expose decimal values
Scenario: Create organization with billing fields
Given I have organization data with billing
When I create an Org domain model
Then the org should include billing configuration
Scenario: Create a user with default plan config
Given I have user data referencing a default plan config
When I create a User model
Then the user should include the default plan config
Scenario: Create an org role definition
Given I have an org role definition
When I create an OrgRole model
Then the org role should retain its label
Scenario: Create an org user association
Given I have an org user association
When I create an OrgUser model
Then the org user config should be attached
Scenario: Create an invite record with timestamps
Given I have invite metadata
When I create an Invite model
Then the invite should track acceptance timestamps
Scenario: Create a credits transaction entry
Given I have credits transaction data
When I create a CreditsTransaction model
Then the credits transaction should track balances
+289
View File
@@ -1,6 +1,7 @@
"""Step definitions for domain models testing."""
from datetime import datetime
from decimal import Decimal
from pathlib import Path
from behave import given, then, when
@@ -11,16 +12,55 @@ from cleveragents.domain.models.aimodelsdatamodels import AIModel, Provider
from cleveragents.domain.models.core import (
Change,
ChangeSet,
CloudBillingFields,
Context,
ContextType,
ContextUpdateResult,
CreditType,
CreditsTransaction,
CreditsTransactionType,
Invite,
MaxContextCount,
OperationType,
Org,
OrgRole,
OrgUser,
Plan,
PlanStatus,
Project,
ProjectSettings,
SummaryForUpdateContextParams,
User,
)
from cleveragents.domain.models.core.enums import ModelProvider
from cleveragents.domain.models.orguserconfig import OrgUserConfig
from cleveragents.domain.models.planconfig import PlanConfig
def _sample_plan_config() -> PlanConfig:
"""Create a reusable PlanConfig instance for tests."""
return PlanConfig(
auto_mode="manual",
editor="vim",
editor_command="vim",
editor_args=[],
editor_open_manually=False,
auto_continue=False,
auto_build=False,
auto_update_context=False,
auto_context=False,
smart_context=False,
auto_apply=False,
auto_commit=False,
skip_commit=False,
can_exec=False,
auto_exec=False,
auto_debug=False,
auto_debug_tries=0,
auto_revert_on_rewind=False,
skip_changes_menu=False,
)
@given("I have valid project data")
@@ -381,6 +421,255 @@ def step_stats_count_operations(context):
assert "applied" in context.stats
# Domain models from cloud/organization data
def _sample_billing_payload() -> dict[str, object]:
"""Provide representative camelCase billing payload."""
return {
"creditsBalance": "125.5",
"monthlyGrant": "200.0",
"autoRebuyEnabled": True,
"autoRebuyMinThreshold": "5.0",
"autoRebuyToBalance": "50.0",
"notifyThreshold": "10.0",
"maxThresholdPerMonth": "500.0",
"billingCycleStartedAt": "2025-01-01T00:00:00Z",
"changedBillingMode": False,
"trialPaid": True,
"stripeSubscriptionId": "sub-123",
"subscriptionStatus": "active",
"subscriptionPausedAt": "2025-01-10T00:00:00Z",
"stripePaymentMethod": "pm_abc",
"subscriptionActionRequired": False,
"subscriptionActionRequiredInvoiceUrl": None,
}
@given("I have sample cloud billing data")
def step_sample_cloud_billing(context):
"""Prepare representative cloud billing data."""
context.cloud_billing_data = _sample_billing_payload()
@when("I create a CloudBillingFields model")
def step_create_billing_fields(context):
"""Instantiate CloudBillingFields from camelCase payload."""
context.billing_fields = CloudBillingFields(**context.cloud_billing_data)
@then("the billing fields should expose decimal values")
def step_verify_billing_fields(context):
"""Validate decimal conversion for billing fields."""
billing = context.billing_fields
assert billing.credits_balance == Decimal("125.5")
assert billing.monthly_grant == Decimal("200.0")
assert billing.subscription_action_required is False
@given("I have organization data with billing")
def step_org_with_billing(context):
"""Prepare organization payload referencing billing fields."""
context.org_payload = {
"id": "org-1",
"name": "Example Org",
"isTrial": True,
"autoAddDomainUsers": True,
"integratedModelsMode": False,
}
context.org_billing_payload = _sample_billing_payload()
@when("I create an Org domain model")
def step_create_org_model(context):
"""Instantiate Org with nested billing fields."""
billing = CloudBillingFields(**context.org_billing_payload)
context.org_model = Org(cloud_billing_fields=billing, **context.org_payload)
@then("the org should include billing configuration")
def step_verify_org_model(context):
"""Ensure the org retains billing configuration."""
org = context.org_model
assert org.cloud_billing_fields is not None
assert org.cloud_billing_fields.notify_threshold == Decimal("10.0")
@given("I have an org role definition")
def step_org_role_definition(context):
"""Set up org role payload."""
context.org_role_data = {
"id": "role-1",
"isDefault": True,
"label": "Owner",
"description": "Full access",
}
@when("I create an OrgRole model")
def step_create_org_role(context):
"""Instantiate OrgRole."""
context.org_role = OrgRole(**context.org_role_data)
@then("the org role should retain its label")
def step_verify_org_role(context):
"""Ensure org role values persist."""
assert context.org_role.label == "Owner"
assert context.org_role.is_default is True
@given("I have user data referencing a default plan config")
def step_user_with_plan_config(context):
"""Prepare user payload referencing plan config."""
context.user_plan_config = _sample_plan_config()
context.user_data = {
"id": "user-1",
"name": "Casey",
"email": "casey@example.com",
"isTrial": False,
"numNonDraftPlans": 3,
}
@when("I create a User model")
def step_create_user_model(context):
"""Instantiate User with nested plan config."""
context.user_model = User(
default_plan_config=context.user_plan_config, **context.user_data
)
@then("the user should include the default plan config")
def step_verify_user_model(context):
"""Ensure user retains plan config."""
assert context.user_model.default_plan_config is not None
assert context.user_model.default_plan_config.auto_mode == "manual"
@given("I have an org user association")
def step_org_user_association(context):
"""Prepare OrgUser payload."""
context.org_user_config = OrgUserConfig(
prompted_claude_max=False,
use_claude_subscription=True,
claude_subscription_cooldown_started_at=datetime(2025, 1, 1, 0, 0, 0),
)
context.org_user_data = {
"orgId": "org-1",
"userId": "user-1",
"orgRoleId": "role-1",
}
@when("I create an OrgUser model")
def step_create_org_user(context):
"""Instantiate OrgUser."""
context.org_user_model = OrgUser(
config=context.org_user_config, **context.org_user_data
)
@then("the org user config should be attached")
def step_verify_org_user(context):
"""Ensure OrgUser carries config payload."""
assert context.org_user_model.config is not None
assert context.org_user_model.config.use_claude_subscription is True
@given("I have invite metadata")
def step_invite_metadata(context):
"""Prepare Invite payload."""
context.invite_data = {
"id": "invite-1",
"orgId": "org-1",
"email": "new@example.com",
"name": "New User",
"orgRoleId": "role-1",
"inviterId": "user-1",
"inviteeId": None,
"acceptedAt": "2025-02-01T12:00:00Z",
"createdAt": "2025-01-15T08:30:00Z",
}
@when("I create an Invite model")
def step_create_invite(context):
"""Instantiate Invite."""
context.invite_model = Invite(**context.invite_data)
@then("the invite should track acceptance timestamps")
def step_verify_invite(context):
"""Ensure Invite datetimes parsed."""
assert isinstance(context.invite_model.accepted_at, datetime)
assert context.invite_model.accepted_at.year == 2025
@given("I have credits transaction data")
def step_credits_transaction_data(context):
"""Prepare CreditsTransaction payload."""
context.credits_transaction_data = {
"id": "txn-1",
"orgId": "org-1",
"orgName": "Example Org",
"userId": "user-1",
"userEmail": "casey@example.com",
"userName": "Casey",
"transactionType": CreditsTransactionType.CREDIT,
"amount": "42.0",
"startBalance": "20.0",
"endBalance": "62.0",
"creditType": CreditType.TRIAL,
"creditIsAutoRebuy": True,
"creditAutoRebuyMinThreshold": "5.0",
"creditAutoRebuyToBalance": "40.0",
"debitModelProvider": ModelProvider.OPENAI,
"debitModelRole": "ModelRolePlanner",
"debitModelName": "gpt-4.1",
"debitPlanId": "plan-1",
"debitPlanName": "Core Plan",
"createdAt": "2025-01-20T00:00:00Z",
}
@when("I create a CreditsTransaction model")
def step_create_credits_transaction(context):
"""Instantiate CreditsTransaction."""
context.credits_transaction = CreditsTransaction(**context.credits_transaction_data)
@then("the credits transaction should track balances")
def step_verify_credits_transaction(context):
"""Ensure CreditsTransaction stores decimals and provider metadata."""
tx = context.credits_transaction
assert tx.amount == Decimal("42.0")
assert tx.debit_model_provider == ModelProvider.OPENAI
assert tx.end_balance == Decimal("62.0")
# Auto-generated models steps
@when("I import the auto-generated models")
def step_import_autogenerated(context):
+14 -5
View File
@@ -1558,7 +1558,15 @@ Notes: Log schema decisions, migration quirks, and adapter considerations.
- Implemented the corresponding step definitions in `features/steps/architecture_steps.py` using `pkgutil.walk_packages` and `importlib.import_module`.
- **Impact**: This enhances the robustness of our CI pipeline and ensures coverage metrics are accurate. All architecture tests are passing.
**2025-12-05: Org + billing domain models wired with smoke tests**
- Completed the manual conversion of the remaining organization/billing contracts by adding `Org`, `OrgRole`, `OrgUser`, `Invite`, `OrgRole`, `User`, `CloudBillingFields`, `CreditsTransaction`, and related enums to `src/cleveragents/domain/models/core/org.py:1` and exporting them through `src/cleveragents/domain/models/core/__init__.py:1` and `src/cleveragents/domain/models/__init__.py:1` so consumers can hydrate camelCase payloads directly.
- Extended the Behave suite at `features/domain_models.feature:108` with nested-plan-config, billing, invite, org-user, and credits-transaction scenarios backed by the new step definitions in `features/steps/domain_models_steps.py:450`, covering decimal coercion, timestamp parsing, and OrgUserConfig alias handling.
- Added a Robot helper (`robot/helper_domain_models.py:1`) plus a focused suite at `robot/domain_models.robot:1` that shells out to the helper via `Run Process` to assert `org-model-ok` / `credits-model-ok` outputs; this gives us a lightweight CI guardrail for billing regressions without running all domain Behave scenarios.
- Validation evidence: `nox -s unit_tests -- features/domain_models.feature` and `nox -s integration_tests -- robot/domain_models.robot` both pass locally after the new models and suites were wired in.
**LangChain/LangGraph Integration for Phase 3:**
- Extend SQLAlchemy models to support LangGraph's state persistence schema
- Implement `LangGraphStateAdapter` to bridge SQLAlchemy repositories with LangGraph checkpoints
- Use LangChain's `SQLChatMessageHistory` for conversation persistence
@@ -3795,7 +3803,7 @@ If you can do all of the above by end of Day 1, you're on track!
- [X] Convert data_models.py conversation models (7 models): CurrentStage, ConvoMessageFlags, Subtask, ConvoMessage, ConvoSummary, TellStage, PlanningPhase — implemented in `src/cleveragents/domain/models/conversation/__init__.py`
- [X] Convert data_models.py plan file models (7 models): Branch, Replacement, PlanFileResult, CurrentPlanFiles, PlanApply, CurrentPlanState, PlanStateStatus — implemented in `src/cleveragents/domain/models/planfiles/__init__.py`
- [X] Convert data_models.py core stubs (6 models): Project, Plan, Context, Operation, PlanBuild, PlanResult — already exist in `src/cleveragents/domain/models/core/`
- [ ] Convert data_models.py cloud/billing models (7 models): Org, User, OrgUser, Invite, OrgRole, CloudBillingFields, CreditsTransaction — deferred, not needed for standalone mode
- [X] Convert data_models.py cloud/billing models (7 models): Org, User, OrgUser, Invite, OrgRole, CloudBillingFields, CreditsTransaction — implemented in `src/cleveragents/domain/models/core/org.py:1` with exports from `core/__init__.py:1` and `domain/models/__init__.py:1`, plus Behave coverage in `features/domain_models.feature:108` and Robot smoke tests in `robot/domain_models.robot:1`.
- [X] Convert plan_model_settings.py (1 model): PlanSettings — implemented in `src/cleveragents/domain/models/plansettings/__init__.py` with proper type hints and Pydantic validation.
- [ ] Convert req_res.py (53 API models) - defer to Phase 5 when implementing server endpoints
- [ ] CreateEmailVerificationRequest
@@ -3882,11 +3890,12 @@ If you can do all of the above by end of Day 1, you're on track!
- [X] Test context-load with real files
- [X] Test database persistence
- [X] Ensure >85% coverage maintained
- [X] Added domain-model Robot smoke suite (`robot/domain_models.robot:1`) backed by `robot/helper_domain_models.py:1` to assert org + billing hydration without running the full Behave suite.
- [X] Tests: **LangChain/LangGraph Testing Tasks** (COMPLETED 2025-11-22)
- [X] Convert existing mock to FakeListLLM (Already done in langchain_mock_provider.py)
- [X] Update MockAIProvider to use LangChain (Completed with FakeListLLM implementation)
- [X] Ensure all tests still pass (Verified - 95% coverage maintained)
- [X] Remove hardcoded responses (Using FakeListLLM response list)
- [X] Convert existing mock to FakeListLLM (Already done in langchain_mock_provider.py)
- [X] Update MockAIProvider to use LangChain (Completed with FakeListLLM implementation)
- [X] Ensure all tests still pass (Verified - 95% coverage maintained)
- [X] Remove hardcoded responses (Using FakeListLLM response list)
- [X] Add LangGraph workflow tests (features/plan_generation_langgraph_coverage.feature - 17 scenarios)
- [X] Test PlanGenerationGraph execution (covered in langgraph coverage tests)
- [X] Test conditional edges and retry logic (should_retry scenarios in langgraph tests)
+21
View File
@@ -0,0 +1,21 @@
*** Settings ***
Documentation Smoke tests for domain model helpers
Resource common.resource
Suite Setup Setup Test Environment
Suite Teardown Cleanup Test Environment
*** Variables ***
${HELPER_SCRIPT} robot/helper_domain_models.py
*** Test Cases ***
Org Domain Model Hydrates Billing
[Documentation] Ensure Org + CloudBillingFields instantiate via helper script
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} org cwd=${WORKSPACE}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} org-model-ok
Credits Transaction Domain Model Hydrates
[Documentation] Ensure CreditsTransaction instantiates via helper script
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} credits cwd=${WORKSPACE}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} credits-model-ok
+88
View File
@@ -0,0 +1,88 @@
"""Helper utilities for domain model Robot tests."""
from __future__ import annotations
from datetime import datetime, timezone
from decimal import Decimal
import sys
from cleveragents.domain.models.core.org import (
CloudBillingFields,
CreditsTransaction,
CreditsTransactionType,
Org,
)
from cleveragents.domain.models.core.enums import ModelProvider
from cleveragents.domain.models.core.org import CreditType
def _org_test() -> None:
billing = CloudBillingFields(
credits_balance=Decimal("42.5"),
monthly_grant=Decimal("10.0"),
auto_rebuy_enabled=True,
auto_rebuy_min_threshold=Decimal("5.0"),
auto_rebuy_to_balance=Decimal("15.0"),
notify_threshold=Decimal("2.0"),
max_threshold_per_month=Decimal("100.0"),
billing_cycle_started_at=datetime(2025, 1, 1, tzinfo=timezone.utc),
changed_billing_mode=False,
trial_paid=False,
stripe_subscription_id="sub-123",
subscription_status="active",
subscription_paused_at=None,
stripe_payment_method="pm_123",
subscription_action_required=False,
subscription_action_required_invoice_url=None,
)
org = Org(
id="org-robot",
name="Robot Org",
is_trial=True,
auto_add_domain_users=True,
integrated_models_mode=None,
cloud_billing_fields=billing,
)
assert org.cloud_billing_fields is not None
assert org.cloud_billing_fields.credits_balance == Decimal("42.5")
print("org-model-ok")
def _credits_test() -> None:
tx = CreditsTransaction(
id="txn-robot",
org_id="org-robot",
org_name="Robot Org",
user_id="user-robot",
user_email="robot@example.com",
user_name="Robot",
transaction_type=CreditsTransactionType.CREDIT,
amount=Decimal("10.0"),
start_balance=Decimal("5.0"),
end_balance=Decimal("15.0"),
credit_type=CreditType.TRIAL,
credit_is_auto_rebuy=False,
debit_model_provider=ModelProvider.OPENAI,
debit_model_name="gpt-4.1",
debit_model_role="ModelRolePlanner",
created_at=datetime(2025, 1, 2, tzinfo=timezone.utc),
)
assert tx.debit_model_provider == ModelProvider.OPENAI
assert tx.amount == Decimal("10.0")
print("credits-model-ok")
def main() -> None:
if len(sys.argv) < 2:
raise SystemExit("Expected command argument")
command = sys.argv[1]
if command == "org":
_org_test()
elif command == "credits":
_credits_test()
else:
raise SystemExit(f"Unknown helper command: {command}")
if __name__ == "__main__":
main()
@@ -12,13 +12,21 @@ from .conversation import (
from .core import (
Change,
ChangeSet,
CloudBillingFields,
Context,
ContextFile,
ContextType,
ContextUpdateResult,
CreditType,
CreditsTransaction,
CreditsTransactionType,
Invite,
MaxContextCount,
Operation,
OperationType,
Org,
OrgRole,
OrgUser,
Plan,
PlanBuild,
PlanResult,
@@ -27,6 +35,7 @@ from .core import (
ProjectSettings,
ProjectStats,
SummaryForUpdateContextParams,
User,
)
from .planfiles import (
Branch,
@@ -72,4 +81,13 @@ __all__ = [
"Subtask",
"SummaryForUpdateContextParams",
"TellStage",
"CloudBillingFields",
"CreditType",
"CreditsTransaction",
"CreditsTransactionType",
"Invite",
"Org",
"OrgRole",
"OrgUser",
"User",
]
@@ -11,6 +11,17 @@ from .context import (
)
from .debug_attempt import DebugAttempt
from .plan import Plan, PlanBuild, PlanResult, PlanStatus
from .org import (
CloudBillingFields,
CreditType,
CreditsTransaction,
CreditsTransactionType,
Invite,
Org,
OrgRole,
OrgUser,
User,
)
from .project import Project, ProjectSettings, ProjectStats
__all__ = [
@@ -32,4 +43,13 @@ __all__ = [
"ProjectSettings",
"ProjectStats",
"SummaryForUpdateContextParams",
"CloudBillingFields",
"CreditType",
"CreditsTransaction",
"CreditsTransactionType",
"Invite",
"Org",
"OrgRole",
"OrgUser",
"User",
]
+226
View File
@@ -0,0 +1,226 @@
"""Organization and billing domain models for CleverAgents.
Based on Phase 0 discovery stubs in ``docs/reference/contracts/stubs/data_models.py``
with additional validation rules from ADR-004 (Pydantic Validation).
"""
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from enum import Enum
from pydantic import BaseModel, ConfigDict, Field
from ..orguserconfig.org_user_config import OrgUserConfig
from ..planconfig.plan_config import PlanConfig
from .enums import ModelProvider
__all__ = [
"CloudBillingFields",
"CreditType",
"CreditsTransaction",
"CreditsTransactionType",
"Invite",
"Org",
"OrgRole",
"OrgUser",
"User",
]
class OrgRole(BaseModel):
"""Organization role metadata."""
id: str = Field(..., description="Role identifier")
is_default: bool = Field(..., alias="isDefault")
label: str = Field(..., min_length=1)
description: str = Field(..., min_length=1)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
populate_by_name=True,
)
class CloudBillingFields(BaseModel):
"""Billing configuration for a CleverAgents organization."""
credits_balance: Decimal = Field(..., alias="creditsBalance")
monthly_grant: Decimal = Field(..., alias="monthlyGrant")
auto_rebuy_enabled: bool = Field(..., alias="autoRebuyEnabled")
auto_rebuy_min_threshold: Decimal = Field(..., alias="autoRebuyMinThreshold")
auto_rebuy_to_balance: Decimal = Field(..., alias="autoRebuyToBalance")
notify_threshold: Decimal = Field(..., alias="notifyThreshold")
max_threshold_per_month: Decimal = Field(..., alias="maxThresholdPerMonth")
billing_cycle_started_at: datetime = Field(..., alias="billingCycleStartedAt")
changed_billing_mode: bool = Field(..., alias="changedBillingMode")
trial_paid: bool = Field(..., alias="trialPaid")
stripe_subscription_id: str | None = Field(
default=None, alias="stripeSubscriptionId"
)
subscription_status: str | None = Field(default=None, alias="subscriptionStatus")
subscription_paused_at: datetime | None = Field(
default=None, alias="subscriptionPausedAt"
)
stripe_payment_method: str | None = Field(default=None, alias="stripePaymentMethod")
subscription_action_required: bool = Field(..., alias="subscriptionActionRequired")
subscription_action_required_invoice_url: str | None = Field(
default=None, alias="subscriptionActionRequiredInvoiceUrl"
)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
arbitrary_types_allowed=False,
populate_by_name=True,
)
class Org(BaseModel):
"""Organization profile."""
id: str = Field(..., description="Organization identifier")
name: str = Field(..., min_length=1)
is_trial: bool = Field(..., alias="isTrial")
auto_add_domain_users: bool = Field(..., alias="autoAddDomainUsers")
integrated_models_mode: bool | None = Field(
default=None, alias="integratedModelsMode"
)
cloud_billing_fields: CloudBillingFields | None = Field(
default=None, alias="cloudBillingFields"
)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
populate_by_name=True,
)
class User(BaseModel):
"""User profile within an organization."""
id: str = Field(...)
name: str = Field(..., min_length=1)
email: str = Field(..., min_length=3)
is_trial: bool = Field(..., alias="isTrial")
num_non_draft_plans: int = Field(..., alias="numNonDraftPlans", ge=0)
default_plan_config: PlanConfig | None = Field(
default=None, alias="defaultPlanConfig"
)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
populate_by_name=True,
)
class OrgUser(BaseModel):
"""Association between a user and an organization."""
org_id: str = Field(..., alias="orgId")
user_id: str = Field(..., alias="userId")
org_role_id: str = Field(..., alias="orgRoleId")
config: OrgUserConfig | None = Field(default=None)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
populate_by_name=True,
)
class Invite(BaseModel):
"""Organization invitation record."""
id: str = Field(...)
org_id: str = Field(..., alias="orgId")
email: str = Field(...)
name: str = Field(...)
org_role_id: str = Field(..., alias="orgRoleId")
inviter_id: str = Field(..., alias="inviterId")
invitee_id: str | None = Field(default=None, alias="inviteeId")
accepted_at: datetime | None = Field(default=None, alias="acceptedAt")
created_at: datetime = Field(..., alias="createdAt")
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
populate_by_name=True,
)
class CreditsTransactionType(str, Enum):
"""Type of credits transaction."""
CREDIT = "CreditsTransactionTypeCredit"
DEBIT = "CreditsTransactionTypeDebit"
class CreditType(str, Enum):
"""Categorization for credits changes."""
TRIAL = "CreditTypeTrial"
GRANT = "CreditTypeGrant"
ADMIN_GRANT = "CreditTypeAdminGrant"
PURCHASE = "CreditTypePurchase"
SWITCH = "CreditTypeSwitch"
class CreditsTransaction(BaseModel):
"""Ledger entry for credits usage or replenishment."""
id: str = Field(...)
org_id: str = Field(..., alias="orgId")
org_name: str = Field(..., alias="orgName")
user_id: str | None = Field(default=None, alias="userId")
user_email: str | None = Field(default=None, alias="userEmail")
user_name: str | None = Field(default=None, alias="userName")
transaction_type: CreditsTransactionType = Field(..., alias="transactionType")
amount: Decimal = Field(...)
start_balance: Decimal = Field(..., alias="startBalance")
end_balance: Decimal = Field(..., alias="endBalance")
credit_type: CreditType | None = Field(default=None, alias="creditType")
credit_is_auto_rebuy: bool = Field(..., alias="creditIsAutoRebuy")
credit_auto_rebuy_min_threshold: Decimal | None = Field(
default=None, alias="creditAutoRebuyMinThreshold"
)
credit_auto_rebuy_to_balance: Decimal | None = Field(
default=None, alias="creditAutoRebuyToBalance"
)
debit_input_tokens: int | None = Field(default=None, alias="debitInputTokens")
debit_output_tokens: int | None = Field(default=None, alias="debitOutputTokens")
debit_model_input_price_per_token: Decimal | None = Field(
default=None, alias="debitModelInputPricePerToken"
)
debit_model_output_price_per_token: Decimal | None = Field(
default=None, alias="debitModelOutputPricePerToken"
)
debit_base_amount: Decimal | None = Field(default=None, alias="debitBaseAmount")
debit_surcharge: Decimal | None = Field(default=None, alias="debitSurcharge")
debit_model_provider: ModelProvider | None = Field(
default=None, alias="debitModelProvider"
)
debit_model_name: str | None = Field(default=None, alias="debitModelName")
debit_model_pack_name: str | None = Field(default=None, alias="debitModelPackName")
debit_model_role: str | None = Field(default=None, alias="debitModelRole")
debit_purpose: str | None = Field(default=None, alias="debitPurpose")
debit_plan_id: str | None = Field(default=None, alias="debitPlanId")
debit_plan_name: str | None = Field(default=None, alias="debitPlanName")
debit_id: str | None = Field(default=None, alias="debitId")
debit_cache_discount: Decimal | None = Field(
default=None, alias="debitCacheDiscount"
)
debit_session_id: str | None = Field(default=None, alias="debitSessionId")
created_at: datetime = Field(..., alias="createdAt")
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
arbitrary_types_allowed=False,
populate_by_name=True,
use_enum_values=True,
)