forked from cleveragents/cleveragents-core
Feat: Convert data_models.py cloud/billing models
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user