""" normalized_template.py ----------------------- Platform-agnostic intermediate schema that decouples Adobe Sign extraction from DocuSign composition. Both platforms' data is converted to/from this model so neither side is tightly coupled. """ from __future__ import annotations from enum import Enum from typing import Any, Optional from pydantic import BaseModel, Field class ActionType(str, Enum): SIGN = "SIGN" APPROVE = "APPROVE" CC = "CC" ACKNOWLEDGE = "ACKNOWLEDGE" class NormalizedRole(BaseModel): name: str order: int action_type: ActionType = ActionType.SIGN class NormalizedField(BaseModel): """One form field in the normalized intermediate representation.""" type: str # e.g. "signature", "text", "checkbox" label: str page: int x: float y: float width: float height: float required: bool = False read_only: bool = False role_name: str = "" # which role this field belongs to options: list[str] = Field(default_factory=list) # for dropdown/radio validation: str = "" # e.g. "DATE", "NUMBER" content_type: str = "" # e.g. "SIGNATURE_DATE", "SIGNER_NAME" conditional_parent_label: Optional[str] = None conditional_parent_value: Optional[str] = None raw: dict[str, Any] = Field(default_factory=dict) # original source data class NormalizedDocument(BaseModel): name: str content_base64: str = "" # base64-encoded PDF bytes checksum_sha256: str = "" # SHA-256 hex of raw bytes before encoding source_path: str = "" class NormalizedTemplate(BaseModel): """ Platform-agnostic representation of an eSignature template. Used as the bridge between Adobe Sign and DocuSign. """ name: str description: str = "" email_subject: str = "" email_message: str = "" roles: list[NormalizedRole] = Field(default_factory=list) documents: list[NormalizedDocument] = Field(default_factory=list) fields: list[NormalizedField] = Field(default_factory=list) reminder_enabled: bool = False expiration_days: Optional[int] = None source_id: str = "" # original Adobe Sign template ID unsupported_features: list[str] = Field(default_factory=list) def role_names(self) -> list[str]: return [r.name for r in self.roles] def fields_for_role(self, role_name: str) -> list[NormalizedField]: return [f for f in self.fields if f.role_name == role_name]