"""
CodonAdaptPy Data Models
This module contains dependency-free dataclasses shared by parsing,
validation, analysis, optimization, reporting, and command-line workflows.
Models expose ``to_dict`` methods so results can be serialized without a
framework-specific schema library.
Classes:
- SequenceRecord: One coding sequence and its metadata.
- ValidationConfig: User-selected validation policy.
- ValidationIssue: One structured validation observation.
- ValidationReport: Validation outcome and normalized sequence.
- AnalysisResult: Metrics and metadata for a single sequence.
:Created: July 20, 2026
:Updated: July 20, 2026
:Author: Naveen Duhan
:Version: 1.0.2
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from typing import Any, Literal
[docs]
@dataclass(slots=True)
class SequenceRecord:
"""Store a named nucleotide sequence and arbitrary sample metadata."""
identifier: str
sequence: str
description: str = ""
metadata: dict[str, Any] = field(default_factory=dict)
[docs]
def to_dict(self) -> dict[str, Any]:
"""Return a JSON-compatible representation of the record."""
return asdict(self)
[docs]
@dataclass(slots=True)
class ValidationConfig:
"""Control normalization and coding-sequence quality checks.
Reading frames use zero-based offsets ``0``, ``1``, or ``2``. Ambiguous
codons may be rejected, skipped by downstream analyses, or retained with
warnings. Partial sequences relax start, stop, and divisible-by-three
requirements while preserving all other diagnostics.
"""
reading_frame: Literal[0, 1, 2] = 0
genetic_code: int = 1
require_start: bool = True
require_stop: bool = True
ambiguous_policy: Literal["reject", "skip", "allow"] = "reject"
allow_partial: bool = False
convert_rna: bool = True
@dataclass(slots=True)
class ValidationIssue:
"""Describe one validation warning or error with optional sequence locus."""
code: str
message: str
severity: Literal["warning", "error"]
position: int | None = None
[docs]
@dataclass(slots=True)
class ValidationReport:
"""Collect normalized sequence data and structured validation findings."""
identifier: str
normalized_sequence: str
issues: list[ValidationIssue] = field(default_factory=list)
genetic_code: int = 1
reading_frame: int = 0
@property
def is_valid(self) -> bool:
"""Return ``True`` when the report contains no error-level issues."""
return not any(issue.severity == "error" for issue in self.issues)
@property
def errors(self) -> list[ValidationIssue]:
"""Return only error-level findings."""
return [issue for issue in self.issues if issue.severity == "error"]
@property
def warnings(self) -> list[ValidationIssue]:
"""Return only warning-level findings."""
return [issue for issue in self.issues if issue.severity == "warning"]
[docs]
def to_dict(self) -> dict[str, Any]:
"""Return a JSON-compatible representation of the report."""
return asdict(self) | {"is_valid": self.is_valid}
@dataclass(slots=True)
class AnalysisResult:
"""Store validation, scalar metrics, profiles, and count tables."""
identifier: str
validation: ValidationReport
metrics: dict[str, Any]
codon_counts: dict[str, int]
rscu: dict[str, float]
amino_acid_composition: dict[str, float]
dinucleotide_frequencies: dict[str, float]
dinucleotide_odds: dict[str, float | None]
dinucleotide_bias: dict[str, str]
trinucleotide_frequencies: dict[str, float]
codon_pair_scores: dict[str, float] = field(default_factory=dict)
sliding_windows: dict[str, list[dict[str, float]]] = field(default_factory=dict)
host_comparisons: dict[str, dict[str, float]] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
"""Return all nested result fields in JSON-compatible form."""
return asdict(self)