Some checks failed
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
186 lines
7.0 KiB
Python
186 lines
7.0 KiB
Python
"""Evidence pack generation with manifests and signatures."""
|
|
|
|
import io
|
|
from typing import Any
|
|
|
|
import structlog
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
class EvidencePackGenerator: # pylint: disable=too-few-public-methods
|
|
"""Generate evidence packs with manifests and signatures"""
|
|
|
|
def __init__(self, storage_client: Any) -> None:
|
|
self.storage = storage_client
|
|
|
|
async def create_evidence_pack( # pylint: disable=too-many-locals
|
|
self,
|
|
taxpayer_id: str,
|
|
tax_year: str,
|
|
scope: str,
|
|
evidence_items: list[dict[str, Any]],
|
|
) -> dict[str, Any]:
|
|
"""Create evidence pack with manifest and signatures"""
|
|
# pylint: disable=import-outside-toplevel
|
|
import hashlib
|
|
import json
|
|
import zipfile
|
|
from datetime import datetime
|
|
|
|
try:
|
|
# Create ZIP buffer
|
|
zip_buffer = io.BytesIO()
|
|
|
|
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
manifest: dict[str, Any] = {
|
|
"taxpayer_id": taxpayer_id,
|
|
"tax_year": tax_year,
|
|
"scope": scope,
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"evidence_items": [],
|
|
"signatures": {},
|
|
}
|
|
|
|
# Add evidence files to ZIP
|
|
for item in evidence_items:
|
|
doc_id = item["doc_id"]
|
|
page = item.get("page")
|
|
bbox = item.get("bbox")
|
|
text_hash = item.get("text_hash")
|
|
|
|
# Get document content
|
|
doc_content = await self.storage.get_object(
|
|
bucket_name="raw-documents",
|
|
object_name=f"tenants/{taxpayer_id}/raw/{doc_id}.pdf",
|
|
)
|
|
|
|
if doc_content:
|
|
# Add to ZIP
|
|
zip_filename = f"documents/{doc_id}.pdf"
|
|
zip_file.writestr(zip_filename, doc_content)
|
|
|
|
# Calculate file hash
|
|
file_hash = hashlib.sha256(doc_content).hexdigest()
|
|
|
|
# Add to manifest
|
|
manifest["evidence_items"].append(
|
|
{
|
|
"doc_id": doc_id,
|
|
"filename": zip_filename,
|
|
"page": page,
|
|
"bbox": bbox,
|
|
"text_hash": text_hash,
|
|
"file_hash": file_hash,
|
|
"file_size": len(doc_content),
|
|
}
|
|
)
|
|
|
|
# Sign manifest
|
|
manifest_json = json.dumps(manifest, indent=2, sort_keys=True)
|
|
manifest_hash = hashlib.sha256(manifest_json.encode()).hexdigest()
|
|
|
|
manifest["signatures"]["manifest_hash"] = manifest_hash
|
|
manifest["signatures"]["algorithm"] = "SHA-256"
|
|
|
|
# Add manifest to ZIP
|
|
zip_file.writestr("manifest.json", json.dumps(manifest, indent=2))
|
|
|
|
# Get ZIP content
|
|
zip_content = zip_buffer.getvalue()
|
|
|
|
# Store evidence pack
|
|
pack_filename = f"evidence_pack_{taxpayer_id}_{tax_year}_{scope}.zip"
|
|
pack_key = f"tenants/{taxpayer_id}/evidence_packs/{pack_filename}"
|
|
|
|
success = await self.storage.put_object(
|
|
bucket_name="evidence-packs",
|
|
object_name=pack_key,
|
|
data=io.BytesIO(zip_content),
|
|
length=len(zip_content),
|
|
content_type="application/zip",
|
|
)
|
|
|
|
if success:
|
|
return {
|
|
"pack_filename": pack_filename,
|
|
"pack_key": pack_key,
|
|
"pack_size": len(zip_content),
|
|
"evidence_count": len(evidence_items),
|
|
"manifest_hash": manifest_hash,
|
|
"s3_url": f"s3://evidence-packs/{pack_key}",
|
|
}
|
|
raise RuntimeError("Failed to store evidence pack")
|
|
|
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
logger.error("Failed to create evidence pack", error=str(e))
|
|
raise
|
|
|
|
|
|
# Form configuration for UK tax forms
|
|
UK_TAX_FORMS = {
|
|
"SA100": {
|
|
"name": "Self Assessment Tax Return",
|
|
"template_path": "forms/templates/SA100.pdf",
|
|
"boxes": {
|
|
"1": {"description": "Your name", "type": "text"},
|
|
"2": {"description": "Your address", "type": "text"},
|
|
"3": {"description": "Your UTR", "type": "text"},
|
|
"4": {"description": "Your NI number", "type": "text"},
|
|
},
|
|
},
|
|
"SA103": {
|
|
"name": "Self-employment (full)",
|
|
"template_path": "forms/templates/SA103.pdf",
|
|
"boxes": {
|
|
"1": {"description": "Business name", "type": "text"},
|
|
"2": {"description": "Business description", "type": "text"},
|
|
"3": {"description": "Accounting period start", "type": "date"},
|
|
"4": {"description": "Accounting period end", "type": "date"},
|
|
"20": {"description": "Total turnover", "type": "currency"},
|
|
"31": {
|
|
"description": "Total allowable business expenses",
|
|
"type": "currency",
|
|
},
|
|
"32": {"description": "Net profit", "type": "currency"},
|
|
"33": {"description": "Balancing charges", "type": "currency"},
|
|
"34": {"description": "Goods/services for own use", "type": "currency"},
|
|
"35": {"description": "Total taxable profits", "type": "currency"},
|
|
},
|
|
},
|
|
"SA105": {
|
|
"name": "Property income",
|
|
"template_path": "forms/templates/SA105.pdf",
|
|
"boxes": {
|
|
"20": {"description": "Total rents and other income", "type": "currency"},
|
|
"29": {
|
|
"description": "Premiums for the grant of a lease",
|
|
"type": "currency",
|
|
},
|
|
"31": {
|
|
"description": "Rent, rates, insurance, ground rents etc",
|
|
"type": "currency",
|
|
},
|
|
"32": {"description": "Property management", "type": "currency"},
|
|
"33": {
|
|
"description": "Services provided, including wages",
|
|
"type": "currency",
|
|
},
|
|
"34": {
|
|
"description": "Repairs, maintenance and renewals",
|
|
"type": "currency",
|
|
},
|
|
"35": {
|
|
"description": "Finance costs, including interest",
|
|
"type": "currency",
|
|
},
|
|
"36": {"description": "Professional fees", "type": "currency"},
|
|
"37": {"description": "Costs of services provided", "type": "currency"},
|
|
"38": {
|
|
"description": "Other allowable property expenses",
|
|
"type": "currency",
|
|
},
|
|
},
|
|
},
|
|
}
|