Spaces:
Running
Running
| """Contract models and schemas for inter-agent contract management.""" | |
| import hashlib | |
| import json | |
| from datetime import datetime | |
| from enum import Enum | |
| from typing import Any, Dict, List, Optional | |
| from uuid import UUID, uuid4 | |
| from pydantic import BaseModel, Field, validator | |
| from sqlmodel import SQLModel, Field as SQLField, Relationship | |
| class ContractState(str, Enum): | |
| """Contract lifecycle states.""" | |
| DRAFT = "draft" | |
| PROPOSED = "proposed" | |
| SIGNED = "signed" | |
| REVOKED = "revoked" | |
| EXPIRED = "expired" | |
| class Clause(BaseModel): | |
| """A contract clause with structured content.""" | |
| id: str = Field(default_factory=lambda: str(uuid4())) | |
| title: str = Field(..., description="Clause title") | |
| content: str = Field(..., description="Clause content") | |
| type: str = Field(default="general", description="Clause type (e.g., 'hipaa', 'data_handling')") | |
| metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional clause metadata") | |
| class Config: | |
| json_encoders = { | |
| datetime: lambda v: v.isoformat() | |
| } | |
| class Signature(BaseModel): | |
| """Cryptographic signature for contract verification.""" | |
| signer_id: str = Field(..., description="ID of the signing party") | |
| signer_type: str = Field(..., description="Type of signer (e.g., 'provider', 'payor', 'patient')") | |
| signature: str = Field(..., description="Base64-encoded Ed25519 signature") | |
| public_key: str = Field(..., description="Base64-encoded public key") | |
| timestamp: datetime = Field(default_factory=datetime.utcnow) | |
| metadata: Dict[str, Any] = Field(default_factory=dict) | |
| class Config: | |
| json_encoders = { | |
| datetime: lambda v: v.isoformat() | |
| } | |
| def model_dump(self, **kwargs): | |
| """Override model_dump to handle datetime serialization.""" | |
| data = super().model_dump(**kwargs) | |
| # Convert datetime objects to ISO format strings | |
| for key, value in data.items(): | |
| if isinstance(value, datetime): | |
| data[key] = value.isoformat() | |
| return data | |
| class Contract(SQLModel, table=True): | |
| """Contract model with SQLModel persistence.""" | |
| __tablename__ = "contracts" | |
| # Primary fields | |
| id: UUID = SQLField(default_factory=uuid4, primary_key=True) | |
| title: str = SQLField(..., description="Contract title") | |
| description: str = SQLField(..., description="Contract description") | |
| # Contract content | |
| clauses: str = SQLField(..., description="JSON-encoded clauses") | |
| parties: str = SQLField(..., description="JSON-encoded parties") | |
| # Lifecycle | |
| state: ContractState = SQLField(default=ContractState.DRAFT) | |
| created_at: datetime = SQLField(default_factory=datetime.utcnow) | |
| proposed_at: Optional[datetime] = SQLField(default=None) | |
| signed_at: Optional[datetime] = SQLField(default=None) | |
| revoked_at: Optional[datetime] = SQLField(default=None) | |
| expires_at: Optional[datetime] = SQLField(default=None) | |
| # Signatures | |
| signatures: str = SQLField(default="[]", description="JSON-encoded signatures") | |
| # HIPAA compliance | |
| is_hipaa_compliant: bool = SQLField(default=False) | |
| hipaa_entities: str = SQLField(default="[]", description="JSON-encoded HIPAA entities") | |
| # Metadata | |
| contract_metadata: str = SQLField(default="{}", description="JSON-encoded metadata") | |
| version: str = SQLField(default="1.0.0") | |
| # Audit trail | |
| created_by: str = SQLField(..., description="ID of the creating party") | |
| last_modified: datetime = SQLField(default_factory=datetime.utcnow) | |
| def get_clauses(self) -> List[Clause]: | |
| """Get parsed clauses from JSON.""" | |
| try: | |
| clauses_data = json.loads(self.clauses) | |
| return [Clause(**clause) for clause in clauses_data] | |
| except (json.JSONDecodeError, ValueError): | |
| return [] | |
| def set_clauses(self, clauses: List[Clause]) -> None: | |
| """Set clauses as JSON.""" | |
| self.clauses = json.dumps([clause.model_dump() for clause in clauses]) | |
| def get_parties(self) -> List[Dict[str, Any]]: | |
| """Get parsed parties from JSON.""" | |
| try: | |
| return json.loads(self.parties) | |
| except json.JSONDecodeError: | |
| return [] | |
| def set_parties(self, parties: List[Dict[str, Any]]) -> None: | |
| """Set parties as JSON.""" | |
| self.parties = json.dumps(parties) | |
| def get_signatures(self) -> List[Signature]: | |
| """Get parsed signatures from JSON.""" | |
| try: | |
| signatures_data = json.loads(self.signatures) | |
| return [Signature(**sig) for sig in signatures_data] | |
| except (json.JSONDecodeError, ValueError): | |
| return [] | |
| def set_signatures(self, signatures: List[Signature]) -> None: | |
| """Set signatures as JSON.""" | |
| self.signatures = json.dumps([sig.model_dump() for sig in signatures]) | |
| def get_hipaa_entities(self) -> List[Dict[str, Any]]: | |
| """Get parsed HIPAA entities from JSON.""" | |
| try: | |
| return json.loads(self.hipaa_entities) | |
| except json.JSONDecodeError: | |
| return [] | |
| def set_hipaa_entities(self, entities: List[Dict[str, Any]]) -> None: | |
| """Set HIPAA entities as JSON.""" | |
| self.hipaa_entities = json.dumps(entities) | |
| def get_metadata(self) -> Dict[str, Any]: | |
| """Get parsed metadata from JSON.""" | |
| try: | |
| return json.loads(self.contract_metadata) | |
| except json.JSONDecodeError: | |
| return {} | |
| def set_metadata(self, metadata: Dict[str, Any]) -> None: | |
| """Set metadata as JSON.""" | |
| self.contract_metadata = json.dumps(metadata) | |
| def get_content_hash(self) -> str: | |
| """Get SHA-256 hash of contract content for signing.""" | |
| content = { | |
| "id": str(self.id), | |
| "title": self.title, | |
| "description": self.description, | |
| "clauses": self.clauses, | |
| "parties": self.parties, | |
| "version": self.version | |
| } | |
| content_str = json.dumps(content, sort_keys=True) | |
| return hashlib.sha256(content_str.encode()).hexdigest() | |
| def can_transition_to(self, new_state: ContractState) -> bool: | |
| """Check if contract can transition to new state.""" | |
| valid_transitions = { | |
| ContractState.DRAFT: [ContractState.PROPOSED, ContractState.REVOKED], | |
| ContractState.PROPOSED: [ContractState.SIGNED, ContractState.REVOKED, ContractState.DRAFT], | |
| ContractState.SIGNED: [ContractState.REVOKED], | |
| ContractState.REVOKED: [], # Terminal state | |
| ContractState.EXPIRED: [] # Terminal state | |
| } | |
| return new_state in valid_transitions.get(self.state, []) | |
| def is_fully_signed(self) -> bool: | |
| """Check if contract is fully signed by all required parties.""" | |
| parties = self.get_parties() | |
| signatures = self.get_signatures() | |
| # Check if all parties have signed | |
| signed_party_ids = {sig.signer_id for sig in signatures} | |
| required_party_ids = {party["id"] for party in parties} | |
| return required_party_ids.issubset(signed_party_ids) | |
| def get_unsigned_parties(self) -> List[Dict[str, Any]]: | |
| """Get parties that haven't signed yet.""" | |
| parties = self.get_parties() | |
| signatures = self.get_signatures() | |
| signed_party_ids = {sig.signer_id for sig in signatures} | |
| return [party for party in parties if party["id"] not in signed_party_ids] | |
| class ContractCreateRequest(BaseModel): | |
| """Request model for creating a contract.""" | |
| title: str = Field(..., description="Contract title") | |
| description: str = Field(..., description="Contract description") | |
| clauses: List[Clause] = Field(..., description="Contract clauses") | |
| parties: List[Dict[str, Any]] = Field(..., description="Contract parties") | |
| is_hipaa_compliant: bool = Field(default=False, description="HIPAA compliance flag") | |
| hipaa_entities: Optional[List[Dict[str, Any]]] = Field(default=None, description="HIPAA entities") | |
| expires_at: Optional[datetime] = Field(default=None, description="Contract expiration") | |
| metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata") | |
| version: str = Field(default="1.0.0", description="Contract version") | |
| class ContractProposeRequest(BaseModel): | |
| """Request model for proposing a contract.""" | |
| proposed_to: List[str] = Field(..., description="IDs of parties to propose to") | |
| message: Optional[str] = Field(default=None, description="Proposal message") | |
| class ContractSignRequest(BaseModel): | |
| """Request model for signing a contract.""" | |
| signer_id: str = Field(..., description="ID of the signing party") | |
| signer_type: str = Field(..., description="Type of signer") | |
| public_key: str = Field(..., description="Base64-encoded public key") | |
| signature: str = Field(..., description="Base64-encoded Ed25519 signature") | |
| metadata: Dict[str, Any] = Field(default_factory=dict, description="Signature metadata") | |
| class ContractRevokeRequest(BaseModel): | |
| """Request model for revoking a contract.""" | |
| reason: str = Field(..., description="Reason for revocation") | |
| revoked_by: str = Field(..., description="ID of the revoking party") | |
| metadata: Dict[str, Any] = Field(default_factory=dict, description="Revocation metadata") | |
| class ContractResponse(BaseModel): | |
| """Response model for contract operations.""" | |
| id: str | |
| title: str | |
| description: str | |
| clauses: List[Clause] | |
| parties: List[Dict[str, Any]] | |
| state: ContractState | |
| created_at: datetime | |
| proposed_at: Optional[datetime] | |
| signed_at: Optional[datetime] | |
| revoked_at: Optional[datetime] | |
| expires_at: Optional[datetime] | |
| signatures: List[Dict[str, Any]] | |
| is_hipaa_compliant: bool | |
| hipaa_entities: List[Dict[str, Any]] | |
| contract_metadata: Dict[str, Any] | |
| version: str | |
| created_by: str | |
| last_modified: datetime | |
| content_hash: str | |
| is_fully_signed: bool | |
| unsigned_parties: List[Dict[str, Any]] | |
| def model_dump(self, **kwargs): | |
| """Override model_dump to handle datetime serialization.""" | |
| data = super().model_dump(**kwargs) | |
| # Convert datetime objects to ISO format strings | |
| for key, value in data.items(): | |
| if isinstance(value, datetime): | |
| data[key] = value.isoformat() | |
| elif key == "signatures" and isinstance(value, list): | |
| # Handle nested Signature objects | |
| data[key] = [sig.model_dump() if hasattr(sig, 'model_dump') else sig for sig in value] | |
| return data | |
| def from_contract(cls, contract: Contract) -> "ContractResponse": | |
| """Create response from contract model.""" | |
| # Pre-serialize signatures to handle datetime fields | |
| signatures = contract.get_signatures() | |
| serialized_signatures = [sig.model_dump() for sig in signatures] | |
| return cls( | |
| id=str(contract.id), # Convert UUID to string | |
| title=contract.title, | |
| description=contract.description, | |
| clauses=contract.get_clauses(), | |
| parties=contract.get_parties(), | |
| state=contract.state, | |
| created_at=contract.created_at, | |
| proposed_at=contract.proposed_at, | |
| signed_at=contract.signed_at, | |
| revoked_at=contract.revoked_at, | |
| expires_at=contract.expires_at, | |
| signatures=serialized_signatures, # Use pre-serialized signatures | |
| is_hipaa_compliant=contract.is_hipaa_compliant, | |
| hipaa_entities=contract.get_hipaa_entities(), | |
| contract_metadata=contract.get_metadata(), | |
| version=contract.version, | |
| created_by=contract.created_by, | |
| last_modified=contract.last_modified, | |
| content_hash=contract.get_content_hash(), | |
| is_fully_signed=contract.is_fully_signed(), | |
| unsigned_parties=contract.get_unsigned_parties() | |
| ) | |