Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
132 lines
5.2 KiB
Python
132 lines
5.2 KiB
Python
"""CCPA/CPRA Compliance Audit handler (LLM-based).
|
|
|
|
Generates a California Consumer Privacy Act / California Privacy Rights Act
|
|
compliance audit report. Also used for the "data-mapping" service which
|
|
shares similar analysis.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from .base_handler import BaseServiceHandler
|
|
|
|
SERVICE_SYSTEM_PROMPT = """You are a compliance analyst at Performance West Inc.
|
|
generating a CCPA/CPRA Privacy Compliance Audit report.
|
|
|
|
RULES:
|
|
- Write in professional, clear business English
|
|
- Cite specific CCPA/CPRA sections (Cal. Civ. Code §§ 1798.100-1798.199.100)
|
|
- Reference CPRA amendments effective January 1, 2023
|
|
- Never provide legal advice — use "we recommend" not "you must"
|
|
- For each finding: what was found, regulation, risk level (Low/Medium/High/Critical), remediation
|
|
- Consider CCPA applicability thresholds ($25M revenue, 100k consumers, 50% revenue from data)
|
|
- Address all consumer rights: know, delete, opt-out, correct, limit use of sensitive PI
|
|
- Reference CPPA enforcement guidance where applicable
|
|
"""
|
|
|
|
SECTIONS = [
|
|
{
|
|
"name": "executive_summary",
|
|
"prompt": (
|
|
"Write a 200-word executive summary of the CCPA/CPRA audit findings. "
|
|
"Include applicability determination, scope, overall compliance posture, "
|
|
"and critical findings."
|
|
),
|
|
},
|
|
{
|
|
"name": "data_inventory",
|
|
"prompt": (
|
|
"Analyze the organization's personal information inventory. Categories "
|
|
"of PI collected, sources, purposes, third-party sharing, sensitive PI "
|
|
"processing, retention periods, and cross-border transfers. Identify "
|
|
"gaps in data mapping documentation."
|
|
),
|
|
},
|
|
{
|
|
"name": "consumer_rights",
|
|
"prompt": (
|
|
"Assess compliance with each CCPA/CPRA consumer right: right to know, "
|
|
"right to delete, right to opt-out of sale/sharing, right to correct, "
|
|
"right to limit use of sensitive PI, right to non-discrimination. "
|
|
"For each: process exists (Y/N), response timeline, verification method, gaps."
|
|
),
|
|
},
|
|
{
|
|
"name": "privacy_notices",
|
|
"prompt": (
|
|
"Review privacy notices and disclosures: at-or-before-collection notice, "
|
|
"privacy policy, financial incentive notices, opt-out preference signals "
|
|
"compliance (GPC), 'Do Not Sell or Share' link. Assess content completeness "
|
|
"and accessibility."
|
|
),
|
|
},
|
|
{
|
|
"name": "vendor_management",
|
|
"prompt": (
|
|
"Review service provider and contractor agreements for CCPA/CPRA "
|
|
"compliance. Assess: data processing agreements, flow-down obligations, "
|
|
"audit rights, breach notification, and distinction between service "
|
|
"providers, contractors, and third parties under CPRA."
|
|
),
|
|
},
|
|
{
|
|
"name": "security_measures",
|
|
"prompt": (
|
|
"Evaluate reasonable security measures under CCPA § 1798.150 (private "
|
|
"right of action for data breaches). Review technical and organizational "
|
|
"safeguards, breach detection capabilities, and incident response readiness."
|
|
),
|
|
},
|
|
{
|
|
"name": "remediation_plan",
|
|
"prompt": (
|
|
"Provide a prioritized remediation plan. For each finding: reference, "
|
|
"risk level, recommended action, responsible party, timeline. Consider "
|
|
"CPPA enforcement priorities and the cure period provisions."
|
|
),
|
|
},
|
|
]
|
|
|
|
|
|
class CCPAAuditHandler(BaseServiceHandler):
|
|
SERVICE_SLUG = "ccpa-audit"
|
|
SERVICE_NAME = "CCPA/CPRA Compliance Audit"
|
|
TEMPLATE_NAME = "ccpa_audit_template.docx"
|
|
REQUIRES_LLM = True
|
|
|
|
async def process(self, order_data: dict) -> list[str]:
|
|
work_dir = self._make_work_dir()
|
|
order_number = order_data["name"]
|
|
context = self._extract_order_context(order_data)
|
|
|
|
# data-mapping uses same handler with a different template
|
|
items = order_data.get("items", [])
|
|
item_code = items[0].get("item_code", "") if items else ""
|
|
if item_code == "data-mapping":
|
|
template_name = "data_mapping_template.docx"
|
|
else:
|
|
template_name = self.TEMPLATE_NAME
|
|
|
|
template_path = self._get_template_path(template_name)
|
|
docx_filename = self._output_filename(order_number, "docx")
|
|
docx_path = os.path.join(work_dir, docx_filename)
|
|
|
|
variables = {
|
|
"order_number": order_number,
|
|
"customer_name": order_data.get("customer_name", ""),
|
|
"date": __import__("datetime").datetime.now().strftime("%B %d, %Y"),
|
|
"service_name": self.SERVICE_NAME,
|
|
"company_size": order_data.get("custom_company_size", "N/A"),
|
|
"industry": order_data.get("custom_industry", "N/A"),
|
|
"state": order_data.get("custom_state", "N/A"),
|
|
}
|
|
self._fill_template(template_path, variables, docx_path)
|
|
|
|
sections = await self._generate_sections(
|
|
SERVICE_SYSTEM_PROMPT, SECTIONS, context
|
|
)
|
|
self._add_sections_to_doc(docx_path, sections)
|
|
|
|
pdf_path = self._convert_to_pdf(docx_path)
|
|
return [docx_path, pdf_path]
|