"""
models.patent_data - Data models for USPTO patent data API
This module provides Pydantic-style data models, primarily using frozen
dataclasses, for representing responses from the USPTO Patent Data API.
It aims to offer more Pythonic representations (e.g., Enums, native
date/datetime objects) of the API's JSON data. Models cover aspects like
application metadata, party information (applicants, inventors, attorneys),
document details, continuity, assignments, and more.
"""
import csv
import io
import json
import warnings
from dataclasses import asdict, dataclass, field
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, Iterator, List, Optional, Union
# Import utility functions from models.utils module
from pyUSPTO.models.utils import (
ASSUMED_NAIVE_TIMEZONE,
ASSUMED_NAIVE_TIMEZONE_STR,
parse_to_date,
parse_to_datetime_utc,
parse_yn_to_bool,
serialize_bool_to_yn,
serialize_date,
serialize_datetime_as_iso,
serialize_datetime_as_naive,
to_camel_case,
)
from pyUSPTO.warnings import USPTOEnumParseWarning
# --- Enums for Categorical Data ---
[docs]
class DirectionCategory(Enum):
"""Represents the direction of a document relative to the USPTO (e.g., INCOMING, OUTGOING)."""
INCOMING = "INCOMING"
OUTGOING = "OUTGOING"
[docs]
class ActiveIndicator(Enum):
"""Represents an active or inactive status, often used for practitioners or entities.
This Enum is designed to flexibly parse common string representations of
active/inactive or true/false states (e.g., "Y", "N", "true", "false", "Active")
into standardized Enum members.
"""
YES = "Y"
NO = "N"
TRUE = "true"
FALSE = "false"
ACTIVE = "Active"
@classmethod
def _missing_(cls, value: Any) -> "ActiveIndicator":
if isinstance(value, str):
val_upper = value.upper()
if val_upper == "Y":
return cls.YES
if val_upper == "N":
return cls.NO
if val_upper == "TRUE":
return cls.TRUE
if val_upper == "FALSE":
return cls.FALSE
if val_upper == "ACTIVE":
return cls.ACTIVE
return super()._missing_(value=value) # type: ignore[no-any-return]
# --- Data Models ---
[docs]
@dataclass(frozen=True)
class Document:
"""Represents a single document associated with a patent application.
This includes metadata such as its identifier, official date, code, description,
direction (incoming/outgoing), and available download formats.
Attributes:
application_number_text: The application number this document belongs to.
official_date: The official date of the document.
document_identifier: A unique identifier for this document.
document_code: A code representing the type of document.
document_code_description_text: A textual description of the document code.
direction_category: The direction of the document (e.g., INCOMING, OUTGOING).
document_formats: A list of available download formats for this document.
"""
application_number_text: Optional[str] = None
official_date: Optional[datetime] = None
document_identifier: Optional[str] = None
document_code: Optional[str] = None
document_code_description_text: Optional[str] = None
direction_category: Optional[DirectionCategory] = None
document_formats: List[DocumentFormat] = field(default_factory=list)
def __str__(self) -> str:
date_str = (
self.official_date.strftime("%Y-%m-%d") if self.official_date else "No date"
)
return f"Document {self.document_identifier} ({self.document_code}): {self.document_code_description_text} - {date_str}"
def __repr__(self) -> str:
return f"Document(id={self.document_identifier}, code={self.document_code}, date={self.official_date.strftime('%Y-%m-%d') if self.official_date else 'None'})"
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Document":
"""Creates a `Document` instance from a dictionary representation.
Maps API JSON keys (camelCase) to class attributes, parsing nested
objects like `DocumentFormat` and `DirectionCategory`.
Args:
data (Dict[str, Any]): A dictionary containing document data,
typically from an API response.
Returns:
Document: An instance of `Document`.
"""
dl_formats = [
DocumentFormat.from_dict(f)
for f in data.get("downloadOptionBag", [])
if isinstance(f, dict)
]
dir_val = data.get("documentDirectionCategory")
dir_cat = None
if dir_val:
try:
dir_cat = DirectionCategory(dir_val)
except ValueError:
warnings.warn(
f"Unknown document direction category '{dir_val}'",
category=USPTOEnumParseWarning,
stacklevel=2,
)
return cls(
application_number_text=data.get("applicationNumberText"),
official_date=parse_to_datetime_utc(data.get("officialDate")),
document_identifier=data.get("documentIdentifier"),
document_code=data.get("documentCode"),
document_code_description_text=data.get("documentCodeDescriptionText"),
direction_category=dir_cat,
document_formats=dl_formats,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Document` instance to a dictionary for API compatibility.
Serializes attributes to camelCase keys and handles nested objects.
Omits keys with None values or empty lists.
Returns:
Dict[str, Any]: A dictionary representation of the `Document`.
"""
d = {
"applicationNumberText": self.application_number_text,
"officialDate": (
serialize_datetime_as_iso(self.official_date)
if self.official_date
else None
),
"documentIdentifier": self.document_identifier,
"documentCode": self.document_code,
"documentCodeDescriptionText": self.document_code_description_text,
"documentDirectionCategory": (
self.direction_category.value if self.direction_category else None
),
"downloadOptionBag": [df.to_dict() for df in self.document_formats],
}
return {
k: v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
class DocumentBag:
"""A collection of Document objects associated with a patent application.
Provides iterable access and standard collection methods like `len` and `getitem`.
This class is immutable by convention after initialization.
Attributes:
documents (tuple[Document, ...]): An immutable tuple of `Document` objects.
"""
[docs]
def __init__(self, documents: List[Document]):
"""Initializes the DocumentBag with a list of documents.
Args:
documents (List[Document]): A list of `Document` instances.
"""
self._documents = tuple(documents)
@property
def documents(self) -> tuple[Document, ...]:
"""Provides access to the tuple of documents."""
return self._documents
def __iter__(self) -> Iterator[Document]:
return iter(self._documents)
def __len__(self) -> int:
return len(self._documents)
def __getitem__(self, index: int) -> Document:
return self._documents[index]
[docs]
def __str__(self) -> str:
"""Returns a string representation showing document count and summary.
Returns:
str: Human-readable summary of the DocumentBag.
"""
count = len(self._documents)
if count == 0:
return "DocumentBag(0 documents)"
# Count unique document codes
doc_codes: Dict[str, int] = {}
for doc in self._documents:
code = doc.document_code or "Unknown"
doc_codes[code] = doc_codes.get(code, 0) + 1
# Format summary
if count == 1:
code = self._documents[0].document_code or "Unknown"
return f"DocumentBag(1 document: {code})"
# Show top 3 most common document codes
sorted_codes = sorted(doc_codes.items(), key=lambda x: x[1], reverse=True)
top_codes = sorted_codes[:3]
code_summary = ", ".join(f"{code} ({cnt})" for code, cnt in top_codes)
if len(sorted_codes) > 3:
remaining = len(sorted_codes) - 3
return f"DocumentBag({count} documents: {code_summary}, +{remaining} more types)"
else:
return f"DocumentBag({count} documents: {code_summary})"
[docs]
def __repr__(self) -> str:
"""Returns a detailed string representation for debugging.
Returns:
str: Detailed representation of the DocumentBag.
"""
return f"DocumentBag(documents={self._documents!r})"
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DocumentBag":
"""Creates a `DocumentBag` instance from a dictionary representation.
Expects a dictionary with a "documentBag" key containing a list of
document data dictionaries.
Args:
data (Dict[str, Any]): A dictionary, typically from an API response,
containing the document bag.
Returns:
DocumentBag: An instance of `DocumentBag`.
"""
docs_data = data.get("documentBag", [])
docs = (
[Document.from_dict(dd) for dd in docs_data if isinstance(dd, dict)]
if isinstance(docs_data, list)
else []
)
return cls(documents=docs)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `DocumentBag` instance to a dictionary.
Serializes the collection into a dictionary with a "documentBag" key,
containing a list of `Document` dictionaries.
Returns:
Dict[str, Any]: A dictionary representation of the `DocumentBag`.
"""
return {"documentBag": [doc.to_dict() for doc in self._documents]}
[docs]
@dataclass(frozen=True)
class Address:
"""Represents a postal address with fields for street, city, region, country, and postal code.
It can be used for various entities like applicants, inventors, or correspondence.
Attributes:
name_line_one_text: First line of the name (e.g., company name).
name_line_two_text: Second line of the name.
address_line_one_text: First line of the street address.
address_line_two_text: Second line of the street address.
address_line_three_text: Third line of the street address.
address_line_four_text: Fourth line of the street address.
geographic_region_name: Name of the geographic region (e.g., state, province).
geographic_region_code: Code for the geographic region.
postal_code: Postal or ZIP code.
city_name: Name of the city.
country_code: Two-letter country code (e.g., "US").
country_name: Full name of the country (e.g., "United States").
postal_address_category: Category of the address (e.g., "MAILING_ADDRESS").
correspondent_name_text: Name of the correspondent at this address.
country_or_state_code: Country or state code.
ict_state_code: International code for the state/region (USPTO format).
ict_country_code: International code for the country (USPTO format).
"""
name_line_one_text: Optional[str] = None
name_line_two_text: Optional[str] = None
address_line_one_text: Optional[str] = None
address_line_two_text: Optional[str] = None
address_line_three_text: Optional[str] = None
address_line_four_text: Optional[str] = None
geographic_region_name: Optional[str] = None
geographic_region_code: Optional[str] = None
postal_code: Optional[str] = None
city_name: Optional[str] = None
country_code: Optional[str] = None
country_name: Optional[str] = None
postal_address_category: Optional[str] = None
correspondent_name_text: Optional[str] = None
country_or_state_code: Optional[str] = None
ict_state_code: Optional[str] = None
ict_country_code: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Address":
"""Creates an `Address` instance from a dictionary representation.
Maps camelCase keys from API data to class attributes.
Args:
data (Dict[str, Any]): Dictionary containing address data.
Returns:
Address: An instance of `Address`.
"""
return cls(
name_line_one_text=data.get("nameLineOneText"),
name_line_two_text=data.get("nameLineTwoText"),
address_line_one_text=data.get("addressLineOneText"),
address_line_two_text=data.get("addressLineTwoText"),
address_line_three_text=data.get("addressLineThreeText"),
address_line_four_text=data.get("addressLineFourText"),
geographic_region_name=data.get("geographicRegionName"),
geographic_region_code=data.get("geographicRegionCode"),
postal_code=data.get("postalCode"),
city_name=data.get("cityName"),
country_code=data.get("countryCode"),
country_name=data.get("countryName"),
postal_address_category=data.get("postalAddressCategory"),
correspondent_name_text=data.get("correspondentNameText"),
country_or_state_code=data.get("countryOrStateCode"),
ict_state_code=data.get("ictStateCode"),
ict_country_code=data.get("ictCountryCode"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Address` instance to a dictionary with camelCase keys.
Returns:
Dict[str, Any]: A dictionary representation of the address.
"""
_dict = {
"nameLineOneText": self.name_line_one_text,
"nameLineTwoText": self.name_line_two_text,
"addressLineOneText": self.address_line_one_text,
"addressLineTwoText": self.address_line_two_text,
"addressLineThreeText": self.address_line_three_text,
"addressLineFourText": self.address_line_four_text,
"geographicRegionName": self.geographic_region_name,
"geographicRegionCode": self.geographic_region_code,
"postalCode": self.postal_code,
"cityName": self.city_name,
"countryCode": self.country_code,
"countryName": self.country_name,
"postalAddressCategory": self.postal_address_category,
"correspondentNameText": self.correspondent_name_text,
"countryOrStateCode": self.country_or_state_code,
"ictStateCode": self.ict_state_code,
"ictCountryCode": self.ict_country_code,
}
# Filter out None values to match API behavior
return {k: v for k, v in _dict.items() if v is not None}
[docs]
@dataclass(frozen=True)
class Telecommunication:
"""Represents telecommunication details, such as phone or fax numbers.
Attributes:
telecommunication_number: The main number (e.g., phone number).
extension_number: Any extension associated with the number.
telecom_type_code: A code indicating the type of telecommunication (e.g., "TEL", "FAX").
"""
telecommunication_number: Optional[str] = None
extension_number: Optional[str] = None
telecom_type_code: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Telecommunication":
"""Creates a `Telecommunication` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with telecommunication data.
Returns:
Telecommunication: An instance of `Telecommunication`.
"""
return cls(
telecommunication_number=data.get("telecommunicationNumber"),
extension_number=data.get("extensionNumber"),
telecom_type_code=data.get("telecomTypeCode"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Telecommunication` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
_dict = {
"telecommunicationNumber": self.telecommunication_number,
"extensionNumber": self.extension_number,
"telecomTypeCode": self.telecom_type_code,
}
# Filter out None values to match API behavior
return {k: v for k, v in _dict.items() if v is not None}
[docs]
@dataclass(frozen=True)
class Person:
"""A base data class representing a person with common name and country attributes.
This class is typically inherited by more specific types like Applicant, Inventor, or Attorney.
Attributes:
first_name: The first name of the person.
middle_name: The middle name or initial of the person.
last_name: The last name or surname of the person.
name_prefix: A prefix for the name (e.g., "Dr.", "Mr.").
name_suffix: A suffix for the name (e.g., "Jr.", "PhD").
preferred_name: The person's preferred name, if different.
country_code: The country code associated with the person (e.g., citizenship).
"""
first_name: Optional[str] = None
middle_name: Optional[str] = None
last_name: Optional[str] = None
name_prefix: Optional[str] = None
name_suffix: Optional[str] = None
preferred_name: Optional[str] = None
country_code: Optional[str] = None
@classmethod
def _extract_person_fields(cls, data: Dict[str, Any]) -> Dict[str, Any]:
return {
"first_name": data.get("firstName"),
"middle_name": data.get("middleName"),
"last_name": data.get("lastName"),
"name_prefix": data.get("namePrefix"),
"name_suffix": data.get("nameSuffix"),
"preferred_name": data.get("preferredName"),
"country_code": data.get("countryCode"),
}
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Person` instance to a dictionary with camelCase keys.
Omits attributes that are None.
Returns:
Dict[str, Any]: A dictionary representation of the person.
"""
return {to_camel_case(k): v for k, v in asdict(self).items() if v is not None}
[docs]
@dataclass(frozen=True)
class Applicant(Person):
"""Represents an applicant for a patent, inheriting from Person.
Includes applicant-specific name text and a list of correspondence addresses.
Attributes:
applicant_name_text: The full name of the applicant as a single string.
correspondence_address_bag: A list of `Address` objects for the applicant.
"""
applicant_name_text: Optional[str] = None
correspondence_address_bag: List[Address] = field(default_factory=list)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Applicant":
"""Creates an `Applicant` instance from a dictionary.
Inherits person fields and adds applicant-specific fields.
Args:
data (Dict[str, Any]): Dictionary with applicant data.
Returns:
Applicant: An instance of `Applicant`.
"""
pf = Person._extract_person_fields(data)
addrs = [
Address.from_dict(a)
for a in data.get("correspondenceAddressBag", [])
if isinstance(a, dict)
]
return cls(
**pf,
applicant_name_text=data.get("applicantNameText"),
correspondence_address_bag=addrs,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Applicant` instance to a dictionary.
Includes inherited person fields and applicant-specific fields,
using camelCase keys and omitting None values or empty lists.
Returns:
Dict[str, Any]: Dictionary representation of the applicant.
"""
d = super().to_dict()
d.update(
{
"applicantNameText": self.applicant_name_text,
"correspondenceAddressBag": [
a.to_dict() for a in self.correspondence_address_bag
],
}
)
return {
k: v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class Inventor(Person):
"""Represents an inventor for a patent application, inheriting from Person.
Includes inventor-specific name text and a list of correspondence addresses.
Attributes:
inventor_name_text: The full name of the inventor as a single string.
correspondence_address_bag: A list of `Address` objects for the inventor.
"""
inventor_name_text: Optional[str] = None
correspondence_address_bag: List[Address] = field(default_factory=list)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Inventor":
"""Creates an `Inventor` instance from a dictionary.
Inherits person fields and adds inventor-specific fields.
Args:
data (Dict[str, Any]): Dictionary with inventor data.
Returns:
Inventor: An instance of `Inventor`.
"""
pf = Person._extract_person_fields(data)
addrs = [
Address.from_dict(a)
for a in data.get("correspondenceAddressBag", [])
if isinstance(a, dict)
]
return cls(
**pf,
inventor_name_text=data.get("inventorNameText"),
correspondence_address_bag=addrs,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Inventor` instance to a dictionary.
Includes inherited person fields and inventor-specific fields,
using camelCase keys and omitting None values or empty lists.
Returns:
Dict[str, Any]: Dictionary representation of the inventor.
"""
d = super().to_dict()
d.update(
{
"inventorNameText": self.inventor_name_text,
"correspondenceAddressBag": [
a.to_dict() for a in self.correspondence_address_bag
],
}
)
return {
k: v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class Attorney(Person):
"""Represents an attorney or agent associated with a patent application, inheriting from Person.
Includes registration number, active status, practitioner category, addresses, and telecommunication details.
Attributes:
registration_number: The attorney's USPTO registration number.
active_indicator: Indicates if the attorney is currently active (e.g., "Y", "N").
registered_practitioner_category: Category of the practitioner (e.g., "ATTORNEY", "AGENT").
attorney_address_bag: List of `Address` objects for the attorney.
telecommunication_address_bag: List of `Telecommunication` objects for the attorney.
"""
registration_number: Optional[str] = None
active_indicator: Optional[str] = None
registered_practitioner_category: Optional[str] = None
attorney_address_bag: List[Address] = field(default_factory=list)
telecommunication_address_bag: List[Telecommunication] = field(default_factory=list)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Attorney":
"""Creates an `Attorney` instance from a dictionary.
Inherits person fields and adds attorney-specific details.
Args:
data (Dict[str, Any]): Dictionary with attorney data.
Returns:
Attorney: An instance of `Attorney`.
"""
pf = Person._extract_person_fields(data)
addrs = [
Address.from_dict(a)
for a in data.get("attorneyAddressBag", [])
if isinstance(a, dict)
]
telecoms = [
Telecommunication.from_dict(t)
for t in data.get("telecommunicationAddressBag", [])
if isinstance(t, dict)
]
return cls(
**pf,
registration_number=data.get("registrationNumber"),
active_indicator=data.get("activeIndicator"),
registered_practitioner_category=data.get("registeredPractitionerCategory"),
attorney_address_bag=addrs,
telecommunication_address_bag=telecoms,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Attorney` instance to a dictionary.
Includes inherited person fields and attorney-specific fields,
using camelCase keys and omitting None values or empty lists.
Returns:
Dict[str, Any]: Dictionary representation of the attorney.
"""
d = super().to_dict()
d.update(
{
"registrationNumber": self.registration_number,
"activeIndicator": self.active_indicator,
"registeredPractitionerCategory": self.registered_practitioner_category,
"attorneyAddressBag": [a.to_dict() for a in self.attorney_address_bag],
"telecommunicationAddressBag": [
t.to_dict() for t in self.telecommunication_address_bag
],
}
)
return {
k: v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class EntityStatus:
"""Represents the entity status of an applicant (e.g., small entity status).
Attributes:
small_entity_status_indicator: Boolean indicating if the applicant qualifies for small entity status.
business_entity_status_category: String category of the business entity status (e.g., "Undiscounted").
"""
small_entity_status_indicator: Optional[bool] = None
business_entity_status_category: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "EntityStatus":
"""Creates an `EntityStatus` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with entity status data.
Returns:
EntityStatus: An instance of `EntityStatus`.
"""
return cls(
small_entity_status_indicator=data.get("smallEntityStatusIndicator"),
business_entity_status_category=data.get("businessEntityStatusCategory"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `EntityStatus` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
return {
"smallEntityStatusIndicator": self.small_entity_status_indicator,
"businessEntityStatusCategory": self.business_entity_status_category,
}
[docs]
@dataclass(frozen=True)
class CustomerNumberCorrespondence:
"""Represents correspondence data associated with a USPTO customer number.
Includes patron identifier, organization name, power of attorney addresses, and telecommunication details.
Attributes:
patron_identifier: The USPTO customer number.
organization_standard_name: The name of the organization associated with the customer number.
power_of_attorney_address_bag: List of `Address` objects for power of attorney.
telecommunication_address_bag: List of `Telecommunication` objects.
"""
patron_identifier: Optional[int] = None
organization_standard_name: Optional[str] = None
power_of_attorney_address_bag: List[Address] = field(default_factory=list)
telecommunication_address_bag: List[Telecommunication] = field(default_factory=list)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CustomerNumberCorrespondence":
"""Creates a `CustomerNumberCorrespondence` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with customer number correspondence data.
Returns:
CustomerNumberCorrespondence: An instance of `CustomerNumberCorrespondence`.
"""
addrs = [
Address.from_dict(a)
for a in data.get("powerOfAttorneyAddressBag", [])
if isinstance(a, dict)
]
telecoms = [
Telecommunication.from_dict(t)
for t in data.get("telecommunicationAddressBag", [])
if isinstance(t, dict)
]
return cls(
patron_identifier=data.get("patronIdentifier"),
organization_standard_name=data.get("organizationStandardName"),
power_of_attorney_address_bag=addrs,
telecommunication_address_bag=telecoms,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `CustomerNumberCorrespondence` instance to a dictionary.
Omits keys with None values or empty lists.
Returns:
Dict[str, Any]: Dictionary representation.
"""
d = {
"patronIdentifier": self.patron_identifier,
"organizationStandardName": self.organization_standard_name,
"powerOfAttorneyAddressBag": [
a.to_dict() for a in self.power_of_attorney_address_bag
],
"telecommunicationAddressBag": [
t.to_dict() for t in self.telecommunication_address_bag
],
}
return {
k: v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class RecordAttorney:
"""Represents information about the attorney(s) of record for a patent application.
Contains customer number correspondence data, power of attorney information, and listed attorneys.
Attributes:
customer_number_correspondence_data: `CustomerNumberCorrespondence` object with customer number details.
power_of_attorney_bag: List of `Attorney` objects named in a power of attorney.
attorney_bag: List of `Attorney` objects listed as attorneys of record.
"""
customer_number_correspondence_data: Optional[CustomerNumberCorrespondence] = None
power_of_attorney_bag: List[Attorney] = field(default_factory=list)
attorney_bag: List[Attorney] = field(default_factory=list)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RecordAttorney":
"""Creates a `RecordAttorney` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with record attorney data.
Returns:
RecordAttorney: An instance of `RecordAttorney`.
"""
cust_corr_data = data.get("customerNumberCorrespondenceData")
cust_corr = (
CustomerNumberCorrespondence.from_dict(cust_corr_data)
if isinstance(cust_corr_data, dict)
else None
)
poa_bag = [
Attorney.from_dict(a)
for a in data.get("powerOfAttorneyBag", [])
if isinstance(a, dict)
]
att_bag = [
Attorney.from_dict(a)
for a in data.get("attorneyBag", [])
if isinstance(a, dict)
]
return cls(
customer_number_correspondence_data=cust_corr,
power_of_attorney_bag=poa_bag,
attorney_bag=att_bag,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `RecordAttorney` instance to a dictionary.
Omits keys with None values. Includes empty lists to match API behavior.
Returns:
Dict[str, Any]: Dictionary representation.
"""
d = {
"customerNumberCorrespondenceData": (
self.customer_number_correspondence_data.to_dict()
if self.customer_number_correspondence_data
else None
),
"powerOfAttorneyBag": [p.to_dict() for p in self.power_of_attorney_bag],
"attorneyBag": [a.to_dict() for a in self.attorney_bag],
}
return {k: v for k, v in d.items() if v is not None}
[docs]
@dataclass(frozen=True)
class Assignor:
"""Represents an assignor in a patent assignment.
Attributes:
assignor_name: The name of the assigning party.
execution_date: The date the assignment was executed.
"""
assignor_name: Optional[str] = None
execution_date: Optional[date] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Assignor":
"""Creates an `Assignor` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with assignor data.
Returns:
Assignor: An instance of `Assignor`.
"""
return cls(
assignor_name=data.get("assignorName"),
execution_date=parse_to_date(data.get("executionDate")),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Assignor` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
return {
"assignorName": self.assignor_name,
"executionDate": serialize_date(self.execution_date),
}
[docs]
@dataclass(frozen=True)
class Assignee:
"""Represents an assignee in a patent assignment.
Attributes:
assignee_name_text: The name of the party receiving the assignment.
assignee_address: The `Address` of the assignee.
"""
assignee_name_text: Optional[str] = None
assignee_address: Optional[Address] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Assignee":
"""Creates an `Assignee` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with assignee data.
Returns:
Assignee: An instance of `Assignee`.
"""
addr_data = data.get("assigneeAddress")
addr = Address.from_dict(addr_data) if isinstance(addr_data, dict) else None
return cls(
assignee_name_text=data.get("assigneeNameText"), assignee_address=addr
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Assignee` instance to a dictionary.
Omits keys with None values.
Returns:
Dict[str, Any]: Dictionary representation.
"""
d = {
"assigneeNameText": self.assignee_name_text,
"assigneeAddress": (
self.assignee_address.to_dict() if self.assignee_address else None
),
}
return {k: v for k, v in d.items() if v is not None}
[docs]
@dataclass(frozen=True)
class Assignment:
"""Represents a patent assignment, detailing the transfer of rights.
Includes information about the reel and frame, document location, dates, conveyance text,
and bags of assignors, assignees, correspondence address, and domestic representative.
Attributes:
reel_number: Reel number for the assignment record.
frame_number: Frame number for the assignment record.
reel_and_frame_number: Combined reel and frame number.
page_total_quantity: Total number of pages in the assignment document.
assignment_document_location_uri: URI for the assignment document.
assignment_received_date: Date the assignment was received by USPTO.
assignment_recorded_date: Date the assignment was recorded by USPTO.
assignment_mailed_date: Date the assignment notification was mailed.
conveyance_text: Text describing the nature of the conveyance.
image_available_status_code: Code to indicate the availability of the image.
attorney_docket_number: Attorney docket number for the assignment.
assignor_bag: List of `Assignor` objects.
assignee_bag: List of `Assignee` objects.
correspondence_address: `Address` object for correspondence (single object).
domestic_representative: `Address` object for the domestic representative.
"""
reel_number: Optional[int] = None
frame_number: Optional[int] = None
reel_and_frame_number: Optional[str] = None
page_total_quantity: Optional[int] = None
assignment_document_location_uri: Optional[str] = None
assignment_received_date: Optional[date] = None
assignment_recorded_date: Optional[date] = None
assignment_mailed_date: Optional[date] = None
conveyance_text: Optional[str] = None
image_available_status_code: Optional[bool] = None
attorney_docket_number: Optional[str] = None
assignor_bag: List[Assignor] = field(default_factory=list)
assignee_bag: List[Assignee] = field(default_factory=list)
correspondence_address: Optional[Address] = None
domestic_representative: Optional[Address] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Assignment":
"""Creates an `Assignment` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with assignment data.
Returns:
Assignment: An instance of `Assignment`.
"""
assignors = [
Assignor.from_dict(a)
for a in data.get("assignorBag", [])
if isinstance(a, dict)
]
assignees = [
Assignee.from_dict(a)
for a in data.get("assigneeBag", [])
if isinstance(a, dict)
]
# Parse correspondence address (single object, not bag)
corr_addr_data = data.get("correspondenceAddress")
corr_addr = (
Address.from_dict(corr_addr_data)
if isinstance(corr_addr_data, dict)
else None
)
# Parse domestic representative
dom_rep_data = data.get("domesticRepresentative")
dom_rep = (
Address.from_dict(dom_rep_data) if isinstance(dom_rep_data, dict) else None
)
return cls(
reel_number=data.get("reelNumber"),
frame_number=data.get("frameNumber"),
reel_and_frame_number=data.get("reelAndFrameNumber"),
page_total_quantity=data.get("pageTotalQuantity"),
assignment_document_location_uri=data.get("assignmentDocumentLocationURI"),
assignment_received_date=parse_to_date(data.get("assignmentReceivedDate")),
assignment_recorded_date=parse_to_date(data.get("assignmentRecordedDate")),
assignment_mailed_date=parse_to_date(data.get("assignmentMailedDate")),
conveyance_text=data.get("conveyanceText"),
image_available_status_code=data.get("imageAvailableStatusCode"),
attorney_docket_number=data.get("attorneyDocketNumber"),
assignor_bag=assignors,
assignee_bag=assignees,
correspondence_address=corr_addr,
domestic_representative=dom_rep,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Assignment` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
_dict = {
"reelNumber": self.reel_number,
"frameNumber": self.frame_number,
"reelAndFrameNumber": self.reel_and_frame_number,
"pageTotalQuantity": self.page_total_quantity,
"assignmentDocumentLocationURI": self.assignment_document_location_uri,
"assignmentReceivedDate": serialize_date(self.assignment_received_date),
"assignmentRecordedDate": serialize_date(self.assignment_recorded_date),
"assignmentMailedDate": serialize_date(self.assignment_mailed_date),
"conveyanceText": self.conveyance_text,
"imageAvailableStatusCode": self.image_available_status_code,
"attorneyDocketNumber": self.attorney_docket_number,
"assignorBag": [a.to_dict() for a in self.assignor_bag],
"assigneeBag": [a.to_dict() for a in self.assignee_bag],
"correspondenceAddress": (
self.correspondence_address.to_dict()
if self.correspondence_address
else None
),
"domesticRepresentative": (
self.domestic_representative.to_dict()
if self.domestic_representative
else None
),
}
# Filter out None values to match API behavior
return {k: v for k, v in _dict.items() if v is not None}
[docs]
@dataclass(frozen=True)
class ForeignPriority:
"""Represents a foreign priority claim for a patent application.
Attributes:
ip_office_name: The name of the intellectual property office of the priority application.
filing_date: The filing date of the priority application.
application_number_text: The application number of the priority application.
"""
ip_office_name: Optional[str] = None
filing_date: Optional[date] = None
application_number_text: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ForeignPriority":
"""Creates a `ForeignPriority` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with foreign priority data.
Returns:
ForeignPriority: An instance of `ForeignPriority`.
"""
return cls(
ip_office_name=data.get("ipOfficeName"),
filing_date=parse_to_date(data.get("filingDate")),
application_number_text=data.get("applicationNumberText"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `ForeignPriority` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
return {
"ipOfficeName": self.ip_office_name,
"filingDate": serialize_date(self.filing_date),
"applicationNumberText": self.application_number_text,
}
[docs]
@dataclass(frozen=True)
class Continuity:
"""Base class representing continuity data for a patent application.
This includes details about the application's relationship to other applications (parent/child),
its filing status under AIA (America Invents Act), and key identifiers.
Attributes:
first_inventor_to_file_indicator: Boolean indicating if the application is under First-Inventor-to-File provisions.
application_number_text: The application number of the related (parent or child) application.
filing_date: The filing date of the related application.
status_code: The status code of the related application.
status_description_text: The status description of the related application.
patent_number: The patent number if the related application is granted.
claim_parentage_type_code: Code indicating the type of continuity claim (e.g., "CON", "DIV").
claim_parentage_type_code_description_text: Description of the continuity claim type.
"""
first_inventor_to_file_indicator: Optional[bool] = None
application_number_text: Optional[str] = None
filing_date: Optional[date] = None
status_code: Optional[int] = None
status_description_text: Optional[str] = None
patent_number: Optional[str] = None
claim_parentage_type_code: Optional[str] = None
claim_parentage_type_code_description_text: Optional[str] = None
@property
def is_aia(self) -> Optional[bool]:
"""Returns True if the application is AIA, False if pre-AIA, None if unknown."""
return self.first_inventor_to_file_indicator
@property
def is_pre_aia(self) -> Optional[bool]:
"""Returns True if the application is pre-AIA, False if AIA, None if unknown."""
if self.first_inventor_to_file_indicator is None:
return None
return not self.first_inventor_to_file_indicator
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `Continuity` instance to a dictionary.
Omits attributes that are None and property-derived fields.
Keys are converted to camelCase.
Returns:
Dict[str, Any]: A dictionary representation of the continuity data.
"""
return {
to_camel_case(k): v
for k, v in asdict(self).items()
if v is not None and not k.startswith("is_")
}
[docs]
@dataclass(frozen=True)
class ParentContinuity(Continuity):
"""Represents a parent application in a patent application's continuity chain.
Inherits from Continuity and adds specific fields for parent application details.
Attributes:
parent_application_status_code: Status code of the parent application.
parent_patent_number: Patent number of the parent application, if granted.
parent_application_status_description_text: Status description of the parent application.
parent_application_filing_date: Filing date of the parent application.
parent_application_number_text: Application number of the parent application.
child_application_number_text: Application number of the child (current) application.
"""
parent_application_status_code: Optional[int] = None
parent_patent_number: Optional[str] = None
parent_application_status_description_text: Optional[str] = None
parent_application_filing_date: Optional[date] = None
parent_application_number_text: Optional[str] = None
child_application_number_text: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ParentContinuity":
"""Creates a `ParentContinuity` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with parent continuity data.
Returns:
ParentContinuity: An instance of `ParentContinuity`.
"""
p_filing_date = parse_to_date(data.get("parentApplicationFilingDate"))
return cls(
first_inventor_to_file_indicator=data.get("firstInventorToFileIndicator"),
parent_application_status_code=data.get("parentApplicationStatusCode"),
parent_patent_number=data.get("parentPatentNumber"),
parent_application_status_description_text=data.get(
"parentApplicationStatusDescriptionText"
),
parent_application_filing_date=p_filing_date,
parent_application_number_text=data.get("parentApplicationNumberText"),
child_application_number_text=data.get("childApplicationNumberText"),
claim_parentage_type_code=data.get("claimParentageTypeCode"),
claim_parentage_type_code_description_text=data.get(
"claimParentageTypeCodeDescriptionText"
),
application_number_text=data.get("parentApplicationNumberText"),
filing_date=p_filing_date,
status_code=data.get("parentApplicationStatusCode"),
status_description_text=data.get("parentApplicationStatusDescriptionText"),
patent_number=data.get("parentPatentNumber"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `ParentContinuity` instance to a dictionary.
Maps attributes to specific camelCase keys expected by the API for parent continuity.
Filters out None values to match the API response structure.
Returns:
Dict[str, Any]: Dictionary representation.
"""
_dict = {
"firstInventorToFileIndicator": self.first_inventor_to_file_indicator,
"parentApplicationStatusCode": self.parent_application_status_code,
"parentPatentNumber": self.parent_patent_number,
"parentApplicationStatusDescriptionText": self.parent_application_status_description_text,
"parentApplicationFilingDate": serialize_date(
self.parent_application_filing_date
),
"parentApplicationNumberText": self.parent_application_number_text,
"childApplicationNumberText": self.child_application_number_text,
"claimParentageTypeCode": self.claim_parentage_type_code,
"claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text,
}
return {k: v for k, v in _dict.items() if v is not None}
[docs]
@dataclass(frozen=True)
class ChildContinuity(Continuity):
"""Represents a child application in a patent application's continuity chain.
Inherits from Continuity and adds specific fields for child application details.
Attributes:
child_application_status_code: Status code of the child application.
parent_application_number_text: Application number of the parent (current) application.
child_application_number_text: Application number of the child application.
child_application_status_description_text: Status description of the child application.
child_application_filing_date: Filing date of the child application.
child_patent_number: Patent number of the child application, if granted.
"""
child_application_status_code: Optional[int] = None
parent_application_number_text: Optional[str] = None
child_application_number_text: Optional[str] = None
child_application_status_description_text: Optional[str] = None
child_application_filing_date: Optional[date] = None
child_patent_number: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ChildContinuity":
"""Creates a `ChildContinuity` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with child continuity data.
Returns:
ChildContinuity: An instance of `ChildContinuity`.
"""
c_filing_date = parse_to_date(data.get("childApplicationFilingDate"))
return cls(
first_inventor_to_file_indicator=data.get("firstInventorToFileIndicator"),
child_application_status_code=data.get("childApplicationStatusCode"),
parent_application_number_text=data.get("parentApplicationNumberText"),
child_application_number_text=data.get("childApplicationNumberText"),
child_application_status_description_text=data.get(
"childApplicationStatusDescriptionText"
),
child_application_filing_date=c_filing_date,
child_patent_number=data.get("childPatentNumber"),
claim_parentage_type_code=data.get("claimParentageTypeCode"),
claim_parentage_type_code_description_text=data.get(
"claimParentageTypeCodeDescriptionText"
),
application_number_text=data.get("childApplicationNumberText"),
filing_date=c_filing_date,
status_code=data.get("childApplicationStatusCode"),
status_description_text=data.get("childApplicationStatusDescriptionText"),
patent_number=data.get("childPatentNumber"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `ChildContinuity` instance to a dictionary.
Maps attributes to specific camelCase keys expected by the API for child continuity.
Filters out None values to match the API response structure.
Returns:
Dict[str, Any]: Dictionary representation.
"""
_dict = {
"childApplicationStatusCode": self.child_application_status_code,
"parentApplicationNumberText": self.parent_application_number_text,
"childApplicationNumberText": self.child_application_number_text,
"childApplicationStatusDescriptionText": self.child_application_status_description_text,
"childApplicationFilingDate": serialize_date(
self.child_application_filing_date
),
"firstInventorToFileIndicator": self.first_inventor_to_file_indicator,
"childPatentNumber": self.child_patent_number,
"claimParentageTypeCode": self.claim_parentage_type_code,
"claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text,
}
return {k: v for k, v in _dict.items() if v is not None}
[docs]
@dataclass(frozen=True)
class PatentTermAdjustmentHistoryData:
"""Represents a single entry in the patent term adjustment (PTA) history for an application.
Details specific events, dates, and day quantities affecting the patent term.
Attributes:
event_date: Date of the PTA event.
applicant_day_delay_quantity: Number of days of delay attributable to the applicant for this event.
event_description_text: Textual description of the PTA event.
event_sequence_number: Sequence number of this event in the PTA history.
originating_event_sequence_number: Sequence number of an event that originated this event.
pta_pte_code: Code indicating if the event relates to PTA or Patent Term Extension (PTE).
ip_office_day_delay_quantity: Number of days of IP office delay used in adjustment calculation for this event.
"""
event_date: Optional[date] = None
applicant_day_delay_quantity: Optional[float] = None
event_description_text: Optional[str] = None
event_sequence_number: Optional[float] = None
originating_event_sequence_number: Optional[float] = None
pta_pte_code: Optional[str] = None
ip_office_day_delay_quantity: Optional[float] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentHistoryData":
"""Creates a `PatentTermAdjustmentHistoryData` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with PTA history event data.
Returns:
PatentTermAdjustmentHistoryData: An instance of `PatentTermAdjustmentHistoryData`.
"""
return cls(
event_date=parse_to_date(data.get("eventDate")),
applicant_day_delay_quantity=data.get("applicantDayDelayQuantity"),
event_description_text=data.get("eventDescriptionText"),
event_sequence_number=data.get("eventSequenceNumber"),
originating_event_sequence_number=data.get(
"originatingEventSequenceNumber"
),
pta_pte_code=data.get("ptaPTECode"),
ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `PatentTermAdjustmentHistoryData` instance to a dictionary.
Omits keys with None values.
Returns:
Dict[str, Any]: Dictionary representation with camelCase keys.
"""
final_dict: Dict[str, Any] = {}
if self.event_date is not None:
final_dict["eventDate"] = serialize_date(self.event_date)
if self.applicant_day_delay_quantity is not None:
final_dict["applicantDayDelayQuantity"] = self.applicant_day_delay_quantity
if self.event_description_text is not None:
final_dict["eventDescriptionText"] = self.event_description_text
if self.event_sequence_number is not None:
final_dict["eventSequenceNumber"] = self.event_sequence_number
if self.originating_event_sequence_number is not None:
final_dict["originatingEventSequenceNumber"] = (
self.originating_event_sequence_number
)
if self.pta_pte_code is not None:
final_dict["ptaPTECode"] = self.pta_pte_code
if self.ip_office_day_delay_quantity is not None:
final_dict["ipOfficeDayDelayQuantity"] = self.ip_office_day_delay_quantity
return final_dict
[docs]
@dataclass(frozen=True)
class PatentTermAdjustmentData:
"""Represents the overall patent term adjustment (PTA) data for an application.
Includes various delay quantities (A, B, C, applicant, IP office), total adjustment,
and a history of PTA events.
Attributes:
a_delay_quantity: Number of days of 'A' delay.
adjustment_total_quantity: Total calculated PTA in days.
applicant_day_delay_quantity: Total days of delay attributable to the applicant.
b_delay_quantity: Number of days of 'B' delay.
c_delay_quantity: Number of days of 'C' delay.
non_overlapping_day_quantity: Number of non-overlapping delay days.
overlapping_day_quantity: Number of overlapping delay days.
non_overlapping_day_delay_quantity: Number of non-overlapping delay days specifically for delay calculation.
ip_office_adjustment_delay_quantity: Days of IP office delay used in adjustment calculation.
patent_term_adjustment_history_data_bag: List of `PatentTermAdjustmentHistoryData` events.
"""
a_delay_quantity: Optional[float] = None
adjustment_total_quantity: Optional[float] = None
applicant_day_delay_quantity: Optional[float] = None
b_delay_quantity: Optional[float] = None
c_delay_quantity: Optional[float] = None
non_overlapping_day_quantity: Optional[float] = None
overlapping_day_quantity: Optional[float] = None
non_overlapping_day_delay_quantity: Optional[float] = None
ip_office_adjustment_delay_quantity: Optional[float] = None
patent_term_adjustment_history_data_bag: List[PatentTermAdjustmentHistoryData] = (
field(default_factory=list)
)
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentData":
"""Creates a `PatentTermAdjustmentData` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with PTA data.
Returns:
PatentTermAdjustmentData: An instance of `PatentTermAdjustmentData`.
"""
history = [
PatentTermAdjustmentHistoryData.from_dict(h)
for h in data.get("patentTermAdjustmentHistoryDataBag", [])
if isinstance(h, dict)
]
return cls(
a_delay_quantity=data.get("aDelayQuantity"),
adjustment_total_quantity=data.get("adjustmentTotalQuantity"),
applicant_day_delay_quantity=data.get("applicantDayDelayQuantity"),
b_delay_quantity=data.get("bDelayQuantity"),
c_delay_quantity=data.get("cDelayQuantity"),
non_overlapping_day_quantity=data.get("nonOverlappingDayQuantity"),
overlapping_day_quantity=data.get("overlappingDayQuantity"),
non_overlapping_day_delay_quantity=data.get(
"nonOverlappingDayDelayQuantity"
),
ip_office_adjustment_delay_quantity=data.get(
"ipOfficeAdjustmentDelayQuantity"
),
patent_term_adjustment_history_data_bag=history,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `PatentTermAdjustmentData` instance to a dictionary.
Omits keys with None values or empty lists, and converts field names to camelCase.
Returns:
Dict[str, Any]: Dictionary representation.
"""
d = asdict(self)
d["patentTermAdjustmentHistoryDataBag"] = [
h.to_dict() for h in self.patent_term_adjustment_history_data_bag
]
return {
to_camel_case(k): v
for k, v in d.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class EventData:
"""Represents a single event in the transaction history of a patent application.
Attributes:
event_code: A code identifying the type of event.
event_description_text: A textual description of the event.
event_date: The date the event was recorded.
"""
event_code: Optional[str] = None
event_description_text: Optional[str] = None
event_date: Optional[date] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "EventData":
"""Creates an `EventData` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with event data.
Returns:
EventData: An instance of `EventData`.
"""
return cls(
event_code=data.get("eventCode"),
event_description_text=data.get("eventDescriptionText"),
event_date=parse_to_date(data.get("eventDate")),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `EventData` instance to a dictionary.
Omits keys with None values and converts field names to camelCase.
Returns:
Dict[str, Any]: Dictionary representation.
"""
d = asdict(self)
d["eventDate"] = serialize_date(self.event_date)
return {to_camel_case(k): v for k, v in d.items() if v is not None}
[docs]
@dataclass(frozen=True)
class PatentFileWrapper:
"""Represents the complete file wrapper for a single patent application.
This is a top-level object containing all data sections related to an application,
such as metadata, addresses, assignments, attorney information, continuity data,
PTA data, transaction events, and associated document metadata.
Attributes:
application_number_text: The primary application number.
application_meta_data: Comprehensive `ApplicationMetaData`.
correspondence_address_bag: List of `Address` objects for correspondence.
assignment_bag: List of `Assignment` records.
record_attorney: Information about the `RecordAttorney`.
foreign_priority_bag: List of `ForeignPriority` claims.
parent_continuity_bag: List of `ParentContinuity` records.
child_continuity_bag: List of `ChildContinuity` records.
patent_term_adjustment_data: `PatentTermAdjustmentData` details.
event_data_bag: List of `EventData` (transaction history).
pgpub_document_meta_data: `PrintedMetaData` for Pre-Grant Publication.
grant_document_meta_data: `PrintedMetaData` for the granted patent.
last_ingestion_date_time: Timestamp of when this data was last ingested by the API (UTC).
"""
application_number_text: Optional[str] = None
application_meta_data: Optional[ApplicationMetaData] = None
correspondence_address_bag: List[Address] = field(default_factory=list)
assignment_bag: List[Assignment] = field(default_factory=list)
record_attorney: Optional[RecordAttorney] = None
foreign_priority_bag: List[ForeignPriority] = field(default_factory=list)
parent_continuity_bag: List[ParentContinuity] = field(default_factory=list)
child_continuity_bag: List[ChildContinuity] = field(default_factory=list)
patent_term_adjustment_data: Optional[PatentTermAdjustmentData] = None
event_data_bag: List[EventData] = field(default_factory=list)
pgpub_document_meta_data: Optional[PrintedMetaData] = None
grant_document_meta_data: Optional[PrintedMetaData] = None
last_ingestion_date_time: Optional[datetime] = None
[docs]
@classmethod
def from_dict(
cls, data: Dict[str, Any], include_raw_data: bool = False
) -> "PatentFileWrapper":
"""Creates a `PatentFileWrapper` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with patent file wrapper data.
include_raw_data (bool): If True, store the raw JSON for debugging.
Returns:
PatentFileWrapper: An instance of `PatentFileWrapper`.
"""
amd_json = data.get("applicationMetaData")
amd = (
ApplicationMetaData.from_dict(amd_json, include_raw_data=include_raw_data)
if isinstance(amd_json, dict)
else None
)
corr_addrs = [
Address.from_dict(a)
for a in data.get("correspondenceAddressBag", [])
if isinstance(a, dict)
]
assigns = [
Assignment.from_dict(a)
for a in data.get("assignmentBag", [])
if isinstance(a, dict)
]
rec_att_json = data.get("recordAttorney")
rec_att = (
RecordAttorney.from_dict(rec_att_json)
if isinstance(rec_att_json, dict)
else None
)
f_pris = [
ForeignPriority.from_dict(fp)
for fp in data.get("foreignPriorityBag", [])
if isinstance(fp, dict)
]
p_conts = [
ParentContinuity.from_dict(pc)
for pc in data.get("parentContinuityBag", [])
if isinstance(pc, dict)
]
c_conts = [
ChildContinuity.from_dict(cc)
for cc in data.get("childContinuityBag", [])
if isinstance(cc, dict)
]
pta_json = data.get("patentTermAdjustmentData")
pta = (
PatentTermAdjustmentData.from_dict(pta_json)
if isinstance(pta_json, dict)
else None
)
evts = [
EventData.from_dict(e)
for e in data.get("eventDataBag", [])
if isinstance(e, dict)
]
pgpub_json = data.get("pgpubDocumentMetaData")
pgpub = (
PrintedMetaData.from_dict(pgpub_json)
if isinstance(pgpub_json, dict)
else None
)
grant_json = data.get("grantDocumentMetaData")
grant = (
PrintedMetaData.from_dict(grant_json)
if isinstance(grant_json, dict)
else None
)
return cls(
application_number_text=data.get("applicationNumberText"),
application_meta_data=amd,
correspondence_address_bag=corr_addrs,
assignment_bag=assigns,
record_attorney=rec_att,
foreign_priority_bag=f_pris,
parent_continuity_bag=p_conts,
child_continuity_bag=c_conts,
patent_term_adjustment_data=pta,
event_data_bag=evts,
pgpub_document_meta_data=pgpub,
grant_document_meta_data=grant,
last_ingestion_date_time=parse_to_datetime_utc(
data.get("lastIngestionDateTime")
),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `PatentFileWrapper` instance to a dictionary.
Omits keys with None values or empty lists. Serializes nested objects.
Returns:
Dict[str, Any]: Dictionary representation.
"""
_dict = {
"applicationNumberText": self.application_number_text,
"applicationMetaData": (
self.application_meta_data.to_dict()
if self.application_meta_data
else None
),
"correspondenceAddressBag": [
a.to_dict() for a in self.correspondence_address_bag
],
"assignmentBag": [a.to_dict() for a in self.assignment_bag],
"recordAttorney": (
self.record_attorney.to_dict() if self.record_attorney else None
),
"foreignPriorityBag": [fp.to_dict() for fp in self.foreign_priority_bag],
"parentContinuityBag": [pc.to_dict() for pc in self.parent_continuity_bag],
"childContinuityBag": [cc.to_dict() for cc in self.child_continuity_bag],
"patentTermAdjustmentData": (
self.patent_term_adjustment_data.to_dict()
if self.patent_term_adjustment_data
else None
),
"eventDataBag": [e.to_dict() for e in self.event_data_bag],
"pgpubDocumentMetaData": (
self.pgpub_document_meta_data.to_dict()
if self.pgpub_document_meta_data
else None
),
"grantDocumentMetaData": (
self.grant_document_meta_data.to_dict()
if self.grant_document_meta_data
else None
),
"lastIngestionDateTime": (
serialize_datetime_as_naive(self.last_ingestion_date_time)
if self.last_ingestion_date_time
else None
),
}
return {
k: v
for k, v in _dict.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class PatentDataResponse:
"""Represents the overall response from a patent data API request.
It typically includes a count of the results and a list of PatentFileWrapper objects,
each containing detailed data for a patent application.
Attributes:
count: The total number of patent applications found matching the query.
patent_file_wrapper_data_bag: A list of `PatentFileWrapper` objects.
request_identifier: An identifier for the API request, if provided.
raw_data: Optional raw JSON data from the API response (for debugging).
"""
count: int
patent_file_wrapper_data_bag: List[PatentFileWrapper] = field(default_factory=list)
request_identifier: Optional[str] = None
raw_data: Optional[str] = field(default=None, compare=False, repr=False)
[docs]
@classmethod
def from_dict(
cls, data: Dict[str, Any], include_raw_data: bool = False
) -> "PatentDataResponse":
"""Creates a `PatentDataResponse` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with API response data.
include_raw_data (bool): If True, store the raw JSON for debugging.
Returns:
PatentDataResponse: An instance of `PatentDataResponse`.
"""
wrappers = [
PatentFileWrapper.from_dict(w, include_raw_data=include_raw_data)
for w in data.get("patentFileWrapperDataBag", [])
if isinstance(w, dict)
]
return cls(
count=data.get("count", 0),
patent_file_wrapper_data_bag=wrappers,
request_identifier=data.get("requestIdentifier"),
raw_data=json.dumps(data) if include_raw_data else None,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `PatentDataResponse` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation.
"""
_dict = {
"count": self.count,
"patentFileWrapperDataBag": [
w.to_dict() for w in self.patent_file_wrapper_data_bag
],
"requestIdentifier": self.request_identifier,
}
return {
k: v
for k, v in _dict.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
def to_csv(self) -> str:
"""Converts the patent data in this response to a CSV formatted string.
The CSV will contain key metadata fields for each application,
such as invention title, application number, filing date, status, etc.
Returns:
str: A string containing the data in CSV format.
"""
headers = [
"inventionTitle",
"applicationNumberText",
"filingDate",
"applicationTypeLabelName",
"publicationCategoryBag",
"applicationStatusDescriptionText",
"applicationStatusDate",
"firstInventorName",
]
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(headers)
if not self.patent_file_wrapper_data_bag:
return output.getvalue()
for wrapper in self.patent_file_wrapper_data_bag:
if not wrapper.application_meta_data:
continue
meta = wrapper.application_meta_data
pub_category_str = (
"|".join(meta.publication_category_bag)
if meta.publication_category_bag
else ""
)
row_data = [
meta.invention_title or "",
wrapper.application_number_text or "",
serialize_date(meta.filing_date) or "",
meta.application_type_label_name or "",
pub_category_str,
meta.application_status_description_text or "",
serialize_date(meta.application_status_date) or "",
meta.first_inventor_name or "",
]
writer.writerow(row_data)
return output.getvalue()
[docs]
@dataclass(frozen=True)
class StatusCode:
"""Represents a USPTO application status code and its textual description.
Attributes:
code: The numeric status code.
description: The textual description of the status code.
"""
code: Optional[int] = None
description: Optional[str] = None
[docs]
def __str__(self) -> str:
"""Returns a user-friendly string representation of the status code."""
return f"{self.code}: {self.description}"
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "StatusCode":
"""Creates a `StatusCode` instance from a dictionary.
Handles two possible key sets from the API for status information.
Args:
data (Dict[str, Any]): Dictionary with status code data.
Returns:
StatusCode: An instance of `StatusCode`.
"""
if "code" in data:
return cls(
code=data.get("code"),
description=data.get("description"),
)
else:
return cls(
code=data.get("applicationStatusCode"),
description=data.get("applicationStatusDescriptionText"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `StatusCode` instance to a dictionary.
Uses keys "applicationStatusCode" and "applicationStatusDescriptionText"
for consistency with some API response parts.
Returns:
Dict[str, Any]: Dictionary representation.
"""
return {
"applicationStatusCode": self.code,
"applicationStatusDescriptionText": self.description,
}
[docs]
class StatusCodeCollection:
"""A collection of StatusCode objects.
Provides iterable access and helper methods to find or filter status codes.
This class is immutable by convention after initialization.
Attributes:
status_codes (tuple[StatusCode, ...]): An immutable tuple of `StatusCode` objects.
"""
[docs]
def __init__(self, status_codes: List[StatusCode]):
"""Initializes the StatusCodeCollection with a list of status codes.
Args:
status_codes (List[StatusCode]): A list of `StatusCode` instances.
"""
self._status_codes: tuple[StatusCode, ...] = tuple(status_codes)
def __iter__(self) -> Iterator[StatusCode]:
return iter(self._status_codes)
def __len__(self) -> int:
return len(self._status_codes)
def __getitem__(self, index: int) -> StatusCode:
return self._status_codes[index]
def __str__(self) -> str:
return f"StatusCodeCollection with {len(self)} status codes."
def __repr__(self) -> str:
if not self._status_codes:
return "StatusCodeCollection(empty)"
if len(self._status_codes) <= 3:
codes = ", ".join(str(s.code) for s in self._status_codes)
return f"StatusCodeCollection({len(self)} status codes: {codes})"
else:
first_codes = ", ".join(str(s.code) for s in self._status_codes[:3])
return f"StatusCodeCollection({len(self)} status codes: {first_codes}, ...)"
[docs]
def find_by_code(self, code_to_find: int) -> Optional[StatusCode]:
"""Finds a status code by its numeric code.
Args:
code_to_find (int): The numeric status code to search for.
Returns:
Optional[StatusCode]: The `StatusCode` object if found, otherwise None.
"""
for status in self._status_codes:
if status.code == code_to_find:
return status
return None
[docs]
def search_by_description(self, text: str) -> "StatusCodeCollection":
"""Searches for status codes by a case-insensitive text match in their description.
Args:
text (str): The text to search for within status code descriptions.
Returns:
StatusCodeCollection: A new collection containing matching status codes.
"""
matching = [
s
for s in self._status_codes
if s.description and text.lower() in s.description.lower()
]
return StatusCodeCollection(status_codes=matching)
[docs]
def to_dict(self) -> List[Dict[str, Any]]:
"""Converts the collection of status codes to a list of dictionaries.
Returns:
List[Dict[str, Any]]: A list where each item is the dictionary
representation of a `StatusCode`.
"""
return [sc.to_dict() for sc in self._status_codes]
[docs]
@dataclass(frozen=True)
class StatusCodeSearchResponse:
"""Represents the response from a search query for patent application status codes.
Attributes:
count: The total number of status codes found matching the query.
status_code_bag: A `StatusCodeCollection` of the `StatusCode` objects returned.
request_identifier: An identifier for the API request, if provided.
"""
count: int
status_code_bag: StatusCodeCollection
request_identifier: Optional[str] = None
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "StatusCodeSearchResponse":
"""Creates a `StatusCodeSearchResponse` instance from a dictionary.
Args:
data (Dict[str, Any]): Dictionary with API response data for status codes.
Returns:
StatusCodeSearchResponse: An instance of `StatusCodeSearchResponse`.
"""
codes_json = data.get("statusCodeBag", [])
parsed_codes = (
[StatusCode.from_dict(cd) for cd in codes_json if isinstance(cd, dict)]
if isinstance(codes_json, list)
else []
)
collection = StatusCodeCollection(parsed_codes)
return cls(
count=data.get("count", 0),
status_code_bag=collection,
request_identifier=data.get("requestIdentifier"),
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `StatusCodeSearchResponse` instance to a dictionary.
Omits keys with None values or empty lists.
Returns:
Dict[str, Any]: Dictionary representation.
"""
_dict = {
"count": self.count,
"statusCodeBag": self.status_code_bag.to_dict(),
"requestIdentifier": self.request_identifier,
}
return {
k: v
for k, v in _dict.items()
if v is not None and (not isinstance(v, list) or v)
}
[docs]
@dataclass(frozen=True)
class ApplicationContinuityData:
"""Holds parent and child continuity application data for a specific patent application.
This class consolidates lists of ParentContinuity and ChildContinuity objects,
representing the lineage of an application.
Attributes:
parent_continuity_bag: List of `ParentContinuity` objects.
child_continuity_bag: List of `ChildContinuity` objects.
"""
parent_continuity_bag: List[ParentContinuity] = field(default_factory=list)
child_continuity_bag: List[ChildContinuity] = field(default_factory=list)
[docs]
@classmethod
def from_wrapper(cls, wrapper: PatentFileWrapper) -> "ApplicationContinuityData":
"""Creates an `ApplicationContinuityData` instance from a `PatentFileWrapper`.
Extracts parent and child continuity bags from the wrapper.
Args:
wrapper (PatentFileWrapper): The patent file wrapper containing continuity data.
Returns:
ApplicationContinuityData: An instance of `ApplicationContinuityData`.
"""
return cls(
parent_continuity_bag=wrapper.parent_continuity_bag,
child_continuity_bag=wrapper.child_continuity_bag,
)
[docs]
def to_dict(
self,
) -> Dict[str, Any]:
"""Converts the `ApplicationContinuityData` instance to a dictionary.
Returns:
Dict[str, Any]: Dictionary representation with "parentContinuityBag"
and "childContinuityBag" keys.
"""
return {
"parentContinuityBag": [pc.to_dict() for pc in self.parent_continuity_bag],
"childContinuityBag": [cc.to_dict() for cc in self.child_continuity_bag],
}
[docs]
@dataclass(frozen=True)
class PrintedPublication:
"""Holds metadata for associated documents like Pre-Grant Publications (PGPUB)
and Grant documents for a specific patent application.
Attributes:
pgpub_document_meta_data: `PrintedMetaData` for the Pre-Grant Publication, if any.
grant_document_meta_data: `PrintedMetaData` for the Grant document, if any.
"""
pgpub_document_meta_data: Optional[PrintedMetaData] = None
grant_document_meta_data: Optional[PrintedMetaData] = None
[docs]
@classmethod
def from_wrapper(cls, wrapper: PatentFileWrapper) -> "PrintedPublication":
"""Creates a `PrintedPublication` instance from a `PatentFileWrapper`.
Extracts PGPUB and Grant document metadata from the wrapper.
Args:
wrapper (PatentFileWrapper): The patent file wrapper.
Returns:
PrintedPublication: An instance of `PrintedPublication`.
"""
return cls(
pgpub_document_meta_data=wrapper.pgpub_document_meta_data,
grant_document_meta_data=wrapper.grant_document_meta_data,
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Converts the `PrintedPublication` instance to a dictionary.
Omits keys if their corresponding metadata is None.
Returns:
Dict[str, Any]: Dictionary representation.
"""
return {
"pgpubDocumentMetaData": (
self.pgpub_document_meta_data.to_dict()
if self.pgpub_document_meta_data
else None
),
"grantDocumentMetaData": (
self.grant_document_meta_data.to_dict()
if self.grant_document_meta_data
else None
),
}