67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
|
|
"""Domain ports for compliance history persistence."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class FindingRecord:
|
||
|
|
"""Single finding row linked to an analysis."""
|
||
|
|
id: str
|
||
|
|
analysis_id: str
|
||
|
|
seq: int
|
||
|
|
title: str
|
||
|
|
description: str
|
||
|
|
status: str # "ok" | "warn" | "risk"
|
||
|
|
clause_ref: Optional[str] = None
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class AnalysisRecord:
|
||
|
|
"""Full compliance analysis record with nested findings."""
|
||
|
|
id: str # UUID string; empty string means not yet persisted
|
||
|
|
created_at: datetime
|
||
|
|
created_by: Optional[str]
|
||
|
|
doc_name: str
|
||
|
|
standard_name: str
|
||
|
|
risk_score: int
|
||
|
|
conclusion: str
|
||
|
|
actions: list # list[dict] — serialised action items
|
||
|
|
para_text: str
|
||
|
|
highlight_terms: list # list[str]
|
||
|
|
findings: list[FindingRecord] = field(default_factory=list)
|
||
|
|
|
||
|
|
|
||
|
|
class ComplianceRepository(ABC):
|
||
|
|
"""Port for persisting and retrieving compliance analysis records."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def save_analysis(self, record: AnalysisRecord) -> str:
|
||
|
|
"""Persist a new analysis record and return the assigned UUID string."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def list_analyses(self, limit: int = 50, offset: int = 0) -> list[AnalysisRecord]:
|
||
|
|
"""Return analyses ordered by created_at DESC, without nested findings."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def get_analysis(self, analysis_id: str) -> Optional[AnalysisRecord]:
|
||
|
|
"""Return a single analysis with all nested findings, or None."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def delete_analysis(self, analysis_id: str) -> None:
|
||
|
|
"""Delete an analysis and all related findings and chat messages (cascade)."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def save_message(self, analysis_id: str, finding_id: str, role: str, content: str) -> str:
|
||
|
|
"""Persist a chat message and return its UUID string."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def get_messages(self, finding_id: str) -> list[dict]:
|
||
|
|
"""Return chat messages for a finding ordered by created_at ASC.
|
||
|
|
|
||
|
|
Each dict has keys: id, role, content, created_at (ISO string).
|
||
|
|
"""
|