Skip to content

Reports Module

Report generation and templates for compliance reporting.


ReportGenerator

ReportGenerator

Generator for compliance reports from audit data.

ReportGenerator retrieves audit entries from storage, runs compliance checks against configured frameworks, and produces comprehensive reports.

Attributes:

Name Type Description
storage

Storage backend for retrieving audit entries.

frameworks

Dictionary mapping framework names to implementations.

Example

from rotalabs_comply.audit.storage import MemoryStorage from rotalabs_comply.frameworks.eu_ai_act import EUAIActFramework

storage = MemoryStorage() frameworks = { ... "eu_ai_act": EUAIActFramework(), ... } generator = ReportGenerator(storage, frameworks)

Generate a report

report = await generator.generate( ... period_start=datetime(2026, 1, 1), ... period_end=datetime(2026, 1, 31), ... profile=profile, ... framework="eu_ai_act", ... )

Source code in src/rotalabs_comply/reports/generator.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
class ReportGenerator:
    """
    Generator for compliance reports from audit data.

    ReportGenerator retrieves audit entries from storage, runs compliance
    checks against configured frameworks, and produces comprehensive reports.

    Attributes:
        storage: Storage backend for retrieving audit entries.
        frameworks: Dictionary mapping framework names to implementations.

    Example:
        >>> from rotalabs_comply.audit.storage import MemoryStorage
        >>> from rotalabs_comply.frameworks.eu_ai_act import EUAIActFramework
        >>>
        >>> storage = MemoryStorage()
        >>> frameworks = {
        ...     "eu_ai_act": EUAIActFramework(),
        ... }
        >>> generator = ReportGenerator(storage, frameworks)
        >>>
        >>> # Generate a report
        >>> report = await generator.generate(
        ...     period_start=datetime(2026, 1, 1),
        ...     period_end=datetime(2026, 1, 31),
        ...     profile=profile,
        ...     framework="eu_ai_act",
        ... )
    """

    def __init__(
        self,
        audit_logger: StorageProtocol,
        frameworks: Optional[Dict[FrameworkName, ComplianceFramework]] = None,
    ) -> None:
        """
        Initialize the report generator.

        Args:
            audit_logger: Storage backend implementing list_entries method.
            frameworks: Optional dict mapping framework names to implementations.
                If not provided, compliance checks will be skipped.

        Example:
            >>> generator = ReportGenerator(storage)
            >>> generator = ReportGenerator(storage, {"soc2": SOC2Framework()})
        """
        self.storage = audit_logger
        self.frameworks = frameworks or {}

    async def generate(
        self,
        period_start: datetime,
        period_end: datetime,
        profile: ComplianceProfile,
        framework: Optional[FrameworkName] = None,
        format: Literal["markdown", "json", "html"] = "markdown",
    ) -> ComplianceReport:
        """
        Generate a comprehensive compliance report.

        Retrieves audit entries for the specified period, runs compliance
        checks, and generates a full report with all standard sections.

        Args:
            period_start: Start of the analysis period (inclusive).
            period_end: End of the analysis period (inclusive).
            profile: ComplianceProfile defining evaluation parameters.
            framework: Specific framework to report on (None = all in profile).
            format: Output format hint (used for template selection).

        Returns:
            ComplianceReport with all sections populated.

        Example:
            >>> report = await generator.generate(
            ...     period_start=datetime(2026, 1, 1),
            ...     period_end=datetime(2026, 1, 31),
            ...     profile=profile,
            ...     format="markdown",
            ... )
            >>> print(f"Generated: {report.title}")
            >>> print(f"Entries: {report.total_entries}")
            >>> print(f"Score: {report.compliance_score:.2%}")
        """
        # Retrieve audit entries
        entries = await self.storage.list_entries(period_start, period_end)
        total_entries = len(entries)

        # Determine which frameworks to evaluate
        frameworks_to_check = []
        if framework:
            frameworks_to_check = [framework]
        elif profile.enabled_frameworks:
            frameworks_to_check = profile.enabled_frameworks
        else:
            frameworks_to_check = list(self.frameworks.keys())

        # Run compliance checks
        all_results: List[ComplianceCheckResult] = []
        all_violations: List[ComplianceViolation] = []

        for fw_name in frameworks_to_check:
            if fw_name in self.frameworks:
                fw_impl = self.frameworks[fw_name]
                for entry in entries:
                    # Convert storage entry to AuditEntry if needed
                    audit_entry = self._convert_to_audit_entry(entry)
                    result = await fw_impl.check(audit_entry, profile)
                    all_results.append(result)
                    all_violations.extend(result.violations)

        # Calculate compliance metrics
        total_checks = sum(r.rules_checked for r in all_results)
        violations_count = len(all_violations)
        critical_violations = sum(
            1 for v in all_violations
            if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "critical"
        )
        high_violations = sum(
            1 for v in all_violations
            if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "high"
        )

        compliance_score = self._calculate_compliance_score(all_violations, total_checks)
        status = self._determine_status(compliance_score, critical_violations)

        # Prepare statistics for section generators
        stats = {
            "total_entries": total_entries,
            "violations_count": violations_count,
            "compliance_rate": compliance_score * 100,
            "critical_violations": critical_violations,
            "high_violations": high_violations,
            "period_start": period_start.strftime("%Y-%m-%d"),
            "period_end": period_end.strftime("%Y-%m-%d"),
            "frameworks": frameworks_to_check,
        }

        # Generate sections
        sections = [
            generate_executive_summary(stats),
            generate_risk_assessment(all_violations),
            generate_compliance_matrix(all_results),
            generate_metrics_summary(entries),
            generate_recommendations(all_violations),
            generate_audit_summary(
                entries,
                f"{period_start.strftime('%Y-%m-%d')} to {period_end.strftime('%Y-%m-%d')}",
            ),
        ]

        # Select template based on framework
        if framework == "eu_ai_act":
            template = EU_AI_ACT_TEMPLATE
        elif framework == "soc2":
            template = SOC2_TEMPLATE
        elif framework == "hipaa":
            template = HIPAA_TEMPLATE
        else:
            template = EXECUTIVE_SUMMARY_TEMPLATE

        title = template.title if framework else f"Compliance Report - {profile.name}"

        return ComplianceReport(
            id=str(uuid.uuid4()),
            title=title,
            framework=framework,
            period_start=period_start,
            period_end=period_end,
            generated_at=datetime.utcnow(),
            profile=profile,
            summary=stats,
            sections=sections,
            total_entries=total_entries,
            violations_count=violations_count,
            compliance_score=compliance_score,
            status=status,
        )

    async def generate_executive_summary(
        self,
        period_start: datetime,
        period_end: datetime,
        profile: ComplianceProfile,
    ) -> ComplianceReport:
        """
        Generate a high-level executive summary report.

        Creates a condensed report suitable for executive audiences,
        focusing on key metrics and critical findings without technical details.

        Args:
            period_start: Start of the analysis period.
            period_end: End of the analysis period.
            profile: ComplianceProfile defining evaluation parameters.

        Returns:
            ComplianceReport with executive-focused sections.

        Example:
            >>> report = await generator.generate_executive_summary(
            ...     period_start=datetime(2026, 1, 1),
            ...     period_end=datetime(2026, 3, 31),
            ...     profile=profile,
            ... )
            >>> print(f"Status: {report.status}")
            >>> print(f"Score: {report.compliance_score:.2%}")
        """
        # Retrieve audit entries
        entries = await self.storage.list_entries(period_start, period_end)
        total_entries = len(entries)

        # Run compliance checks for all frameworks in profile
        frameworks_to_check = profile.enabled_frameworks or list(self.frameworks.keys())
        all_violations: List[ComplianceViolation] = []
        all_results: List[ComplianceCheckResult] = []

        for fw_name in frameworks_to_check:
            if fw_name in self.frameworks:
                fw_impl = self.frameworks[fw_name]
                for entry in entries:
                    audit_entry = self._convert_to_audit_entry(entry)
                    result = await fw_impl.check(audit_entry, profile)
                    all_results.append(result)
                    all_violations.extend(result.violations)

        # Calculate metrics
        total_checks = sum(r.rules_checked for r in all_results)
        violations_count = len(all_violations)
        critical_violations = sum(
            1 for v in all_violations
            if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "critical"
        )
        high_violations = sum(
            1 for v in all_violations
            if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "high"
        )

        compliance_score = self._calculate_compliance_score(all_violations, total_checks)
        status = self._determine_status(compliance_score, critical_violations)

        # Prepare statistics
        stats = {
            "total_entries": total_entries,
            "violations_count": violations_count,
            "compliance_rate": compliance_score * 100,
            "critical_violations": critical_violations,
            "high_violations": high_violations,
            "period_start": period_start.strftime("%Y-%m-%d"),
            "period_end": period_end.strftime("%Y-%m-%d"),
            "frameworks": frameworks_to_check,
        }

        # Generate executive-focused sections only
        sections = [
            generate_executive_summary(stats),
            generate_risk_assessment(all_violations),
            generate_recommendations(all_violations),
        ]

        return ComplianceReport(
            id=str(uuid.uuid4()),
            title=f"Executive Summary - {profile.name}",
            framework=None,
            period_start=period_start,
            period_end=period_end,
            generated_at=datetime.utcnow(),
            profile=profile,
            summary=stats,
            sections=sections,
            total_entries=total_entries,
            violations_count=violations_count,
            compliance_score=compliance_score,
            status=status,
        )

    def _calculate_compliance_score(
        self,
        violations: List[ComplianceViolation],
        total_checks: int,
    ) -> float:
        """
        Calculate overall compliance score from violations.

        Uses a weighted scoring system where higher-severity violations
        have a greater impact on the score.

        Args:
            violations: List of compliance violations.
            total_checks: Total number of compliance checks performed.

        Returns:
            Compliance score from 0.0 (worst) to 1.0 (best).

        Example:
            >>> score = generator._calculate_compliance_score(violations, 100)
            >>> print(f"{score:.2%}")
            95.00%
        """
        if total_checks == 0:
            return 1.0  # No checks = assume compliant

        if not violations:
            return 1.0  # No violations = fully compliant

        # Weighted penalty per violation by severity
        severity_weights = {
            "critical": 10.0,
            "high": 5.0,
            "medium": 2.0,
            "low": 1.0,
            "info": 0.5,
        }

        total_penalty = 0.0
        for v in violations:
            severity = (
                v.severity.value if hasattr(v.severity, "value") else str(v.severity)
            ).lower()
            weight = severity_weights.get(severity, 1.0)
            total_penalty += weight

        # Calculate score (penalty capped at total checks)
        max_penalty = total_checks * 10.0  # Max if all were critical
        penalty_ratio = min(total_penalty / max_penalty, 1.0)
        score = 1.0 - penalty_ratio

        return max(0.0, min(1.0, score))

    def _determine_status(
        self,
        score: float,
        critical_violations: int,
    ) -> Literal["compliant", "non_compliant", "needs_review"]:
        """
        Determine overall compliance status from score and violations.

        Args:
            score: Compliance score (0.0 to 1.0).
            critical_violations: Number of critical-severity violations.

        Returns:
            Status string: "compliant", "non_compliant", or "needs_review".

        Example:
            >>> status = generator._determine_status(0.95, 0)
            >>> print(status)
            'compliant'
        """
        if critical_violations > 0:
            return "non_compliant"
        if score >= 0.95:
            return "compliant"
        if score >= 0.80:
            return "needs_review"
        return "non_compliant"

    def _convert_to_audit_entry(self, entry: Any) -> AuditEntry:
        """
        Convert a storage entry to the AuditEntry type expected by frameworks.

        Handles both dictionary entries and dataclass/object entries.

        Args:
            entry: Entry from storage (dict or object).

        Returns:
            AuditEntry instance.
        """
        if isinstance(entry, AuditEntry):
            return entry

        if isinstance(entry, dict):
            return AuditEntry(
                entry_id=entry.get("id", str(uuid.uuid4())),
                timestamp=datetime.fromisoformat(entry["timestamp"])
                if isinstance(entry.get("timestamp"), str)
                else entry.get("timestamp", datetime.utcnow()),
                event_type=entry.get("event_type", "unknown"),
                actor=entry.get("actor", entry.get("provider", "unknown")),
                action=entry.get("action", "AI interaction"),
                resource=entry.get("resource", ""),
                metadata=entry.get("metadata", {}),
                risk_level=RiskLevel.LOW,
                system_id=entry.get("system_id", ""),
                data_classification=entry.get("data_classification", "unclassified"),
                user_notified=entry.get("user_notified", False),
                human_oversight=entry.get("human_oversight", False),
                error_handled=entry.get("error_handled", True),
                documentation_ref=entry.get("documentation_ref"),
            )

        # Handle dataclass or object
        return AuditEntry(
            entry_id=getattr(entry, "id", str(uuid.uuid4())),
            timestamp=getattr(entry, "timestamp", datetime.utcnow())
            if isinstance(getattr(entry, "timestamp", None), datetime)
            else datetime.fromisoformat(getattr(entry, "timestamp", datetime.utcnow().isoformat())),
            event_type=getattr(entry, "event_type", "unknown"),
            actor=getattr(entry, "actor", getattr(entry, "provider", "unknown")) or "unknown",
            action=getattr(entry, "action", "AI interaction"),
            resource=getattr(entry, "resource", ""),
            metadata=getattr(entry, "metadata", {}),
            risk_level=RiskLevel.LOW,
            system_id=getattr(entry, "system_id", ""),
            data_classification=getattr(entry, "data_classification", "unclassified"),
            user_notified=getattr(entry, "user_notified", False),
            human_oversight=getattr(entry, "human_oversight", False),
            error_handled=getattr(entry, "error_handled", True),
            documentation_ref=getattr(entry, "documentation_ref", None),
        )

    def export_markdown(self, report: ComplianceReport) -> str:
        """
        Export report to Markdown format.

        Creates a well-formatted Markdown document with proper headings,
        tables, and structure suitable for documentation or rendering.

        Args:
            report: ComplianceReport to export.

        Returns:
            Markdown formatted string.

        Example:
            >>> md = generator.export_markdown(report)
            >>> with open("report.md", "w") as f:
            ...     f.write(md)
        """
        lines = [
            f"# {report.title}",
            "",
            f"**Report ID:** {report.id}",
            f"**Generated:** {report.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
            f"**Period:** {report.period_start.strftime('%Y-%m-%d')} to {report.period_end.strftime('%Y-%m-%d')}",
            f"**Framework:** {report.framework or 'Multiple'}",
            f"**Profile:** {report.profile.name}",
            "",
            "---",
            "",
            f"**Compliance Score:** {report.compliance_score:.2%}",
            f"**Status:** {report.status.upper().replace('_', ' ')}",
            f"**Total Entries:** {report.total_entries:,}",
            f"**Violations:** {report.violations_count:,}",
            "",
            "---",
            "",
        ]

        # Add each section
        for section in report.sections:
            lines.append(section.to_markdown(level=2))
            lines.append("")

        # Footer
        lines.extend([
            "---",
            "",
            "*This report was generated by rotalabs-comply.*",
        ])

        return "\n".join(lines)

    def export_json(self, report: ComplianceReport) -> str:
        """
        Export report to JSON format.

        Creates a JSON document with all report data, suitable for
        programmatic processing or API responses.

        Args:
            report: ComplianceReport to export.

        Returns:
            JSON formatted string (pretty-printed).

        Example:
            >>> json_str = generator.export_json(report)
            >>> data = json.loads(json_str)
            >>> print(data["compliance_score"])
        """
        return json.dumps(
            report.to_dict(),
            indent=2,
            default=self._json_serializer,
        )

    def export_html(self, report: ComplianceReport) -> str:
        """
        Export report to HTML format.

        Creates a standalone HTML document with embedded styles,
        suitable for viewing in a browser or embedding in web applications.

        Args:
            report: ComplianceReport to export.

        Returns:
            HTML formatted string.

        Example:
            >>> html = generator.export_html(report)
            >>> with open("report.html", "w") as f:
            ...     f.write(html)
        """
        # Determine status color
        status_colors = {
            "compliant": "#28a745",
            "needs_review": "#ffc107",
            "non_compliant": "#dc3545",
        }
        status_color = status_colors.get(report.status, "#6c757d")

        # Convert sections to HTML
        sections_html = []
        for section in report.sections:
            section_html = self._section_to_html(section)
            sections_html.append(section_html)

        html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{html.escape(report.title)}</title>
    <style>
        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.6;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            color: #333;
        }}
        h1 {{
            color: #2c3e50;
            border-bottom: 3px solid #3498db;
            padding-bottom: 10px;
        }}
        h2 {{
            color: #34495e;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 5px;
            margin-top: 30px;
        }}
        h3 {{
            color: #7f8c8d;
        }}
        .metadata {{
            background: #f8f9fa;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }}
        .metadata p {{
            margin: 5px 0;
        }}
        .status-badge {{
            display: inline-block;
            padding: 5px 15px;
            border-radius: 20px;
            color: white;
            font-weight: bold;
            background-color: {status_color};
        }}
        .score {{
            font-size: 2em;
            font-weight: bold;
            color: {status_color};
        }}
        table {{
            border-collapse: collapse;
            width: 100%;
            margin: 15px 0;
        }}
        th, td {{
            border: 1px solid #ddd;
            padding: 10px;
            text-align: left;
        }}
        th {{
            background-color: #f2f2f2;
        }}
        tr:nth-child(even) {{
            background-color: #f9f9f9;
        }}
        .section {{
            margin-bottom: 30px;
            padding: 20px;
            background: white;
            border: 1px solid #e1e4e8;
            border-radius: 5px;
        }}
        .footer {{
            margin-top: 40px;
            padding-top: 20px;
            border-top: 1px solid #e1e4e8;
            text-align: center;
            color: #6c757d;
            font-size: 0.9em;
        }}
    </style>
</head>
<body>
    <h1>{html.escape(report.title)}</h1>

    <div class="metadata">
        <p><strong>Report ID:</strong> {html.escape(report.id)}</p>
        <p><strong>Generated:</strong> {report.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}</p>
        <p><strong>Period:</strong> {report.period_start.strftime('%Y-%m-%d')} to {report.period_end.strftime('%Y-%m-%d')}</p>
        <p><strong>Framework:</strong> {html.escape(report.framework or 'Multiple')}</p>
        <p><strong>Profile:</strong> {html.escape(report.profile.name)}</p>
    </div>

    <div class="metadata" style="text-align: center;">
        <p><span class="score">{report.compliance_score:.1%}</span></p>
        <p><span class="status-badge">{report.status.upper().replace('_', ' ')}</span></p>
        <p style="margin-top: 15px;">
            <strong>Entries Analyzed:</strong> {report.total_entries:,} |
            <strong>Violations:</strong> {report.violations_count:,}
        </p>
    </div>

    {''.join(sections_html)}

    <div class="footer">
        <p>This report was generated by rotalabs-comply</p>
    </div>
</body>
</html>"""

        return html_content

    def _section_to_html(self, section: ReportSection, level: int = 2) -> str:
        """
        Convert a report section to HTML.

        Args:
            section: ReportSection to convert.
            level: Heading level (default 2).

        Returns:
            HTML string for the section.
        """
        # Convert markdown-style content to basic HTML
        content = html.escape(section.content)

        # Convert markdown tables to HTML tables
        lines = content.split("\n")
        in_table = False
        html_lines = []

        for line in lines:
            if line.strip().startswith("|") and "|" in line[1:]:
                if not in_table:
                    html_lines.append("<table>")
                    in_table = True
                    # Check if this is a header separator
                    if "---" in line:
                        continue
                cells = [c.strip() for c in line.split("|")[1:-1]]
                if html_lines[-1] == "<table>":
                    # First row is header
                    html_lines.append("<tr>" + "".join(f"<th>{c}</th>" for c in cells) + "</tr>")
                else:
                    html_lines.append("<tr>" + "".join(f"<td>{c}</td>" for c in cells) + "</tr>")
            else:
                if in_table:
                    html_lines.append("</table>")
                    in_table = False

                # Convert markdown formatting
                line = line.replace("**", "<strong>", 1).replace("**", "</strong>", 1)
                while "**" in line:
                    line = line.replace("**", "<strong>", 1).replace("**", "</strong>", 1)

                # Convert headers
                if line.startswith("### "):
                    line = f"<h4>{line[4:]}</h4>"
                elif line.startswith("#### "):
                    line = f"<h5>{line[5:]}</h5>"
                elif line.startswith("- "):
                    line = f"<li>{line[2:]}</li>"
                elif line.strip():
                    line = f"<p>{line}</p>"

                html_lines.append(line)

        if in_table:
            html_lines.append("</table>")

        content_html = "\n".join(html_lines)

        # Build subsections
        subsections_html = ""
        for subsection in section.subsections:
            subsections_html += self._section_to_html(subsection, level + 1)

        return f"""
    <div class="section">
        <h{level}>{html.escape(section.title)}</h{level}>
        {content_html}
        {subsections_html}
    </div>
"""

    def _json_serializer(self, obj: Any) -> Any:
        """
        Custom JSON serializer for non-standard types.

        Args:
            obj: Object to serialize.

        Returns:
            JSON-serializable representation.
        """
        if isinstance(obj, datetime):
            return obj.isoformat()
        if hasattr(obj, "value"):  # Enum
            return obj.value
        if hasattr(obj, "to_dict"):
            return obj.to_dict()
        if hasattr(obj, "__dict__"):
            return obj.__dict__
        return str(obj)

__init__

__init__(
    audit_logger: StorageProtocol,
    frameworks: Optional[
        Dict[FrameworkName, ComplianceFramework]
    ] = None,
) -> None

Initialize the report generator.

Parameters:

Name Type Description Default
audit_logger StorageProtocol

Storage backend implementing list_entries method.

required
frameworks Optional[Dict[FrameworkName, ComplianceFramework]]

Optional dict mapping framework names to implementations. If not provided, compliance checks will be skipped.

None
Example

generator = ReportGenerator(storage) generator = ReportGenerator(storage, {"soc2": SOC2Framework()})

Source code in src/rotalabs_comply/reports/generator.py
def __init__(
    self,
    audit_logger: StorageProtocol,
    frameworks: Optional[Dict[FrameworkName, ComplianceFramework]] = None,
) -> None:
    """
    Initialize the report generator.

    Args:
        audit_logger: Storage backend implementing list_entries method.
        frameworks: Optional dict mapping framework names to implementations.
            If not provided, compliance checks will be skipped.

    Example:
        >>> generator = ReportGenerator(storage)
        >>> generator = ReportGenerator(storage, {"soc2": SOC2Framework()})
    """
    self.storage = audit_logger
    self.frameworks = frameworks or {}

generate async

generate(
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
    framework: Optional[FrameworkName] = None,
    format: Literal[
        "markdown", "json", "html"
    ] = "markdown",
) -> ComplianceReport

Generate a comprehensive compliance report.

Retrieves audit entries for the specified period, runs compliance checks, and generates a full report with all standard sections.

Parameters:

Name Type Description Default
period_start datetime

Start of the analysis period (inclusive).

required
period_end datetime

End of the analysis period (inclusive).

required
profile ComplianceProfile

ComplianceProfile defining evaluation parameters.

required
framework Optional[FrameworkName]

Specific framework to report on (None = all in profile).

None
format Literal['markdown', 'json', 'html']

Output format hint (used for template selection).

'markdown'

Returns:

Type Description
ComplianceReport

ComplianceReport with all sections populated.

Example

report = await generator.generate( ... period_start=datetime(2026, 1, 1), ... period_end=datetime(2026, 1, 31), ... profile=profile, ... format="markdown", ... ) print(f"Generated: {report.title}") print(f"Entries: {report.total_entries}") print(f"Score: {report.compliance_score:.2%}")

Source code in src/rotalabs_comply/reports/generator.py
async def generate(
    self,
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
    framework: Optional[FrameworkName] = None,
    format: Literal["markdown", "json", "html"] = "markdown",
) -> ComplianceReport:
    """
    Generate a comprehensive compliance report.

    Retrieves audit entries for the specified period, runs compliance
    checks, and generates a full report with all standard sections.

    Args:
        period_start: Start of the analysis period (inclusive).
        period_end: End of the analysis period (inclusive).
        profile: ComplianceProfile defining evaluation parameters.
        framework: Specific framework to report on (None = all in profile).
        format: Output format hint (used for template selection).

    Returns:
        ComplianceReport with all sections populated.

    Example:
        >>> report = await generator.generate(
        ...     period_start=datetime(2026, 1, 1),
        ...     period_end=datetime(2026, 1, 31),
        ...     profile=profile,
        ...     format="markdown",
        ... )
        >>> print(f"Generated: {report.title}")
        >>> print(f"Entries: {report.total_entries}")
        >>> print(f"Score: {report.compliance_score:.2%}")
    """
    # Retrieve audit entries
    entries = await self.storage.list_entries(period_start, period_end)
    total_entries = len(entries)

    # Determine which frameworks to evaluate
    frameworks_to_check = []
    if framework:
        frameworks_to_check = [framework]
    elif profile.enabled_frameworks:
        frameworks_to_check = profile.enabled_frameworks
    else:
        frameworks_to_check = list(self.frameworks.keys())

    # Run compliance checks
    all_results: List[ComplianceCheckResult] = []
    all_violations: List[ComplianceViolation] = []

    for fw_name in frameworks_to_check:
        if fw_name in self.frameworks:
            fw_impl = self.frameworks[fw_name]
            for entry in entries:
                # Convert storage entry to AuditEntry if needed
                audit_entry = self._convert_to_audit_entry(entry)
                result = await fw_impl.check(audit_entry, profile)
                all_results.append(result)
                all_violations.extend(result.violations)

    # Calculate compliance metrics
    total_checks = sum(r.rules_checked for r in all_results)
    violations_count = len(all_violations)
    critical_violations = sum(
        1 for v in all_violations
        if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "critical"
    )
    high_violations = sum(
        1 for v in all_violations
        if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "high"
    )

    compliance_score = self._calculate_compliance_score(all_violations, total_checks)
    status = self._determine_status(compliance_score, critical_violations)

    # Prepare statistics for section generators
    stats = {
        "total_entries": total_entries,
        "violations_count": violations_count,
        "compliance_rate": compliance_score * 100,
        "critical_violations": critical_violations,
        "high_violations": high_violations,
        "period_start": period_start.strftime("%Y-%m-%d"),
        "period_end": period_end.strftime("%Y-%m-%d"),
        "frameworks": frameworks_to_check,
    }

    # Generate sections
    sections = [
        generate_executive_summary(stats),
        generate_risk_assessment(all_violations),
        generate_compliance_matrix(all_results),
        generate_metrics_summary(entries),
        generate_recommendations(all_violations),
        generate_audit_summary(
            entries,
            f"{period_start.strftime('%Y-%m-%d')} to {period_end.strftime('%Y-%m-%d')}",
        ),
    ]

    # Select template based on framework
    if framework == "eu_ai_act":
        template = EU_AI_ACT_TEMPLATE
    elif framework == "soc2":
        template = SOC2_TEMPLATE
    elif framework == "hipaa":
        template = HIPAA_TEMPLATE
    else:
        template = EXECUTIVE_SUMMARY_TEMPLATE

    title = template.title if framework else f"Compliance Report - {profile.name}"

    return ComplianceReport(
        id=str(uuid.uuid4()),
        title=title,
        framework=framework,
        period_start=period_start,
        period_end=period_end,
        generated_at=datetime.utcnow(),
        profile=profile,
        summary=stats,
        sections=sections,
        total_entries=total_entries,
        violations_count=violations_count,
        compliance_score=compliance_score,
        status=status,
    )

generate_executive_summary async

generate_executive_summary(
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
) -> ComplianceReport

Generate a high-level executive summary report.

Creates a condensed report suitable for executive audiences, focusing on key metrics and critical findings without technical details.

Parameters:

Name Type Description Default
period_start datetime

Start of the analysis period.

required
period_end datetime

End of the analysis period.

required
profile ComplianceProfile

ComplianceProfile defining evaluation parameters.

required

Returns:

Type Description
ComplianceReport

ComplianceReport with executive-focused sections.

Example

report = await generator.generate_executive_summary( ... period_start=datetime(2026, 1, 1), ... period_end=datetime(2026, 3, 31), ... profile=profile, ... ) print(f"Status: {report.status}") print(f"Score: {report.compliance_score:.2%}")

Source code in src/rotalabs_comply/reports/generator.py
async def generate_executive_summary(
    self,
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
) -> ComplianceReport:
    """
    Generate a high-level executive summary report.

    Creates a condensed report suitable for executive audiences,
    focusing on key metrics and critical findings without technical details.

    Args:
        period_start: Start of the analysis period.
        period_end: End of the analysis period.
        profile: ComplianceProfile defining evaluation parameters.

    Returns:
        ComplianceReport with executive-focused sections.

    Example:
        >>> report = await generator.generate_executive_summary(
        ...     period_start=datetime(2026, 1, 1),
        ...     period_end=datetime(2026, 3, 31),
        ...     profile=profile,
        ... )
        >>> print(f"Status: {report.status}")
        >>> print(f"Score: {report.compliance_score:.2%}")
    """
    # Retrieve audit entries
    entries = await self.storage.list_entries(period_start, period_end)
    total_entries = len(entries)

    # Run compliance checks for all frameworks in profile
    frameworks_to_check = profile.enabled_frameworks or list(self.frameworks.keys())
    all_violations: List[ComplianceViolation] = []
    all_results: List[ComplianceCheckResult] = []

    for fw_name in frameworks_to_check:
        if fw_name in self.frameworks:
            fw_impl = self.frameworks[fw_name]
            for entry in entries:
                audit_entry = self._convert_to_audit_entry(entry)
                result = await fw_impl.check(audit_entry, profile)
                all_results.append(result)
                all_violations.extend(result.violations)

    # Calculate metrics
    total_checks = sum(r.rules_checked for r in all_results)
    violations_count = len(all_violations)
    critical_violations = sum(
        1 for v in all_violations
        if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "critical"
    )
    high_violations = sum(
        1 for v in all_violations
        if (v.severity.value if hasattr(v.severity, "value") else str(v.severity)).lower() == "high"
    )

    compliance_score = self._calculate_compliance_score(all_violations, total_checks)
    status = self._determine_status(compliance_score, critical_violations)

    # Prepare statistics
    stats = {
        "total_entries": total_entries,
        "violations_count": violations_count,
        "compliance_rate": compliance_score * 100,
        "critical_violations": critical_violations,
        "high_violations": high_violations,
        "period_start": period_start.strftime("%Y-%m-%d"),
        "period_end": period_end.strftime("%Y-%m-%d"),
        "frameworks": frameworks_to_check,
    }

    # Generate executive-focused sections only
    sections = [
        generate_executive_summary(stats),
        generate_risk_assessment(all_violations),
        generate_recommendations(all_violations),
    ]

    return ComplianceReport(
        id=str(uuid.uuid4()),
        title=f"Executive Summary - {profile.name}",
        framework=None,
        period_start=period_start,
        period_end=period_end,
        generated_at=datetime.utcnow(),
        profile=profile,
        summary=stats,
        sections=sections,
        total_entries=total_entries,
        violations_count=violations_count,
        compliance_score=compliance_score,
        status=status,
    )

export_markdown

export_markdown(report: ComplianceReport) -> str

Export report to Markdown format.

Creates a well-formatted Markdown document with proper headings, tables, and structure suitable for documentation or rendering.

Parameters:

Name Type Description Default
report ComplianceReport

ComplianceReport to export.

required

Returns:

Type Description
str

Markdown formatted string.

Example

md = generator.export_markdown(report) with open("report.md", "w") as f: ... f.write(md)

Source code in src/rotalabs_comply/reports/generator.py
def export_markdown(self, report: ComplianceReport) -> str:
    """
    Export report to Markdown format.

    Creates a well-formatted Markdown document with proper headings,
    tables, and structure suitable for documentation or rendering.

    Args:
        report: ComplianceReport to export.

    Returns:
        Markdown formatted string.

    Example:
        >>> md = generator.export_markdown(report)
        >>> with open("report.md", "w") as f:
        ...     f.write(md)
    """
    lines = [
        f"# {report.title}",
        "",
        f"**Report ID:** {report.id}",
        f"**Generated:** {report.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}",
        f"**Period:** {report.period_start.strftime('%Y-%m-%d')} to {report.period_end.strftime('%Y-%m-%d')}",
        f"**Framework:** {report.framework or 'Multiple'}",
        f"**Profile:** {report.profile.name}",
        "",
        "---",
        "",
        f"**Compliance Score:** {report.compliance_score:.2%}",
        f"**Status:** {report.status.upper().replace('_', ' ')}",
        f"**Total Entries:** {report.total_entries:,}",
        f"**Violations:** {report.violations_count:,}",
        "",
        "---",
        "",
    ]

    # Add each section
    for section in report.sections:
        lines.append(section.to_markdown(level=2))
        lines.append("")

    # Footer
    lines.extend([
        "---",
        "",
        "*This report was generated by rotalabs-comply.*",
    ])

    return "\n".join(lines)

export_json

export_json(report: ComplianceReport) -> str

Export report to JSON format.

Creates a JSON document with all report data, suitable for programmatic processing or API responses.

Parameters:

Name Type Description Default
report ComplianceReport

ComplianceReport to export.

required

Returns:

Type Description
str

JSON formatted string (pretty-printed).

Example

json_str = generator.export_json(report) data = json.loads(json_str) print(data["compliance_score"])

Source code in src/rotalabs_comply/reports/generator.py
def export_json(self, report: ComplianceReport) -> str:
    """
    Export report to JSON format.

    Creates a JSON document with all report data, suitable for
    programmatic processing or API responses.

    Args:
        report: ComplianceReport to export.

    Returns:
        JSON formatted string (pretty-printed).

    Example:
        >>> json_str = generator.export_json(report)
        >>> data = json.loads(json_str)
        >>> print(data["compliance_score"])
    """
    return json.dumps(
        report.to_dict(),
        indent=2,
        default=self._json_serializer,
    )

export_html

export_html(report: ComplianceReport) -> str

Export report to HTML format.

Creates a standalone HTML document with embedded styles, suitable for viewing in a browser or embedding in web applications.

Parameters:

Name Type Description Default
report ComplianceReport

ComplianceReport to export.

required

Returns:

Type Description
str

HTML formatted string.

Example

html = generator.export_html(report) with open("report.html", "w") as f: ... f.write(html)

Source code in src/rotalabs_comply/reports/generator.py
    def export_html(self, report: ComplianceReport) -> str:
        """
        Export report to HTML format.

        Creates a standalone HTML document with embedded styles,
        suitable for viewing in a browser or embedding in web applications.

        Args:
            report: ComplianceReport to export.

        Returns:
            HTML formatted string.

        Example:
            >>> html = generator.export_html(report)
            >>> with open("report.html", "w") as f:
            ...     f.write(html)
        """
        # Determine status color
        status_colors = {
            "compliant": "#28a745",
            "needs_review": "#ffc107",
            "non_compliant": "#dc3545",
        }
        status_color = status_colors.get(report.status, "#6c757d")

        # Convert sections to HTML
        sections_html = []
        for section in report.sections:
            section_html = self._section_to_html(section)
            sections_html.append(section_html)

        html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{html.escape(report.title)}</title>
    <style>
        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.6;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            color: #333;
        }}
        h1 {{
            color: #2c3e50;
            border-bottom: 3px solid #3498db;
            padding-bottom: 10px;
        }}
        h2 {{
            color: #34495e;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 5px;
            margin-top: 30px;
        }}
        h3 {{
            color: #7f8c8d;
        }}
        .metadata {{
            background: #f8f9fa;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }}
        .metadata p {{
            margin: 5px 0;
        }}
        .status-badge {{
            display: inline-block;
            padding: 5px 15px;
            border-radius: 20px;
            color: white;
            font-weight: bold;
            background-color: {status_color};
        }}
        .score {{
            font-size: 2em;
            font-weight: bold;
            color: {status_color};
        }}
        table {{
            border-collapse: collapse;
            width: 100%;
            margin: 15px 0;
        }}
        th, td {{
            border: 1px solid #ddd;
            padding: 10px;
            text-align: left;
        }}
        th {{
            background-color: #f2f2f2;
        }}
        tr:nth-child(even) {{
            background-color: #f9f9f9;
        }}
        .section {{
            margin-bottom: 30px;
            padding: 20px;
            background: white;
            border: 1px solid #e1e4e8;
            border-radius: 5px;
        }}
        .footer {{
            margin-top: 40px;
            padding-top: 20px;
            border-top: 1px solid #e1e4e8;
            text-align: center;
            color: #6c757d;
            font-size: 0.9em;
        }}
    </style>
</head>
<body>
    <h1>{html.escape(report.title)}</h1>

    <div class="metadata">
        <p><strong>Report ID:</strong> {html.escape(report.id)}</p>
        <p><strong>Generated:</strong> {report.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}</p>
        <p><strong>Period:</strong> {report.period_start.strftime('%Y-%m-%d')} to {report.period_end.strftime('%Y-%m-%d')}</p>
        <p><strong>Framework:</strong> {html.escape(report.framework or 'Multiple')}</p>
        <p><strong>Profile:</strong> {html.escape(report.profile.name)}</p>
    </div>

    <div class="metadata" style="text-align: center;">
        <p><span class="score">{report.compliance_score:.1%}</span></p>
        <p><span class="status-badge">{report.status.upper().replace('_', ' ')}</span></p>
        <p style="margin-top: 15px;">
            <strong>Entries Analyzed:</strong> {report.total_entries:,} |
            <strong>Violations:</strong> {report.violations_count:,}
        </p>
    </div>

    {''.join(sections_html)}

    <div class="footer">
        <p>This report was generated by rotalabs-comply</p>
    </div>
</body>
</html>"""

        return html_content

Generator for compliance reports from audit data.

Constructor

ReportGenerator(
    audit_logger: StorageProtocol,
    frameworks: Optional[Dict[str, ComplianceFramework]] = None,
)

Parameters:

Parameter Type Description
audit_logger StorageProtocol Storage backend with list_entries method
frameworks Optional[Dict] Framework name -> implementation mapping

Example:

from rotalabs_comply import ReportGenerator
from rotalabs_comply.audit import MemoryStorage
from rotalabs_comply.frameworks.eu_ai_act import EUAIActFramework
from rotalabs_comply.frameworks.soc2 import SOC2Framework

storage = MemoryStorage()
generator = ReportGenerator(
    audit_logger=storage,
    frameworks={
        "eu_ai_act": EUAIActFramework(),
        "soc2": SOC2Framework(),
    },
)

Methods

generate

async def generate(
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
    framework: Optional[str] = None,
    format: Literal["markdown", "json", "html"] = "markdown",
) -> ComplianceReport

Generate a comprehensive compliance report.

Parameters:

Parameter Type Default Description
period_start datetime Required Analysis start (inclusive)
period_end datetime Required Analysis end (inclusive)
profile ComplianceProfile Required Evaluation configuration
framework Optional[str] None Specific framework (None=all)
format str "markdown" Output format hint

Returns: ComplianceReport

Example:

from datetime import datetime, timedelta

end = datetime.utcnow()
start = end - timedelta(days=30)

report = await generator.generate(
    period_start=start,
    period_end=end,
    profile=profile,
    framework="eu_ai_act",
)

generate_executive_summary

async def generate_executive_summary(
    period_start: datetime,
    period_end: datetime,
    profile: ComplianceProfile,
) -> ComplianceReport

Generate a condensed executive summary report.

Example:

report = await generator.generate_executive_summary(
    period_start=start,
    period_end=end,
    profile=profile,
)

export_markdown

def export_markdown(report: ComplianceReport) -> str

Export report to Markdown format.

Example:

markdown = generator.export_markdown(report)
with open("report.md", "w") as f:
    f.write(markdown)

export_json

def export_json(report: ComplianceReport) -> str

Export report to JSON format (pretty-printed).

Example:

json_str = generator.export_json(report)

export_html

def export_html(report: ComplianceReport) -> str

Export report to standalone HTML format.

Example:

html = generator.export_html(report)
with open("report.html", "w") as f:
    f.write(html)

ComplianceReport

ComplianceReport dataclass

A complete compliance report with all sections and metadata.

ComplianceReport contains the full results of a compliance evaluation, including all sections, summary statistics, and compliance scoring.

Attributes:

Name Type Description
id str

Unique identifier for this report.

title str

Report title.

framework Optional[FrameworkName]

Framework evaluated (None for multi-framework reports).

period_start datetime

Start of the analysis period.

period_end datetime

End of the analysis period.

generated_at datetime

When the report was generated.

profile ComplianceProfile

ComplianceProfile used for evaluation.

summary Dict[str, Any]

Summary statistics dictionary.

sections List[ReportSection]

List of report sections.

total_entries int

Total audit entries analyzed.

violations_count int

Number of violations found.

compliance_score float

Overall compliance score (0.0 to 1.0).

status Literal['compliant', 'non_compliant', 'needs_review']

Overall compliance status.

Example

report = ComplianceReport( ... id="rpt-001", ... title="Q1 2026 Compliance Report", ... framework=None, # Multi-framework ... period_start=datetime(2026, 1, 1), ... period_end=datetime(2026, 3, 31), ... generated_at=datetime.utcnow(), ... profile=profile, ... summary={"total": 10000, "violations": 5}, ... sections=[executive_summary, risk_assessment], ... total_entries=10000, ... violations_count=5, ... compliance_score=0.9995, ... status="compliant", ... ) print(f"Compliance: {report.compliance_score:.2%}") Compliance: 99.95%

Source code in src/rotalabs_comply/reports/generator.py
@dataclass
class ComplianceReport:
    """
    A complete compliance report with all sections and metadata.

    ComplianceReport contains the full results of a compliance evaluation,
    including all sections, summary statistics, and compliance scoring.

    Attributes:
        id: Unique identifier for this report.
        title: Report title.
        framework: Framework evaluated (None for multi-framework reports).
        period_start: Start of the analysis period.
        period_end: End of the analysis period.
        generated_at: When the report was generated.
        profile: ComplianceProfile used for evaluation.
        summary: Summary statistics dictionary.
        sections: List of report sections.
        total_entries: Total audit entries analyzed.
        violations_count: Number of violations found.
        compliance_score: Overall compliance score (0.0 to 1.0).
        status: Overall compliance status.

    Example:
        >>> report = ComplianceReport(
        ...     id="rpt-001",
        ...     title="Q1 2026 Compliance Report",
        ...     framework=None,  # Multi-framework
        ...     period_start=datetime(2026, 1, 1),
        ...     period_end=datetime(2026, 3, 31),
        ...     generated_at=datetime.utcnow(),
        ...     profile=profile,
        ...     summary={"total": 10000, "violations": 5},
        ...     sections=[executive_summary, risk_assessment],
        ...     total_entries=10000,
        ...     violations_count=5,
        ...     compliance_score=0.9995,
        ...     status="compliant",
        ... )
        >>> print(f"Compliance: {report.compliance_score:.2%}")
        Compliance: 99.95%
    """

    id: str
    title: str
    framework: Optional[FrameworkName]
    period_start: datetime
    period_end: datetime
    generated_at: datetime
    profile: ComplianceProfile
    summary: Dict[str, Any]
    sections: List[ReportSection]
    total_entries: int
    violations_count: int
    compliance_score: float
    status: Literal["compliant", "non_compliant", "needs_review"]

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert report to dictionary for serialization.

        Returns:
            Dict containing all report data.

        Example:
            >>> data = report.to_dict()
            >>> print(data["status"])
            'compliant'
        """
        return {
            "id": self.id,
            "title": self.title,
            "framework": self.framework,
            "period_start": self.period_start.isoformat(),
            "period_end": self.period_end.isoformat(),
            "generated_at": self.generated_at.isoformat(),
            "profile": {
                "profile_id": self.profile.profile_id,
                "name": self.profile.name,
                "enabled_frameworks": self.profile.enabled_frameworks,
            },
            "summary": self.summary,
            "sections": [s.to_dict() for s in self.sections],
            "total_entries": self.total_entries,
            "violations_count": self.violations_count,
            "compliance_score": self.compliance_score,
            "status": self.status,
        }

to_dict

to_dict() -> Dict[str, Any]

Convert report to dictionary for serialization.

Returns:

Type Description
Dict[str, Any]

Dict containing all report data.

Example

data = report.to_dict() print(data["status"]) 'compliant'

Source code in src/rotalabs_comply/reports/generator.py
def to_dict(self) -> Dict[str, Any]:
    """
    Convert report to dictionary for serialization.

    Returns:
        Dict containing all report data.

    Example:
        >>> data = report.to_dict()
        >>> print(data["status"])
        'compliant'
    """
    return {
        "id": self.id,
        "title": self.title,
        "framework": self.framework,
        "period_start": self.period_start.isoformat(),
        "period_end": self.period_end.isoformat(),
        "generated_at": self.generated_at.isoformat(),
        "profile": {
            "profile_id": self.profile.profile_id,
            "name": self.profile.name,
            "enabled_frameworks": self.profile.enabled_frameworks,
        },
        "summary": self.summary,
        "sections": [s.to_dict() for s in self.sections],
        "total_entries": self.total_entries,
        "violations_count": self.violations_count,
        "compliance_score": self.compliance_score,
        "status": self.status,
    }

A complete compliance report with all sections and metadata.

Attributes:

Attribute Type Description
id str Unique report identifier
title str Report title
framework Optional[str] Framework evaluated (None=multiple)
period_start datetime Analysis period start
period_end datetime Analysis period end
generated_at datetime When report was generated
profile ComplianceProfile Profile used for evaluation
summary Dict[str, Any] Summary statistics
sections List[ReportSection] Report sections
total_entries int Entries analyzed
violations_count int Violations found
compliance_score float Score 0.0-1.0
status str "compliant", "non_compliant", "needs_review"

Methods:

Method Returns Description
to_dict() Dict[str, Any] Convert to dictionary

Templates

ReportSection

ReportSection dataclass

A section within a compliance report.

Report sections can contain nested subsections to create hierarchical report structures. Each section has a title, content, and optional metadata for additional context.

Attributes:

Name Type Description
title str

Section heading/title.

content str

Main content of the section (text, markdown, etc.).

subsections List['ReportSection']

Nested sections within this section.

metadata Dict[str, Any]

Additional data about the section (charts, tables, etc.).

Example

section = ReportSection( ... title="Risk Assessment", ... content="This section analyzes identified compliance risks.", ... subsections=[ ... ReportSection( ... title="Critical Risks", ... content="No critical risks identified.", ... ), ... ReportSection( ... title="High Risks", ... content="2 high-risk violations require attention.", ... ), ... ], ... metadata={"risk_count": 2, "max_severity": "high"}, ... )

Access nested sections

for sub in section.subsections: ... print(f"- {sub.title}") - Critical Risks - High Risks

Source code in src/rotalabs_comply/reports/templates.py
@dataclass
class ReportSection:
    """
    A section within a compliance report.

    Report sections can contain nested subsections to create hierarchical
    report structures. Each section has a title, content, and optional
    metadata for additional context.

    Attributes:
        title: Section heading/title.
        content: Main content of the section (text, markdown, etc.).
        subsections: Nested sections within this section.
        metadata: Additional data about the section (charts, tables, etc.).

    Example:
        >>> section = ReportSection(
        ...     title="Risk Assessment",
        ...     content="This section analyzes identified compliance risks.",
        ...     subsections=[
        ...         ReportSection(
        ...             title="Critical Risks",
        ...             content="No critical risks identified.",
        ...         ),
        ...         ReportSection(
        ...             title="High Risks",
        ...             content="2 high-risk violations require attention.",
        ...         ),
        ...     ],
        ...     metadata={"risk_count": 2, "max_severity": "high"},
        ... )

        >>> # Access nested sections
        >>> for sub in section.subsections:
        ...     print(f"- {sub.title}")
        - Critical Risks
        - High Risks
    """

    title: str
    content: str
    subsections: List["ReportSection"] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert section to dictionary for serialization.

        Returns:
            Dict containing all section data including nested subsections.

        Example:
            >>> section = ReportSection(title="Test", content="Content")
            >>> data = section.to_dict()
            >>> print(data["title"])
            'Test'
        """
        return {
            "title": self.title,
            "content": self.content,
            "subsections": [s.to_dict() for s in self.subsections],
            "metadata": self.metadata,
        }

    def to_markdown(self, level: int = 2) -> str:
        """
        Render section as markdown with appropriate heading levels.

        Args:
            level: Heading level (default 2 = ##). Subsections use level + 1.

        Returns:
            Markdown formatted string of the section.

        Example:
            >>> section = ReportSection(title="Summary", content="All good.")
            >>> print(section.to_markdown())
            ## Summary
            <BLANKLINE>
            All good.
        """
        heading = "#" * level
        lines = [f"{heading} {self.title}", "", self.content]

        for subsection in self.subsections:
            lines.append("")
            lines.append(subsection.to_markdown(level + 1))

        return "\n".join(lines)

to_dict

to_dict() -> Dict[str, Any]

Convert section to dictionary for serialization.

Returns:

Type Description
Dict[str, Any]

Dict containing all section data including nested subsections.

Example

section = ReportSection(title="Test", content="Content") data = section.to_dict() print(data["title"]) 'Test'

Source code in src/rotalabs_comply/reports/templates.py
def to_dict(self) -> Dict[str, Any]:
    """
    Convert section to dictionary for serialization.

    Returns:
        Dict containing all section data including nested subsections.

    Example:
        >>> section = ReportSection(title="Test", content="Content")
        >>> data = section.to_dict()
        >>> print(data["title"])
        'Test'
    """
    return {
        "title": self.title,
        "content": self.content,
        "subsections": [s.to_dict() for s in self.subsections],
        "metadata": self.metadata,
    }

to_markdown

to_markdown(level: int = 2) -> str

Render section as markdown with appropriate heading levels.

Parameters:

Name Type Description Default
level int

Heading level (default 2 = ##). Subsections use level + 1.

2

Returns:

Type Description
str

Markdown formatted string of the section.

Example

section = ReportSection(title="Summary", content="All good.") print(section.to_markdown())

Summary

All good.

Source code in src/rotalabs_comply/reports/templates.py
def to_markdown(self, level: int = 2) -> str:
    """
    Render section as markdown with appropriate heading levels.

    Args:
        level: Heading level (default 2 = ##). Subsections use level + 1.

    Returns:
        Markdown formatted string of the section.

    Example:
        >>> section = ReportSection(title="Summary", content="All good.")
        >>> print(section.to_markdown())
        ## Summary
        <BLANKLINE>
        All good.
    """
    heading = "#" * level
    lines = [f"{heading} {self.title}", "", self.content]

    for subsection in self.subsections:
        lines.append("")
        lines.append(subsection.to_markdown(level + 1))

    return "\n".join(lines)

A section within a compliance report.

Attributes:

Attribute Type Description
title str Section heading
content str Main content (text/markdown)
subsections List[ReportSection] Nested sections
metadata Dict[str, Any] Additional data

Methods:

Method Returns Description
to_dict() Dict[str, Any] Convert to dictionary
to_markdown(level=2) str Render as markdown

Example:

from rotalabs_comply.reports.templates import ReportSection

section = ReportSection(
    title="Risk Assessment",
    content="Analysis of identified risks...",
    subsections=[
        ReportSection(title="Critical Risks", content="None found."),
        ReportSection(title="High Risks", content="2 issues identified."),
    ],
    metadata={"risk_count": 2},
)

print(section.to_markdown())

ReportTemplate

ReportTemplate dataclass

Template defining the structure and format of a compliance report.

Templates specify which framework the report covers, its title, which sections to include, and the output format.

Attributes:

Name Type Description
framework FrameworkType

The compliance framework this template is for.

title str

Default title for reports using this template.

sections List[str]

List of section names to include in the report.

format Literal['markdown', 'json', 'html']

Output format for the report (markdown, json, or html).

Example

template = ReportTemplate( ... framework="eu_ai_act", ... title="EU AI Act Compliance Report", ... sections=[ ... "executive_summary", ... "risk_assessment", ... "compliance_matrix", ... "recommendations", ... ], ... format="markdown", ... )

Check what sections will be included

"risk_assessment" in template.sections True

Source code in src/rotalabs_comply/reports/templates.py
@dataclass
class ReportTemplate:
    """
    Template defining the structure and format of a compliance report.

    Templates specify which framework the report covers, its title,
    which sections to include, and the output format.

    Attributes:
        framework: The compliance framework this template is for.
        title: Default title for reports using this template.
        sections: List of section names to include in the report.
        format: Output format for the report (markdown, json, or html).

    Example:
        >>> template = ReportTemplate(
        ...     framework="eu_ai_act",
        ...     title="EU AI Act Compliance Report",
        ...     sections=[
        ...         "executive_summary",
        ...         "risk_assessment",
        ...         "compliance_matrix",
        ...         "recommendations",
        ...     ],
        ...     format="markdown",
        ... )

        >>> # Check what sections will be included
        >>> "risk_assessment" in template.sections
        True
    """

    framework: FrameworkType
    title: str
    sections: List[str]
    format: Literal["markdown", "json", "html"] = "markdown"

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert template to dictionary.

        Returns:
            Dict containing template configuration.
        """
        return {
            "framework": self.framework,
            "title": self.title,
            "sections": self.sections,
            "format": self.format,
        }

to_dict

to_dict() -> Dict[str, Any]

Convert template to dictionary.

Returns:

Type Description
Dict[str, Any]

Dict containing template configuration.

Source code in src/rotalabs_comply/reports/templates.py
def to_dict(self) -> Dict[str, Any]:
    """
    Convert template to dictionary.

    Returns:
        Dict containing template configuration.
    """
    return {
        "framework": self.framework,
        "title": self.title,
        "sections": self.sections,
        "format": self.format,
    }

Template defining report structure and format.

Attributes:

Attribute Type Description
framework str Target framework
title str Default title
sections List[str] Section names to include
format str Output format

Pre-defined Templates

EU_AI_ACT_TEMPLATE

EU_AI_ACT_TEMPLATE module-attribute

EU_AI_ACT_TEMPLATE = ReportTemplate(
    framework="eu_ai_act",
    title="EU AI Act Compliance Report",
    sections=[
        "executive_summary",
        "risk_classification",
        "risk_assessment",
        "transparency_obligations",
        "human_oversight",
        "compliance_matrix",
        "data_governance",
        "technical_documentation",
        "recommendations",
        "audit_summary",
    ],
    format="markdown",
)

Template for EU AI Act compliance reports.

Includes sections required for demonstrating compliance with the European Union's Artificial Intelligence Act, focusing on risk classification, transparency, and human oversight requirements.

Example

from rotalabs_comply.reports.templates import EU_AI_ACT_TEMPLATE print(EU_AI_ACT_TEMPLATE.title) 'EU AI Act Compliance Report'

Template for EU AI Act compliance reports.

Sections: - executive_summary - risk_classification - risk_assessment - transparency_obligations - human_oversight - compliance_matrix - data_governance - technical_documentation - recommendations - audit_summary

SOC2_TEMPLATE

SOC2_TEMPLATE module-attribute

SOC2_TEMPLATE = ReportTemplate(
    framework="soc2",
    title="SOC2 Type II Compliance Report",
    sections=[
        "executive_summary",
        "system_overview",
        "risk_assessment",
        "security_controls",
        "availability_controls",
        "processing_integrity",
        "confidentiality_controls",
        "privacy_controls",
        "compliance_matrix",
        "recommendations",
        "audit_summary",
    ],
    format="markdown",
)

Template for SOC2 Type II compliance reports.

Covers the five Trust Service Criteria: Security, Availability, Processing Integrity, Confidentiality, and Privacy.

Example

from rotalabs_comply.reports.templates import SOC2_TEMPLATE "security_controls" in SOC2_TEMPLATE.sections True

Template for SOC2 Type II compliance reports.

Sections: - executive_summary - system_overview - risk_assessment - security_controls - availability_controls - processing_integrity - confidentiality_controls - privacy_controls - compliance_matrix - recommendations - audit_summary

HIPAA_TEMPLATE

HIPAA_TEMPLATE module-attribute

HIPAA_TEMPLATE = ReportTemplate(
    framework="hipaa",
    title="HIPAA Compliance Report",
    sections=[
        "executive_summary",
        "risk_assessment",
        "administrative_safeguards",
        "physical_safeguards",
        "technical_safeguards",
        "breach_notification",
        "phi_handling",
        "compliance_matrix",
        "recommendations",
        "audit_summary",
    ],
    format="markdown",
)

Template for HIPAA compliance reports.

Covers the Security Rule requirements including Administrative, Physical, and Technical Safeguards for Protected Health Information (PHI).

Example

from rotalabs_comply.reports.templates import HIPAA_TEMPLATE "phi_handling" in HIPAA_TEMPLATE.sections True

Template for HIPAA compliance reports.

Sections: - executive_summary - risk_assessment - administrative_safeguards - physical_safeguards - technical_safeguards - breach_notification - phi_handling - compliance_matrix - recommendations - audit_summary

EXECUTIVE_SUMMARY_TEMPLATE

EXECUTIVE_SUMMARY_TEMPLATE module-attribute

EXECUTIVE_SUMMARY_TEMPLATE = ReportTemplate(
    framework="any",
    title="Compliance Executive Summary",
    sections=[
        "executive_summary",
        "key_metrics",
        "risk_assessment",
        "critical_findings",
        "recommendations",
    ],
    format="markdown",
)

Template for high-level executive summary reports.

Designed for executive audiences, focusing on key metrics, critical findings, and high-priority recommendations without technical details.

Example

from rotalabs_comply.reports.templates import EXECUTIVE_SUMMARY_TEMPLATE len(EXECUTIVE_SUMMARY_TEMPLATE.sections) 5

Template for executive summary reports.

Sections: - executive_summary - key_metrics - risk_assessment - critical_findings - recommendations


Section Generators

generate_executive_summary

def generate_executive_summary(stats: Dict[str, Any]) -> ReportSection

Generate executive summary from statistics.

Expected stats keys: - total_entries: int - violations_count: int - compliance_rate: float (0-100) - critical_violations: int - high_violations: int - period_start: str - period_end: str - frameworks: List[str]

Example:

from rotalabs_comply.reports.templates import generate_executive_summary

stats = {
    "total_entries": 10000,
    "violations_count": 15,
    "compliance_rate": 99.85,
    "critical_violations": 0,
    "high_violations": 2,
    "period_start": "2026-01-01",
    "period_end": "2026-01-31",
    "frameworks": ["EU AI Act", "SOC2"],
}

section = generate_executive_summary(stats)
print(section.metadata["status"])  # "NEEDS REVIEW"

generate_risk_assessment

def generate_risk_assessment(violations: Sequence[ComplianceViolation]) -> ReportSection

Generate risk assessment from violations.

Returns section with metadata: - overall_risk: str - severity_counts: Dict[str, int] - category_counts: Dict[str, int] - violation_count: int


generate_compliance_matrix

def generate_compliance_matrix(results: Sequence[ComplianceCheckResult]) -> ReportSection

Generate compliance matrix from check results.

Returns section with metadata: - frameworks: List[str] - total_checks: int - total_passed: int - total_violations: int - compliance_rate: float


generate_recommendations

def generate_recommendations(violations: Sequence[ComplianceViolation]) -> ReportSection

Generate prioritized recommendations from violations.

Returns section with metadata: - recommendation_count: int - immediate_count: int - short_term_count: int - long_term_count: int


generate_metrics_summary

def generate_metrics_summary(entries: Sequence[Any]) -> ReportSection

Generate metrics summary from audit entries.

Returns section with metadata: - entry_count: int - safety_rate: float - avg_latency: float - p50_latency: float - p95_latency: float - p99_latency: float


generate_audit_summary

def generate_audit_summary(entries: Sequence[Any], period: str) -> ReportSection

Generate audit summary for a period.

Returns section with metadata: - period: str - entry_count: int - days_active: int - avg_daily: float - peak_daily: int