feat: add legislation explainer app
Browse filesCo-authored-by: Codex <codex@openai.com>
- app.py +933 -0
- assets/example_bills/cybersecurity-amendment-bill-2025/analysis.json +136 -0
- assets/example_bills/cybersecurity-amendment-bill-2025/chunks.json +47 -0
- assets/example_bills/cybersecurity-amendment-bill-2025/document.txt +1104 -0
- assets/example_bills/cybersecurity-amendment-bill-2025/metadata.json +10 -0
- assets/example_bills/manifest.json +100 -0
- services/__init__.py +0 -0
- services/example_bills.py +90 -0
- services/ingest.py +127 -0
- services/precomputed_assets.py +67 -0
- services/providers.py +113 -0
- services/rag_pipeline.py +1276 -0
app.py
ADDED
|
@@ -0,0 +1,933 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio entrypoint for the Legislation Explainer app."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import html
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from uuid import uuid4
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
import gradio as gr
|
| 11 |
+
|
| 12 |
+
from config import APP_DESCRIPTION, APP_TITLE, DEFAULT_PROVIDER
|
| 13 |
+
from services.example_bills import example_bill_titles, example_bill_urls_by_title
|
| 14 |
+
from services import ingest
|
| 15 |
+
from services.providers import get_default_api_key, instantiate_client, list_providers, validate_api_key
|
| 16 |
+
from services.rag_pipeline import (
|
| 17 |
+
AnalysisResult,
|
| 18 |
+
answer_query,
|
| 19 |
+
answer_query_from_full_document,
|
| 20 |
+
build_cached_document_artifacts,
|
| 21 |
+
generate_analysis_progress,
|
| 22 |
+
get_precomputed_example_artifacts,
|
| 23 |
+
prepare_document_artifacts,
|
| 24 |
+
warm_embedding_stack,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _provider_options() -> list[tuple[str, str, str]]:
|
| 29 |
+
return [(config.name, config.display_name, config.instructions) for config in list_providers()]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
PROVIDER_OPTIONS = _provider_options()
|
| 33 |
+
PROVIDER_LABELS = [display for _, display, _ in PROVIDER_OPTIONS]
|
| 34 |
+
PROVIDER_BY_LABEL = {display: name for name, display, _ in PROVIDER_OPTIONS}
|
| 35 |
+
PROVIDER_DETAILS = {name: description for name, _, description in PROVIDER_OPTIONS}
|
| 36 |
+
PROVIDER_LABEL_BY_NAME = {name: display for name, display, _ in PROVIDER_OPTIONS}
|
| 37 |
+
ANALYSIS_PLACEHOLDER = "Run an analysis to populate this section."
|
| 38 |
+
EXAMPLE_BILL_LABELS = example_bill_titles()
|
| 39 |
+
EXAMPLE_BILL_URLS = example_bill_urls_by_title()
|
| 40 |
+
APP_THEME = gr.themes.Soft(
|
| 41 |
+
primary_hue="sky",
|
| 42 |
+
secondary_hue="slate",
|
| 43 |
+
neutral_hue="stone",
|
| 44 |
+
).set(
|
| 45 |
+
body_background_fill="linear-gradient(180deg, #f7fafc 0%, #edf4f7 100%)",
|
| 46 |
+
block_background_fill="rgba(255, 255, 255, 0.88)",
|
| 47 |
+
block_border_color="#d7e3ea",
|
| 48 |
+
block_shadow="0 18px 48px rgba(15, 23, 42, 0.06)",
|
| 49 |
+
button_primary_background_fill="linear-gradient(135deg, #7dd3fc 0%, #67e8f9 100%)",
|
| 50 |
+
button_primary_background_fill_hover="linear-gradient(135deg, #38bdf8 0%, #22d3ee 100%)",
|
| 51 |
+
button_primary_border_color="#7dd3fc",
|
| 52 |
+
button_secondary_background_fill="#f8fafc",
|
| 53 |
+
button_secondary_background_fill_hover="#eef6f8",
|
| 54 |
+
)
|
| 55 |
+
APP_HEAD = """
|
| 56 |
+
<script>
|
| 57 |
+
(() => {
|
| 58 |
+
function toggleSidebar() {
|
| 59 |
+
const sidebar = document.getElementById("sidebar-panel");
|
| 60 |
+
if (!sidebar) {
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
const isHidden = sidebar.style.display === "none";
|
| 64 |
+
sidebar.style.display = isHidden ? "" : "none";
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
window.__toggleSidebar = toggleSidebar;
|
| 68 |
+
})();
|
| 69 |
+
</script>
|
| 70 |
+
"""
|
| 71 |
+
APP_CSS = """
|
| 72 |
+
#app-shell {
|
| 73 |
+
background: transparent;
|
| 74 |
+
}
|
| 75 |
+
#layout-shell {
|
| 76 |
+
gap: 1.25rem;
|
| 77 |
+
align-items: flex-start;
|
| 78 |
+
}
|
| 79 |
+
#topbar-row {
|
| 80 |
+
justify-content: flex-start;
|
| 81 |
+
align-items: center;
|
| 82 |
+
margin-bottom: 0.75rem;
|
| 83 |
+
}
|
| 84 |
+
#sidebar-toggle-shell {
|
| 85 |
+
width: auto;
|
| 86 |
+
}
|
| 87 |
+
#sidebar-toggle-shell > div {
|
| 88 |
+
width: auto !important;
|
| 89 |
+
}
|
| 90 |
+
#sidebar-toggle-icon {
|
| 91 |
+
display: inline-flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: center;
|
| 94 |
+
width: 2.75rem;
|
| 95 |
+
height: 1.75rem;
|
| 96 |
+
border: 1px solid var(--button-secondary-border-color);
|
| 97 |
+
border-radius: 5px;
|
| 98 |
+
background: var(--button-secondary-background-fill);
|
| 99 |
+
cursor: pointer;
|
| 100 |
+
transition: background-color 0.2s ease, border-color 0.2s ease;
|
| 101 |
+
}
|
| 102 |
+
#sidebar-toggle-icon:hover {
|
| 103 |
+
background: var(--button-secondary-background-fill-hover);
|
| 104 |
+
}
|
| 105 |
+
#sidebar-toggle-icon svg {
|
| 106 |
+
width: 1.35rem;
|
| 107 |
+
height: 1.35rem;
|
| 108 |
+
stroke: #6b7280;
|
| 109 |
+
}
|
| 110 |
+
#sidebar-toggle {
|
| 111 |
+
display: none;
|
| 112 |
+
}
|
| 113 |
+
#sidebar-panel {
|
| 114 |
+
max-width: 360px;
|
| 115 |
+
position: sticky;
|
| 116 |
+
top: 1rem;
|
| 117 |
+
align-self: flex-start;
|
| 118 |
+
border-radius: 16px;
|
| 119 |
+
overflow: hidden;
|
| 120 |
+
}
|
| 121 |
+
#main-panel {
|
| 122 |
+
min-width: 0;
|
| 123 |
+
}
|
| 124 |
+
#analysis-panel,
|
| 125 |
+
#chat-panel {
|
| 126 |
+
border: 1px solid var(--block-border-color);
|
| 127 |
+
border-radius: 16px;
|
| 128 |
+
padding: 1rem;
|
| 129 |
+
backdrop-filter: blur(12px);
|
| 130 |
+
}
|
| 131 |
+
#analysis-output {
|
| 132 |
+
min-height: 100px;
|
| 133 |
+
}
|
| 134 |
+
#status-output,
|
| 135 |
+
#chat-status {
|
| 136 |
+
min-height: 1.5rem;
|
| 137 |
+
}
|
| 138 |
+
#view-document-shell {
|
| 139 |
+
margin-top: 0.5rem;
|
| 140 |
+
}
|
| 141 |
+
#view-document-shell .view-document-link,
|
| 142 |
+
#view-document-shell .view-document-link-disabled {
|
| 143 |
+
display: flex;
|
| 144 |
+
align-items: center;
|
| 145 |
+
justify-content: center;
|
| 146 |
+
gap: 0.45rem;
|
| 147 |
+
width: 100%;
|
| 148 |
+
min-height: 2.75rem;
|
| 149 |
+
padding: 0.7rem 1rem;
|
| 150 |
+
border-radius: 0.6rem;
|
| 151 |
+
border: 1px solid var(--button-secondary-border-color);
|
| 152 |
+
font-weight: 600;
|
| 153 |
+
text-decoration: none;
|
| 154 |
+
box-sizing: border-box;
|
| 155 |
+
}
|
| 156 |
+
#view-document-shell .view-document-link {
|
| 157 |
+
color: inherit;
|
| 158 |
+
background: var(--button-secondary-background-fill);
|
| 159 |
+
}
|
| 160 |
+
#view-document-shell .view-document-link:hover {
|
| 161 |
+
background: var(--button-secondary-background-fill-hover);
|
| 162 |
+
}
|
| 163 |
+
#view-document-shell .view-document-link-disabled {
|
| 164 |
+
color: inherit;
|
| 165 |
+
background: var(--button-secondary-background-fill);
|
| 166 |
+
cursor: not-allowed;
|
| 167 |
+
pointer-events: none;
|
| 168 |
+
}
|
| 169 |
+
#view-document-shell .view-document-link-disabled svg {
|
| 170 |
+
opacity: 0.9;
|
| 171 |
+
}
|
| 172 |
+
#view-document-shell svg {
|
| 173 |
+
width: 0.95rem;
|
| 174 |
+
height: 0.95rem;
|
| 175 |
+
flex: 0 0 auto;
|
| 176 |
+
}
|
| 177 |
+
#example-bills-heading {
|
| 178 |
+
display: flex;
|
| 179 |
+
align-items: center;
|
| 180 |
+
gap: 0.35rem;
|
| 181 |
+
margin-bottom: 0.35rem;
|
| 182 |
+
}
|
| 183 |
+
#example-bills-heading p {
|
| 184 |
+
margin: 0;
|
| 185 |
+
}
|
| 186 |
+
#example-bills-info {
|
| 187 |
+
position: relative;
|
| 188 |
+
display: inline-flex;
|
| 189 |
+
align-items: center;
|
| 190 |
+
justify-content: center;
|
| 191 |
+
width: 1rem;
|
| 192 |
+
height: 1rem;
|
| 193 |
+
border: 1px solid #94a3b8;
|
| 194 |
+
border-radius: 999px;
|
| 195 |
+
color: #64748b;
|
| 196 |
+
font-size: 0.72rem;
|
| 197 |
+
font-weight: 700;
|
| 198 |
+
line-height: 1;
|
| 199 |
+
cursor: help;
|
| 200 |
+
}
|
| 201 |
+
#example-bills-info::after {
|
| 202 |
+
content: attr(data-tooltip);
|
| 203 |
+
position: absolute;
|
| 204 |
+
left: calc(100% + 0.45rem);
|
| 205 |
+
top: 50%;
|
| 206 |
+
transform: translateY(-50%);
|
| 207 |
+
width: 240px;
|
| 208 |
+
padding: 0.6rem 0.7rem;
|
| 209 |
+
border-radius: 0.6rem;
|
| 210 |
+
background: rgba(15, 23, 42, 0.96);
|
| 211 |
+
color: #f8fafc;
|
| 212 |
+
font-size: 0.78rem;
|
| 213 |
+
font-weight: 500;
|
| 214 |
+
line-height: 1.35;
|
| 215 |
+
opacity: 0;
|
| 216 |
+
visibility: hidden;
|
| 217 |
+
pointer-events: none;
|
| 218 |
+
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.22);
|
| 219 |
+
transition: opacity 0.18s ease;
|
| 220 |
+
z-index: 10;
|
| 221 |
+
}
|
| 222 |
+
#example-bills-info:hover::after,
|
| 223 |
+
#example-bills-info:focus-visible::after {
|
| 224 |
+
opacity: 1;
|
| 225 |
+
visibility: visible;
|
| 226 |
+
}
|
| 227 |
+
#app-shell .label-wrap,
|
| 228 |
+
#app-shell .label-wrap > *,
|
| 229 |
+
#app-shell label,
|
| 230 |
+
#app-shell legend,
|
| 231 |
+
#app-shell [data-testid="block-label"] {
|
| 232 |
+
background: transparent !important;
|
| 233 |
+
box-shadow: none !important;
|
| 234 |
+
}
|
| 235 |
+
"""
|
| 236 |
+
|
| 237 |
+
_GRADIO_SESSION_CACHE: dict[str, dict[str, Any]] = {}
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def _empty_session() -> dict[str, Any]:
|
| 241 |
+
return {"session_id": uuid4().hex}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _session_record(session_state: dict[str, Any] | None) -> dict[str, Any]:
|
| 245 |
+
session_state = session_state or _empty_session()
|
| 246 |
+
session_id = session_state.get("session_id")
|
| 247 |
+
if not session_id:
|
| 248 |
+
session_id = uuid4().hex
|
| 249 |
+
session_state["session_id"] = session_id
|
| 250 |
+
return _GRADIO_SESSION_CACHE.setdefault(
|
| 251 |
+
session_id,
|
| 252 |
+
{
|
| 253 |
+
"api_config": None,
|
| 254 |
+
"analysis": None,
|
| 255 |
+
"vector_store": None,
|
| 256 |
+
"doc_text": None,
|
| 257 |
+
"chat_history": [],
|
| 258 |
+
"pending_deeper_question": None,
|
| 259 |
+
"source_url": None,
|
| 260 |
+
},
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def _resolve_provider(use_advanced: bool, provider_label: str | None) -> str:
|
| 265 |
+
return DEFAULT_PROVIDER
|
| 266 |
+
# Proposed future expansion: restore bring-your-own provider selection.
|
| 267 |
+
# if not use_advanced:
|
| 268 |
+
# return DEFAULT_PROVIDER
|
| 269 |
+
# if not provider_label:
|
| 270 |
+
# return DEFAULT_PROVIDER
|
| 271 |
+
# return PROVIDER_BY_LABEL.get(provider_label, DEFAULT_PROVIDER)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _resolve_api_key(provider: str, use_advanced: bool, qwen_key: str | None, custom_key: str | None) -> str | None:
|
| 275 |
+
return qwen_key or get_default_api_key(provider)
|
| 276 |
+
# Proposed future expansion: restore bring-your-own provider API keys.
|
| 277 |
+
# if not use_advanced and provider == DEFAULT_PROVIDER:
|
| 278 |
+
# return qwen_key or get_default_api_key(provider)
|
| 279 |
+
# return custom_key or get_default_api_key(provider)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _ingest_sources(file_path: str | None, url: str | None) -> str:
|
| 283 |
+
chunks = []
|
| 284 |
+
if file_path:
|
| 285 |
+
path = Path(file_path)
|
| 286 |
+
chunks.append(ingest.ingest_file(path.name, path.read_bytes()))
|
| 287 |
+
if url and url.strip():
|
| 288 |
+
chunks.append(ingest.fetch_url_content(url.strip()))
|
| 289 |
+
if not chunks:
|
| 290 |
+
raise ingest.IngestionError("Provide a file or URL to analyze.")
|
| 291 |
+
return ingest.combine_sources(chunks)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def _escape_md_cell(value: str) -> str:
|
| 295 |
+
return str(value).replace("|", "\\|").replace("\n", " ").strip()
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def _markdown_bullets(items: list[str]) -> str:
|
| 299 |
+
if not items:
|
| 300 |
+
return "-"
|
| 301 |
+
return "\n".join(f"- {_escape_md_cell(item) or '—'}" for item in items)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def _analysis_attr(analysis: AnalysisResult, field: str, default: Any) -> Any:
|
| 305 |
+
return getattr(analysis, field, default)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def _swot_attr(analysis: AnalysisResult, field: str) -> list[str]:
|
| 309 |
+
swot = _analysis_attr(analysis, "swot", None)
|
| 310 |
+
return getattr(swot, field, []) if swot is not None else []
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def _format_table_rows(rows: list[list[str]], column_count: int) -> str:
|
| 314 |
+
if not rows:
|
| 315 |
+
return "| " + " | ".join(["—"] * column_count) + " |"
|
| 316 |
+
return "\n".join("| " + " | ".join(_escape_md_cell(cell) or "—" for cell in row) + " |" for row in rows)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _format_analysis(analysis: AnalysisResult | None) -> str:
|
| 320 |
+
if analysis is None:
|
| 321 |
+
return ANALYSIS_PLACEHOLDER
|
| 322 |
+
|
| 323 |
+
executive_summary = _escape_md_cell(_analysis_attr(analysis, "executive_summary", "")) or "No executive summary returned."
|
| 324 |
+
bill_summary = _markdown_bullets(_analysis_attr(analysis, "bill_summary", []))
|
| 325 |
+
implementation_rows = [
|
| 326 |
+
[
|
| 327 |
+
getattr(item, "stakeholder", ""),
|
| 328 |
+
getattr(item, "obligation", ""),
|
| 329 |
+
getattr(item, "implementation_burden", ""),
|
| 330 |
+
getattr(item, "risk_or_note", ""),
|
| 331 |
+
]
|
| 332 |
+
for item in _analysis_attr(analysis, "implementation", [])
|
| 333 |
+
]
|
| 334 |
+
critique_rows = [
|
| 335 |
+
[
|
| 336 |
+
getattr(item, "issue", ""),
|
| 337 |
+
getattr(item, "why_it_matters", ""),
|
| 338 |
+
getattr(item, "recommendation", ""),
|
| 339 |
+
]
|
| 340 |
+
for item in _analysis_attr(analysis, "critique", [])
|
| 341 |
+
]
|
| 342 |
+
strengths = _swot_attr(analysis, "strengths")
|
| 343 |
+
weaknesses = _swot_attr(analysis, "weaknesses")
|
| 344 |
+
opportunities = _swot_attr(analysis, "opportunities")
|
| 345 |
+
threats = _swot_attr(analysis, "threats")
|
| 346 |
+
swot_pair_rows = [
|
| 347 |
+
[strengths[idx] if idx < len(strengths) else "", weaknesses[idx] if idx < len(weaknesses) else ""]
|
| 348 |
+
for idx in range(max(len(strengths), len(weaknesses), 1))
|
| 349 |
+
]
|
| 350 |
+
swot_risk_rows = [
|
| 351 |
+
[opportunities[idx] if idx < len(opportunities) else "", threats[idx] if idx < len(threats) else ""]
|
| 352 |
+
for idx in range(max(len(opportunities), len(threats), 1))
|
| 353 |
+
]
|
| 354 |
+
|
| 355 |
+
return "\n\n".join(
|
| 356 |
+
[
|
| 357 |
+
"## Executive Summary\n" + executive_summary,
|
| 358 |
+
"## Summary of the Bill\n" + bill_summary,
|
| 359 |
+
"## Implementation Implications\n"
|
| 360 |
+
"| Stakeholder | Obligation | Burden | Risk / Note |\n"
|
| 361 |
+
"|---|---|---|---|\n"
|
| 362 |
+
+ _format_table_rows(implementation_rows, 4),
|
| 363 |
+
"## Critique\n"
|
| 364 |
+
"| Issue | Why it matters | Recommendation |\n"
|
| 365 |
+
"|---|---|---|\n"
|
| 366 |
+
+ _format_table_rows(critique_rows, 3),
|
| 367 |
+
"## SWOT Analysis\n"
|
| 368 |
+
"| Strengths | Weaknesses |\n"
|
| 369 |
+
"|---|---|\n"
|
| 370 |
+
+ _format_table_rows(swot_pair_rows, 2)
|
| 371 |
+
+ "\n\n"
|
| 372 |
+
+ "| Opportunities | Threats |\n"
|
| 373 |
+
+ "|---|---|\n"
|
| 374 |
+
+ _format_table_rows(swot_risk_rows, 2),
|
| 375 |
+
]
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def _format_chat_entry(answer_text: str, citations: list[dict[str, Any]], *, provenance: str = "analysis_based") -> str:
|
| 380 |
+
prefix = ""
|
| 381 |
+
if provenance == "analysis_based":
|
| 382 |
+
prefix = "_Based on the summary and analysis._\n\n"
|
| 383 |
+
elif provenance == "full_document":
|
| 384 |
+
prefix = "_Deeper full-document answer._\n\n"
|
| 385 |
+
|
| 386 |
+
if not citations:
|
| 387 |
+
return prefix + answer_text
|
| 388 |
+
citations_text = "\n".join(f"- [ref{item['ref_id']}] {item['snippet']}" for item in citations)
|
| 389 |
+
return f"{prefix}{answer_text}\n\nSupporting snippets:\n{citations_text}"
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
def _deeper_answer_updates(visible: bool, label: str = "Run deeper full-document answer") -> gr.update:
|
| 393 |
+
return gr.update(visible=visible, value=label)
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# Proposed future expansion: restore bring-your-own provider help text and
|
| 397 |
+
# controls when the app supports non-hackathon model/provider comparison again.
|
| 398 |
+
# def _provider_help_text(provider_label: str | None, use_advanced: bool) -> str:
|
| 399 |
+
# provider = _resolve_provider(use_advanced, provider_label)
|
| 400 |
+
# return PROVIDER_DETAILS[provider]
|
| 401 |
+
#
|
| 402 |
+
#
|
| 403 |
+
# def _toggle_provider_mode(use_advanced: bool) -> tuple[gr.update, gr.update, gr.update, gr.update]:
|
| 404 |
+
# provider_value = PROVIDER_LABEL_BY_NAME[DEFAULT_PROVIDER]
|
| 405 |
+
# return (
|
| 406 |
+
# gr.update(visible=not use_advanced),
|
| 407 |
+
# gr.update(visible=use_advanced),
|
| 408 |
+
# gr.update(visible=use_advanced),
|
| 409 |
+
# gr.update(value=_provider_help_text(provider_value, use_advanced)),
|
| 410 |
+
# )
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
def _set_example_url(example_label: str | None) -> str:
|
| 414 |
+
if not example_label:
|
| 415 |
+
return ""
|
| 416 |
+
return EXAMPLE_BILL_URLS.get(example_label, "")
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
def _render_document_link(url_value: str | None) -> str:
|
| 420 |
+
icon = (
|
| 421 |
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" '
|
| 422 |
+
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
|
| 423 |
+
'<path d="M14 5h5v5"></path>'
|
| 424 |
+
'<path d="M10 14 19 5"></path>'
|
| 425 |
+
'<path d="M19 14v5h-14v-14h5"></path>'
|
| 426 |
+
"</svg>"
|
| 427 |
+
)
|
| 428 |
+
if not url_value or not url_value.strip():
|
| 429 |
+
return (
|
| 430 |
+
'<span class="view-document-link-disabled">'
|
| 431 |
+
f'<span>View source</span>{icon}'
|
| 432 |
+
"</span>"
|
| 433 |
+
)
|
| 434 |
+
safe_url = html.escape(url_value.strip(), quote=True)
|
| 435 |
+
return (
|
| 436 |
+
f'<a class="view-document-link" href="{safe_url}" target="_blank" rel="noopener noreferrer">'
|
| 437 |
+
f'<span>View source</span>{icon}'
|
| 438 |
+
"</a>"
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def _analysis_stage_message(stage: str) -> str:
|
| 443 |
+
return f"_Processing... {stage}_"
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def _show_warning(message: str) -> str:
|
| 447 |
+
gr.Warning(message)
|
| 448 |
+
return message
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
def _show_error(message: str) -> str:
|
| 452 |
+
gr.Error(message)
|
| 453 |
+
return message
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def analyze_document(
|
| 457 |
+
uploaded_file: str | None,
|
| 458 |
+
url_value: str | None,
|
| 459 |
+
use_advanced: bool,
|
| 460 |
+
provider_label: str | None,
|
| 461 |
+
qwen_key: str | None,
|
| 462 |
+
custom_key: str | None,
|
| 463 |
+
session_state: dict[str, Any] | None,
|
| 464 |
+
):
|
| 465 |
+
session_state = session_state or _empty_session()
|
| 466 |
+
record = _session_record(session_state)
|
| 467 |
+
provider = _resolve_provider(use_advanced, provider_label)
|
| 468 |
+
api_key = _resolve_api_key(provider, use_advanced, qwen_key, custom_key)
|
| 469 |
+
is_valid, error = validate_api_key(provider, api_key)
|
| 470 |
+
if not is_valid:
|
| 471 |
+
message = "Check the selected provider and API key, then try again."
|
| 472 |
+
_show_warning(message)
|
| 473 |
+
yield (
|
| 474 |
+
session_state,
|
| 475 |
+
message,
|
| 476 |
+
ANALYSIS_PLACEHOLDER,
|
| 477 |
+
[],
|
| 478 |
+
_deeper_answer_updates(False),
|
| 479 |
+
"",
|
| 480 |
+
)
|
| 481 |
+
return
|
| 482 |
+
|
| 483 |
+
precomputed = get_precomputed_example_artifacts(url_value)
|
| 484 |
+
if precomputed is not None:
|
| 485 |
+
record.clear()
|
| 486 |
+
record.update(
|
| 487 |
+
{
|
| 488 |
+
"api_config": {"provider": provider, "api_key": api_key},
|
| 489 |
+
"analysis": precomputed.analysis.model_dump(),
|
| 490 |
+
"vector_store": precomputed.vector_store,
|
| 491 |
+
"doc_text": precomputed.document_text,
|
| 492 |
+
"chat_history": [],
|
| 493 |
+
"pending_deeper_question": None,
|
| 494 |
+
"source_url": url_value,
|
| 495 |
+
}
|
| 496 |
+
)
|
| 497 |
+
yield (
|
| 498 |
+
session_state,
|
| 499 |
+
"Loaded precomputed analysis for this example bill.",
|
| 500 |
+
_format_analysis(precomputed.analysis),
|
| 501 |
+
[],
|
| 502 |
+
_deeper_answer_updates(False),
|
| 503 |
+
"",
|
| 504 |
+
)
|
| 505 |
+
return
|
| 506 |
+
|
| 507 |
+
yield (
|
| 508 |
+
session_state,
|
| 509 |
+
"Loading and parsing document...",
|
| 510 |
+
_analysis_stage_message("Loading and parsing document..."),
|
| 511 |
+
[],
|
| 512 |
+
_deeper_answer_updates(False),
|
| 513 |
+
"",
|
| 514 |
+
)
|
| 515 |
+
|
| 516 |
+
try:
|
| 517 |
+
document_text = _ingest_sources(uploaded_file, url_value)
|
| 518 |
+
except ingest.IngestionError as exc:
|
| 519 |
+
message = str(exc)
|
| 520 |
+
_show_warning(message)
|
| 521 |
+
yield (
|
| 522 |
+
session_state,
|
| 523 |
+
message,
|
| 524 |
+
ANALYSIS_PLACEHOLDER,
|
| 525 |
+
[],
|
| 526 |
+
_deeper_answer_updates(False),
|
| 527 |
+
"",
|
| 528 |
+
)
|
| 529 |
+
return
|
| 530 |
+
except Exception: # noqa: BLE001
|
| 531 |
+
message = "We couldn't load that document. Check the link or upload the file directly and try again."
|
| 532 |
+
_show_error(message)
|
| 533 |
+
yield (
|
| 534 |
+
session_state,
|
| 535 |
+
message,
|
| 536 |
+
ANALYSIS_PLACEHOLDER,
|
| 537 |
+
[],
|
| 538 |
+
_deeper_answer_updates(False),
|
| 539 |
+
"",
|
| 540 |
+
)
|
| 541 |
+
return
|
| 542 |
+
|
| 543 |
+
yield (
|
| 544 |
+
session_state,
|
| 545 |
+
"Preparing chunks and provider client...",
|
| 546 |
+
_analysis_stage_message("Preparing chunks and provider client..."),
|
| 547 |
+
[],
|
| 548 |
+
_deeper_answer_updates(False),
|
| 549 |
+
"",
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
try:
|
| 553 |
+
provider_client = instantiate_client(provider, api_key or "")
|
| 554 |
+
except Exception: # noqa: BLE001
|
| 555 |
+
message = "We couldn't connect to the selected model provider. Check your API key and settings, then try again."
|
| 556 |
+
_show_error(message)
|
| 557 |
+
yield (
|
| 558 |
+
session_state,
|
| 559 |
+
message,
|
| 560 |
+
ANALYSIS_PLACEHOLDER,
|
| 561 |
+
[],
|
| 562 |
+
_deeper_answer_updates(False),
|
| 563 |
+
"",
|
| 564 |
+
)
|
| 565 |
+
return
|
| 566 |
+
|
| 567 |
+
yield (
|
| 568 |
+
session_state,
|
| 569 |
+
"Building retrieval index...",
|
| 570 |
+
_analysis_stage_message("Building retrieval index..."),
|
| 571 |
+
[],
|
| 572 |
+
_deeper_answer_updates(False),
|
| 573 |
+
"",
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
try:
|
| 577 |
+
_, chunks, vector_store = prepare_document_artifacts(document_text)
|
| 578 |
+
except Exception: # noqa: BLE001
|
| 579 |
+
message = "We couldn't prepare the document for analysis. Please try again."
|
| 580 |
+
_show_error(message)
|
| 581 |
+
yield (
|
| 582 |
+
session_state,
|
| 583 |
+
message,
|
| 584 |
+
ANALYSIS_PLACEHOLDER,
|
| 585 |
+
[],
|
| 586 |
+
_deeper_answer_updates(False),
|
| 587 |
+
"",
|
| 588 |
+
)
|
| 589 |
+
return
|
| 590 |
+
|
| 591 |
+
try:
|
| 592 |
+
analysis = AnalysisResult()
|
| 593 |
+
for stage_message, partial in generate_analysis_progress(provider_client, document_text):
|
| 594 |
+
analysis = partial
|
| 595 |
+
yield (
|
| 596 |
+
session_state,
|
| 597 |
+
stage_message,
|
| 598 |
+
_format_analysis(partial),
|
| 599 |
+
[],
|
| 600 |
+
_deeper_answer_updates(False),
|
| 601 |
+
"",
|
| 602 |
+
)
|
| 603 |
+
except Exception: # noqa: BLE001
|
| 604 |
+
message = "We couldn't generate the bill analysis right now. Please try again."
|
| 605 |
+
_show_error(message)
|
| 606 |
+
yield (
|
| 607 |
+
session_state,
|
| 608 |
+
message,
|
| 609 |
+
ANALYSIS_PLACEHOLDER,
|
| 610 |
+
[],
|
| 611 |
+
_deeper_answer_updates(False),
|
| 612 |
+
"",
|
| 613 |
+
)
|
| 614 |
+
return
|
| 615 |
+
|
| 616 |
+
build_cached_document_artifacts(
|
| 617 |
+
document_text=document_text,
|
| 618 |
+
chunks=chunks,
|
| 619 |
+
analysis=analysis,
|
| 620 |
+
vector_store=vector_store,
|
| 621 |
+
source_url=url_value,
|
| 622 |
+
)
|
| 623 |
+
record.clear()
|
| 624 |
+
record.update(
|
| 625 |
+
{
|
| 626 |
+
"api_config": {"provider": provider, "api_key": api_key},
|
| 627 |
+
"analysis": analysis.model_dump(),
|
| 628 |
+
"vector_store": vector_store,
|
| 629 |
+
"doc_text": document_text,
|
| 630 |
+
"chat_history": [],
|
| 631 |
+
"pending_deeper_question": None,
|
| 632 |
+
"source_url": url_value,
|
| 633 |
+
}
|
| 634 |
+
)
|
| 635 |
+
analysis_output = _format_analysis(analysis)
|
| 636 |
+
yield session_state, "", analysis_output, [], _deeper_answer_updates(False), ""
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def ask_question(
|
| 640 |
+
question: str | None,
|
| 641 |
+
session_state: dict[str, Any] | None,
|
| 642 |
+
chat_history: list[dict[str, str]] | None,
|
| 643 |
+
):
|
| 644 |
+
session_state = session_state or _empty_session()
|
| 645 |
+
record = _session_record(session_state)
|
| 646 |
+
chat_history = chat_history or []
|
| 647 |
+
if (not record.get("doc_text") and not record.get("vector_store")) or not record.get("api_config"):
|
| 648 |
+
yield chat_history, session_state, "Analyze a document before asking questions.", _deeper_answer_updates(False), ""
|
| 649 |
+
return
|
| 650 |
+
if not question or not question.strip():
|
| 651 |
+
yield chat_history, session_state, "", _deeper_answer_updates(False), ""
|
| 652 |
+
return
|
| 653 |
+
|
| 654 |
+
question_text = question.strip()
|
| 655 |
+
yield chat_history, session_state, "Searching analysis for an answer...", _deeper_answer_updates(False), ""
|
| 656 |
+
|
| 657 |
+
try:
|
| 658 |
+
provider_client = instantiate_client(
|
| 659 |
+
record["api_config"]["provider"],
|
| 660 |
+
record["api_config"]["api_key"],
|
| 661 |
+
)
|
| 662 |
+
yield chat_history, session_state, "Searching analysis for an answer...", _deeper_answer_updates(False), ""
|
| 663 |
+
answer = answer_query(
|
| 664 |
+
provider_client,
|
| 665 |
+
AnalysisResult.model_validate(record["analysis"]) if record.get("analysis") else None,
|
| 666 |
+
record.get("vector_store"),
|
| 667 |
+
question_text,
|
| 668 |
+
doc_text=record.get("doc_text"),
|
| 669 |
+
)
|
| 670 |
+
except Exception: # noqa: BLE001
|
| 671 |
+
message = "We couldn't answer that question right now. Please try again."
|
| 672 |
+
_show_error(message)
|
| 673 |
+
yield chat_history, session_state, message, _deeper_answer_updates(False), ""
|
| 674 |
+
return
|
| 675 |
+
|
| 676 |
+
citations = [citation.model_dump() for citation in answer.citations]
|
| 677 |
+
chat_history = chat_history + [
|
| 678 |
+
{"role": "user", "content": question_text},
|
| 679 |
+
{"role": "assistant", "content": _format_chat_entry(answer.answer, citations, provenance=answer.provenance)},
|
| 680 |
+
]
|
| 681 |
+
record["chat_history"] = chat_history
|
| 682 |
+
record["pending_deeper_question"] = question_text if answer.deeper_answer_available else None
|
| 683 |
+
deeper_hint = answer.consent_prompt if answer.deeper_answer_available else ""
|
| 684 |
+
yield chat_history, session_state, "", _deeper_answer_updates(answer.deeper_answer_available), deeper_hint
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def run_deeper_answer(
|
| 688 |
+
session_state: dict[str, Any] | None,
|
| 689 |
+
chat_history: list[dict[str, str]] | None,
|
| 690 |
+
):
|
| 691 |
+
session_state = session_state or _empty_session()
|
| 692 |
+
record = _session_record(session_state)
|
| 693 |
+
chat_history = chat_history or []
|
| 694 |
+
question_text = record.get("pending_deeper_question")
|
| 695 |
+
if not question_text:
|
| 696 |
+
yield chat_history, session_state, "No pending question for deeper analysis.", _deeper_answer_updates(False), ""
|
| 697 |
+
return
|
| 698 |
+
|
| 699 |
+
yield chat_history, session_state, "Reading the full document for a deeper answer...", _deeper_answer_updates(False), ""
|
| 700 |
+
|
| 701 |
+
try:
|
| 702 |
+
provider_client = instantiate_client(
|
| 703 |
+
record["api_config"]["provider"],
|
| 704 |
+
record["api_config"]["api_key"],
|
| 705 |
+
)
|
| 706 |
+
answer = answer_query_from_full_document(
|
| 707 |
+
provider_client,
|
| 708 |
+
record.get("vector_store"),
|
| 709 |
+
question_text,
|
| 710 |
+
doc_text=record.get("doc_text"),
|
| 711 |
+
)
|
| 712 |
+
except Exception: # noqa: BLE001
|
| 713 |
+
message = "We couldn't generate the deeper full-document answer right now. Please try again."
|
| 714 |
+
_show_error(message)
|
| 715 |
+
yield chat_history, session_state, message, _deeper_answer_updates(True), "A deeper full-document answer is still available."
|
| 716 |
+
return
|
| 717 |
+
|
| 718 |
+
citations = [citation.model_dump() for citation in answer.citations]
|
| 719 |
+
chat_history = chat_history + [
|
| 720 |
+
{"role": "assistant", "content": _format_chat_entry(answer.answer, citations, provenance=answer.provenance)},
|
| 721 |
+
]
|
| 722 |
+
record["chat_history"] = chat_history
|
| 723 |
+
record["pending_deeper_question"] = None
|
| 724 |
+
yield chat_history, session_state, "", _deeper_answer_updates(False), ""
|
| 725 |
+
|
| 726 |
+
|
| 727 |
+
def reset_session(session_state: dict[str, Any] | None):
|
| 728 |
+
if session_state and session_state.get("session_id"):
|
| 729 |
+
_GRADIO_SESSION_CACHE.pop(session_state["session_id"], None)
|
| 730 |
+
empty = _empty_session()
|
| 731 |
+
return empty, None, None, "", _render_document_link(""), ANALYSIS_PLACEHOLDER, [], "", "", _deeper_answer_updates(False), ""
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
def build_app() -> gr.Blocks:
|
| 735 |
+
with gr.Blocks(title=APP_TITLE, css=APP_CSS, theme=APP_THEME, elem_id="app-shell", head=APP_HEAD) as demo:
|
| 736 |
+
session_state = gr.State(_empty_session())
|
| 737 |
+
|
| 738 |
+
gr.Markdown(f"# {APP_TITLE}")
|
| 739 |
+
gr.Markdown(APP_DESCRIPTION)
|
| 740 |
+
with gr.Row(elem_id="topbar-row"):
|
| 741 |
+
gr.HTML(
|
| 742 |
+
"""
|
| 743 |
+
<div id="sidebar-toggle-shell">
|
| 744 |
+
<div
|
| 745 |
+
id="sidebar-toggle-icon"
|
| 746 |
+
role="button"
|
| 747 |
+
aria-label="Toggle sidebar"
|
| 748 |
+
tabindex="0"
|
| 749 |
+
onclick="window.__toggleSidebar && window.__toggleSidebar()"
|
| 750 |
+
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.__toggleSidebar && window.__toggleSidebar(); }"
|
| 751 |
+
>
|
| 752 |
+
<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 753 |
+
<rect x="3" y="4" width="18" height="16" rx="3"></rect>
|
| 754 |
+
<path d="M9 4v16"></path>
|
| 755 |
+
<path d="M6.5 9h.01"></path>
|
| 756 |
+
<path d="M6.5 12h.01"></path>
|
| 757 |
+
<path d="M6.5 15h.01"></path>
|
| 758 |
+
</svg>
|
| 759 |
+
</div>
|
| 760 |
+
</div>
|
| 761 |
+
"""
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
+
with gr.Row(elem_id="layout-shell"):
|
| 765 |
+
with gr.Column(scale=1, elem_id="sidebar-panel"):
|
| 766 |
+
gr.Markdown("## Document Source")
|
| 767 |
+
gr.Markdown("Upload PDF, DOCX, TXT, or MD")
|
| 768 |
+
uploaded_file = gr.File(label="Upload PDF, DOCX, TXT, or MD", type="filepath", show_label=False)
|
| 769 |
+
gr.HTML(
|
| 770 |
+
"""
|
| 771 |
+
<div id="example-bills-heading">
|
| 772 |
+
<p>Example bills</p>
|
| 773 |
+
<span
|
| 774 |
+
id="example-bills-info"
|
| 775 |
+
tabindex="0"
|
| 776 |
+
aria-label="Example bills information"
|
| 777 |
+
data-tooltip="The Ghana Innovation and Start-Up Bill, 2025 was omitted because a verifiable source could not be found."
|
| 778 |
+
>
|
| 779 |
+
i
|
| 780 |
+
</span>
|
| 781 |
+
</div>
|
| 782 |
+
"""
|
| 783 |
+
)
|
| 784 |
+
example_selector = gr.Dropdown(
|
| 785 |
+
label="Example bills",
|
| 786 |
+
choices=EXAMPLE_BILL_LABELS,
|
| 787 |
+
value=None,
|
| 788 |
+
show_label=False,
|
| 789 |
+
)
|
| 790 |
+
gr.Markdown("Document URL")
|
| 791 |
+
url_value = gr.Textbox(label="Document URL", placeholder="https://example.com/bill", show_label=False)
|
| 792 |
+
analyze_button = gr.Button("Run analysis", variant="primary")
|
| 793 |
+
view_document_link = gr.HTML(_render_document_link(""), elem_id="view-document-shell")
|
| 794 |
+
reset_button = gr.Button("Reset session")
|
| 795 |
+
status_output = gr.Markdown(elem_id="status-output")
|
| 796 |
+
use_advanced = gr.State(False)
|
| 797 |
+
provider_label = gr.State(PROVIDER_LABEL_BY_NAME[DEFAULT_PROVIDER])
|
| 798 |
+
qwen_key = gr.State(None)
|
| 799 |
+
custom_key = gr.State(None)
|
| 800 |
+
|
| 801 |
+
# Proposed future expansion: restore the sidebar Model Settings
|
| 802 |
+
# section after the hackathon build is no longer pinned to Qwen.
|
| 803 |
+
# with gr.Accordion("Model Settings", open=False):
|
| 804 |
+
# provider_help = gr.Markdown(PROVIDER_DETAILS[DEFAULT_PROVIDER])
|
| 805 |
+
# gr.Markdown("Hugging Face Token")
|
| 806 |
+
# qwen_key = gr.Textbox(
|
| 807 |
+
# label="Hugging Face Token",
|
| 808 |
+
# type="password",
|
| 809 |
+
# placeholder="Leave blank to use HF_TOKEN",
|
| 810 |
+
# visible=True,
|
| 811 |
+
# show_label=False,
|
| 812 |
+
# )
|
| 813 |
+
# use_advanced = gr.Checkbox(label="Bring your own provider", value=False)
|
| 814 |
+
# gr.Markdown("Model Provider")
|
| 815 |
+
# provider_label = gr.Dropdown(
|
| 816 |
+
# label="Model Provider",
|
| 817 |
+
# choices=PROVIDER_LABELS,
|
| 818 |
+
# value=PROVIDER_LABEL_BY_NAME[DEFAULT_PROVIDER],
|
| 819 |
+
# visible=False,
|
| 820 |
+
# show_label=False,
|
| 821 |
+
# )
|
| 822 |
+
# gr.Markdown("API Key")
|
| 823 |
+
# custom_key = gr.Textbox(
|
| 824 |
+
# label="API Key",
|
| 825 |
+
# type="password",
|
| 826 |
+
# placeholder="Leave blank to use provider env var",
|
| 827 |
+
# visible=False,
|
| 828 |
+
# show_label=False,
|
| 829 |
+
# )
|
| 830 |
+
|
| 831 |
+
with gr.Column(scale=2, elem_id="main-panel"):
|
| 832 |
+
with gr.Group(elem_id="analysis-panel"):
|
| 833 |
+
gr.Markdown("# Summary Highlights")
|
| 834 |
+
analysis_output = gr.Markdown(ANALYSIS_PLACEHOLDER, elem_id="analysis-output")
|
| 835 |
+
|
| 836 |
+
with gr.Group(elem_id="chat-panel"):
|
| 837 |
+
gr.Markdown("## Ask Further Questions")
|
| 838 |
+
chatbot = gr.Chatbot(type="messages", show_label=False)
|
| 839 |
+
with gr.Row():
|
| 840 |
+
with gr.Column(scale=6, min_width=0):
|
| 841 |
+
gr.Markdown("Question")
|
| 842 |
+
question_input = gr.Textbox(
|
| 843 |
+
label="Question",
|
| 844 |
+
placeholder="What would you like to know?",
|
| 845 |
+
show_label=False,
|
| 846 |
+
)
|
| 847 |
+
ask_button = gr.Button("Ask", scale=1, variant="primary")
|
| 848 |
+
chat_status = gr.Markdown(elem_id="chat-status")
|
| 849 |
+
deeper_answer_button = gr.Button("Run deeper full-document answer", visible=False)
|
| 850 |
+
deeper_answer_hint = gr.Markdown("")
|
| 851 |
+
|
| 852 |
+
# Proposed future expansion: restore bring-your-own provider event wiring
|
| 853 |
+
# when alternate provider controls are re-enabled.
|
| 854 |
+
# use_advanced.change(
|
| 855 |
+
# _toggle_provider_mode,
|
| 856 |
+
# inputs=[use_advanced],
|
| 857 |
+
# outputs=[qwen_key, provider_label, custom_key, provider_help],
|
| 858 |
+
# )
|
| 859 |
+
# provider_label.change(
|
| 860 |
+
# _provider_help_text,
|
| 861 |
+
# inputs=[provider_label, use_advanced],
|
| 862 |
+
# outputs=[provider_help],
|
| 863 |
+
# )
|
| 864 |
+
example_selector.change(
|
| 865 |
+
lambda example_label: (
|
| 866 |
+
_set_example_url(example_label),
|
| 867 |
+
_render_document_link(_set_example_url(example_label)),
|
| 868 |
+
),
|
| 869 |
+
inputs=[example_selector],
|
| 870 |
+
outputs=[url_value, view_document_link],
|
| 871 |
+
)
|
| 872 |
+
url_value.input(_render_document_link, inputs=[url_value], outputs=[view_document_link])
|
| 873 |
+
url_value.change(_render_document_link, inputs=[url_value], outputs=[view_document_link])
|
| 874 |
+
analyze_button.click(
|
| 875 |
+
analyze_document,
|
| 876 |
+
inputs=[uploaded_file, url_value, use_advanced, provider_label, qwen_key, custom_key, session_state],
|
| 877 |
+
outputs=[
|
| 878 |
+
session_state,
|
| 879 |
+
status_output,
|
| 880 |
+
analysis_output,
|
| 881 |
+
chatbot,
|
| 882 |
+
deeper_answer_button,
|
| 883 |
+
deeper_answer_hint,
|
| 884 |
+
],
|
| 885 |
+
)
|
| 886 |
+
ask_button.click(
|
| 887 |
+
ask_question,
|
| 888 |
+
inputs=[question_input, session_state, chatbot],
|
| 889 |
+
outputs=[chatbot, session_state, chat_status, deeper_answer_button, deeper_answer_hint],
|
| 890 |
+
).then(lambda: "", outputs=[question_input])
|
| 891 |
+
question_input.submit(
|
| 892 |
+
ask_question,
|
| 893 |
+
inputs=[question_input, session_state, chatbot],
|
| 894 |
+
outputs=[chatbot, session_state, chat_status, deeper_answer_button, deeper_answer_hint],
|
| 895 |
+
).then(lambda: "", outputs=[question_input])
|
| 896 |
+
deeper_answer_button.click(
|
| 897 |
+
run_deeper_answer,
|
| 898 |
+
inputs=[session_state, chatbot],
|
| 899 |
+
outputs=[chatbot, session_state, chat_status, deeper_answer_button, deeper_answer_hint],
|
| 900 |
+
)
|
| 901 |
+
reset_button.click(
|
| 902 |
+
reset_session,
|
| 903 |
+
inputs=[session_state],
|
| 904 |
+
outputs=[
|
| 905 |
+
session_state,
|
| 906 |
+
uploaded_file,
|
| 907 |
+
example_selector,
|
| 908 |
+
url_value,
|
| 909 |
+
view_document_link,
|
| 910 |
+
analysis_output,
|
| 911 |
+
chatbot,
|
| 912 |
+
status_output,
|
| 913 |
+
chat_status,
|
| 914 |
+
deeper_answer_button,
|
| 915 |
+
deeper_answer_hint,
|
| 916 |
+
],
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
demo.queue()
|
| 920 |
+
return demo
|
| 921 |
+
|
| 922 |
+
|
| 923 |
+
if gr.NO_RELOAD:
|
| 924 |
+
warm_embedding_stack()
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
# Expose the default `demo` symbol so Hugging Face Spaces can launch app.py.
|
| 928 |
+
demo = build_app()
|
| 929 |
+
app = demo
|
| 930 |
+
|
| 931 |
+
|
| 932 |
+
if __name__ == "__main__":
|
| 933 |
+
app.launch()
|
assets/example_bills/cybersecurity-amendment-bill-2025/analysis.json
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"executive_summary": "The Cybersecurity (Amendment) Bill, 2025 expands the powers and responsibilities of Ghana's Cyber Security Authority (CSA) to investigate and prosecute cybercrime, regulate emerging technologies, and strengthen cybersecurity governance. Key changes include granting the CSA prosecutorial authority under the Attorney-General's oversight, establishing standards for AI, IoT, and blockchain, and enhancing enforcement mechanisms. The bill also introduces stricter penalties for non-compliance, mandates registration of critical infrastructure, and creates a cybersecurity fund. These amendments aim to bolster national digital security, protect vulnerable groups, and align Ghana with global cybersecurity practices.",
|
| 3 |
+
"bill_summary": [
|
| 4 |
+
"Amends Section 3 to expand the CSA's objects to include investigating/prosecuting cybercrime, regulating emerging technologies, and protecting vulnerable populations.",
|
| 5 |
+
"Inserts Section 4A to grant the CSA authority to investigate/prosecute cybercrime under the Attorney-General's direction and set security standards for AI, IoT, and blockchain.",
|
| 6 |
+
"Amends Section 13 to include representatives from the Data Protection Commission, National Intelligence Bureau, and National Signals Bureau in the Joint Cybersecurity Committee.",
|
| 7 |
+
"Amends Section 35 to clarify criteria for designating critical information infrastructure (CII) and mandates sector-specific regulations.",
|
| 8 |
+
"Amends Section 36 to require CII owners to register with the CSA, nominate a point of contact, and pay annual fees, with penalties for non-compliance.",
|
| 9 |
+
"Inserts Section 59A to empower the CSA with criminal and administrative enforcement tools, including fines, injunctions, and asset confiscation.",
|
| 10 |
+
"Inserts Section 59B to grant the CSA jurisdiction to prosecute cybercrimes under the Electronic Transactions Act, 2008 (Act 772) and recover proceeds.",
|
| 11 |
+
"Amends Section 31 to establish a cybersecurity fund financed by fines, taxes, and fees, with 30% of certification revenue allocated to the fund.",
|
| 12 |
+
"Inserts Section 57A-57C to create a cyber hygiene certification scheme for professionals and service providers, with standardized fees and audits.",
|
| 13 |
+
"Amends Section 49 to require licenses/accreditations for cybersecurity service providers and practitioners, with penalties for unlicensed activity.",
|
| 14 |
+
"Inserts Section 59C to authorize the CSA to request information from CII owners and third parties during investigations, with court-ordered disclosure if necessary.",
|
| 15 |
+
"Amends Section 47 to mandate Sectoral Computer Emergency Response Teams to report cybersecurity incidents to the National CERT within 24 hours.",
|
| 16 |
+
"Amends Section 20A to ensure CSA staff receive terms/conditions comparable to security/intelligence agencies and legal officers of the Attorney-General.",
|
| 17 |
+
"Inserts Section 20B to grant CSA officers police-like powers (arrest, search, seizure) under specified laws.",
|
| 18 |
+
"Amends Section 57 to require accreditation for cybersecurity professionals/practitioners, with penalties for unaccredited practice."
|
| 19 |
+
],
|
| 20 |
+
"implementation": [
|
| 21 |
+
{
|
| 22 |
+
"stakeholder": "Critical Information Infrastructure (CII) Owners",
|
| 23 |
+
"obligation": "Register designated CII with the Cyber Security Authority (CSA) and submit verified details, nominate a point of contact, and notify the CSA of ownership changes within seven days.",
|
| 24 |
+
"implementation_burden": "Administrative and compliance costs for registration, documentation, and ongoing reporting requirements.",
|
| 25 |
+
"risk_or_note": "Non-compliance risks administrative penalties (Second Schedule) and potential legal exposure for unregistered infrastructure."
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"stakeholder": "Cybersecurity Service Providers",
|
| 29 |
+
"obligation": "Obtain a license from the CSA to provide cybersecurity services for profit or accreditation for not-for-profit services.",
|
| 30 |
+
"implementation_burden": "Costs and time associated with applying for licenses/accreditations and meeting CSA standards.",
|
| 31 |
+
"risk_or_note": "Unlicensed or unaccredited providers face penalties (up to 100,000 penalty units) and potential criminal liability."
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"stakeholder": "Cybersecurity Professionals/Practitioners",
|
| 35 |
+
"obligation": "Obtain CSA accreditation to practice, and adhere to cyber hygiene certification schemes.",
|
| 36 |
+
"implementation_burden": "Training, certification costs, and compliance with periodic audits.",
|
| 37 |
+
"risk_or_note": "Unaccredited individuals face penalties (250\u201320,000 penalty units) and criminal liability for non-compliance."
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"stakeholder": "Cyber Security Authority (CSA)",
|
| 41 |
+
"obligation": "Establish mechanisms for investigating/prosecuting cybercrime, certifying emerging technologies, and enforcing compliance via audits, penalties, and legal actions.",
|
| 42 |
+
"implementation_burden": "Resource allocation for expanded investigative, prosecutorial, and regulatory functions.",
|
| 43 |
+
"risk_or_note": "Potential operational strain from increased responsibilities and reliance on inter-agency coordination."
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"stakeholder": "Law Enforcement and Judicial Bodies",
|
| 47 |
+
"obligation": "Process production orders for computer data, confirm property freezes, and adjudicate cybercrime cases under CSA jurisdiction.",
|
| 48 |
+
"implementation_burden": "Training on new procedures and handling of technical evidence.",
|
| 49 |
+
"risk_or_note": "Increased caseloads and potential delays in resolving cybercrime cases."
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"stakeholder": "Private Sector Entities (Non-CII)",
|
| 53 |
+
"obligation": "Comply with CSA directives on cybersecurity standards and cooperate in investigations (e.g., furnishing information).",
|
| 54 |
+
"implementation_burden": "Adapting internal policies and systems to meet CSA requirements.",
|
| 55 |
+
"risk_or_note": "Non-compliance risks administrative penalties or legal action under Section 59A."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"stakeholder": "Non-Profit Cybersecurity Institutions",
|
| 59 |
+
"obligation": "Seek CSA accreditation to operate legally.",
|
| 60 |
+
"implementation_burden": "Meeting CSA-defined criteria for accreditation.",
|
| 61 |
+
"risk_or_note": "Unaccredited institutions face penalties and operational restrictions."
|
| 62 |
+
}
|
| 63 |
+
],
|
| 64 |
+
"critique": [
|
| 65 |
+
{
|
| 66 |
+
"issue": "Expanded investigative and prosecutorial powers of the Cyber Security Authority (CSA) without clear safeguards",
|
| 67 |
+
"why_it_matters": "Section 59B grants the CSA authority to investigate and prosecute cybercrime independently, including freezing property and confiscating proceeds. This risks overreach and potential misuse of power without explicit judicial oversight or procedural safeguards to protect individual rights.",
|
| 68 |
+
"recommendation": "Introduce mandatory judicial review for property freezes and asset confiscation orders, and establish an independent oversight body to monitor CSA investigations."
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"issue": "Ambiguity in 'critical information infrastructure' designation criteria",
|
| 72 |
+
"why_it_matters": "Section 35 allows the Minister to designate systems as critical based on vague factors like 'national security' or 'economic well-being.' This could lead to arbitrary designations, imposing compliance burdens on businesses without clear justification.",
|
| 73 |
+
"recommendation": "Define specific, measurable criteria for designation and require public consultation or parliamentary approval for such designations."
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"issue": "Mandatory accreditation and licensing for cybersecurity professionals and services",
|
| 77 |
+
"why_it_matters": "Sections 57 and 49 require accreditation/licenses for cybersecurity work, which may stifle innovation and exclude small providers or startups unable to meet bureaucratic hurdles, reducing competition and access to services.",
|
| 78 |
+
"recommendation": "Create tiered accreditation levels with exemptions for low-risk services and provide financial or technical support for SMEs to meet requirements."
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"issue": "Broad administrative penalties without proportionality",
|
| 82 |
+
"why_it_matters": "Penalties in Sections 59A and 59C range from 250 to 100,000 penalty units without clear proportionality to the severity of violations. This risks punitive overreach, especially for minor infractions.",
|
| 83 |
+
"recommendation": "Establish a graduated penalty system tied to specific offense categories and provide a right to appeal administrative penalties."
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"issue": "Mandatory cyber hygiene certification for emerging technologies",
|
| 87 |
+
"why_it_matters": "Section 57A requires certification for AI, blockchain, and other technologies, which could delay innovation and create compliance costs for developers. The CSA may lack expertise to evaluate rapidly evolving fields.",
|
| 88 |
+
"recommendation": "Adopt a risk-based approach to certification, with exemptions for low-risk or experimental technologies, and establish a dedicated technical advisory panel."
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"issue": "Increased funding obligations for businesses and public services",
|
| 92 |
+
"why_it_matters": "Section 31 mandates 12% of communications taxes and 9% of corporate taxes to fund cybersecurity, potentially burdening SMEs and public institutions. The CSA's fee-setting power (Section 57C) could further strain budgets.",
|
| 93 |
+
"recommendation": "Cap mandatory contributions and require cost-benefit analyses for new fees to ensure affordability for small businesses and public services."
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"issue": "Lack of clarity on cross-sector coordination in the Joint Cybersecurity Committee (JCC)",
|
| 97 |
+
"why_it_matters": "Section 14 mandates JCC collaboration but does not define decision-making processes or conflict resolution mechanisms, risking inefficiency and duplication of efforts.",
|
| 98 |
+
"recommendation": "Establish clear protocols for JCC operations, including timelines for consensus-building and a chairperson with binding authority in deadlocks."
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"issue": "Potential privacy risks from production orders for computer data",
|
| 102 |
+
"why_it_matters": "Section 59D allows ex-parte court applications for data production orders, which could enable surveillance without prior notice or defense. The 'reasonable grounds' standard is subjectively interpreted.",
|
| 103 |
+
"recommendation": "Require dual judicial approval for production orders and mandate post-issuance review to assess proportionality and necessity."
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"swot": {
|
| 107 |
+
"strengths": [
|
| 108 |
+
"Expands the Cyber Security Authority's (CSA) powers to investigate and prosecute cybercrime independently, enhancing response speed and reducing reliance on external agencies.",
|
| 109 |
+
"Introduces mandatory accreditation and certification for cybersecurity professionals, service providers, and emerging technologies, raising industry standards and trust.",
|
| 110 |
+
"Establishes a dedicated cybersecurity fund with multiple revenue streams (fines, taxes, fees), ensuring sustainable financing for operations and infrastructure.",
|
| 111 |
+
"Mandates cross-sector collaboration through the Joint Cybersecurity Committee (JCC), improving coordination between public and private entities for threat mitigation.",
|
| 112 |
+
"Enhances protection for vulnerable groups (women, elderly, persons with disabilities) through explicit provisions for online safety and digital rights promotion."
|
| 113 |
+
],
|
| 114 |
+
"weaknesses": [
|
| 115 |
+
"Broad investigative and enforcement powers for the CSA without explicit oversight mechanisms risk potential overreach or misuse, particularly in data access and asset freezes.",
|
| 116 |
+
"Mandatory registration and certification requirements for critical infrastructure and cybersecurity professionals could create compliance burdens for small and medium enterprises (SMEs).",
|
| 117 |
+
"High administrative penalties (e.g., 50,000\u2013100,000 penalty units for unlicensed services) may disproportionately impact startups and informal operators.",
|
| 118 |
+
"The CSA's expanded role in prosecuting cybercrime under the Electronic Transactions Act, 2008 (Act 772) may lead to jurisdictional conflicts with existing law enforcement agencies.",
|
| 119 |
+
"Lack of detailed timelines or performance metrics for the CSA's new functions (e.g., certification of AI/quantum tech) risks delays or inconsistent implementation."
|
| 120 |
+
],
|
| 121 |
+
"opportunities": [
|
| 122 |
+
"Standardization of cybersecurity practices for emerging technologies (AI, IoT, blockchain) positions Ghana as a regional leader in tech governance and innovation.",
|
| 123 |
+
"The cyber hygiene certification scheme creates a formal pathway for local professionals to gain international recognition, boosting employment and exports.",
|
| 124 |
+
"Mandatory incident reporting (24-hour window for Sectoral CERTs) improves threat intelligence sharing, enabling proactive defense against cross-sector attacks.",
|
| 125 |
+
"Integration of cybersecurity into national critical infrastructure (e.g., banking, utilities) strengthens economic resilience against disruptive cyber incidents.",
|
| 126 |
+
"The 30% revenue-sharing model for the cybersecurity fund incentivizes certified professionals and institutions to maintain compliance and quality."
|
| 127 |
+
],
|
| 128 |
+
"threats": [
|
| 129 |
+
"Overly prescriptive standards for emerging technologies (e.g., AI, quantum computing) could stifle innovation by imposing rigid compliance requirements on nascent industries.",
|
| 130 |
+
"The CSA's authority to freeze assets during investigations may deter foreign investment if perceived as a risk to property rights without judicial oversight.",
|
| 131 |
+
"Mandatory accreditation for non-profit cybersecurity institutions could exclude grassroots organizations, limiting public access to affordable security services.",
|
| 132 |
+
"High compliance costs for critical infrastructure owners (registration fees, audits) may force smaller operators out of the market, reducing competition.",
|
| 133 |
+
"The bill's focus on criminal enforcement (e.g., imprisonment for non-compliance) risks prioritizing punitive measures over education and capacity-building for vulnerable groups."
|
| 134 |
+
]
|
| 135 |
+
}
|
| 136 |
+
}
|
assets/example_bills/cybersecurity-amendment-bill-2025/chunks.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"CYBERSECURITY (AMENDMENT) BILL, 2025\n1 ARRANGEMENT OF SECTIONS\nSection\n1. Section 3 of Act 1038 Amended\n2. Section 4A inserted\n3. Section 5 of Act 1038 amended\n4. Section 13 of Act 1038 amended\n5. Section 14 of Act 1038 amended\n6. Section 15A inserted\n7. Section 20A inserted\n8. Section 20B inserted\n9. Section 31 of Act 1038 amended\n10. Section 35 of Act 1038 amended\n11. Section 36 of Act 1038 amended\n12. Section 37 of Act 1038 amended\n13. Section 40 of Act 1038 amended\n14. Section 47 of Act 1038 amended\n15. Section 49 of Act 1038 amended\n16. Section 57 of Act 1038 amended\n17. Section 57A inserted\n18. Section 57B inserted\n19. Section 57C inserted\n20. Section 58A inserted\n21. Section 58B inserted\n22. Section 59 of Act 1038 amended\n23. Section 59A inserted\n24. Section 59B inserted\n25. Section 59C inserted\n26. Section 59D inserted\n27. Section 59E inserted\n28. Section 59F inserted\n29. Section 59G inserted\n30. Section 59H inserted\n31. Section 59I inserted\n32. Section 59J inserted\n33. Section 59K inserted\n34. Section 67 of Act 1038 amended\n35. Section 67A inserted\n36. Section 67B inserted\n37. Section 68 of Act 1038 amended\n38. Section 83 of Act 1038 amended\n39. Section 90 of Act 1038 amended\n40. Section 91 of Act 1038 amended\n41. Section 92 of Act 1038 amended\n42. Section 94 of Act 1038 amended\n43. Section 94A inserted\nCYBERSECURITY (AMENDMENT) BILL, 2025",
|
| 3 |
+
"39. Section 90 of Act 1038 amended\n40. Section 91 of Act 1038 amended\n41. Section 92 of Act 1038 amended\n42. Section 94 of Act 1038 amended\n43. Section 94A inserted\nCYBERSECURITY (AMENDMENT) BILL, 2025\n2 44. Section 94B inserted\n45. Section 97 of Act 1038 amended\n46. Section 98 of Act 1058 amended\n47. Section 99 of Act 1038 amended\n48. First Schedule to Act 1038 amended\n49. Second schedule to Act 1038 amended\n50. Third schedule to Act 1038 amended\nCYBERSECURITY (AMENDMENT) BILL, 2025\n3 A\nBILL\nENTITLED\nCYBERSECURITY (AMENDMENT) BILL, 2025\nAN ACT to amend the Cybersecurity Act, 2020 (Act 1038) to confer powers on the Cyber\nSecurity Authority to investigate and prosecute cybercrime on the authority of the\nAttorney -General and recover proceeds of cyber crime ; to revise the object and\nfunctions of the Cyber Security Authority; to revise the governance and\nadministration of the Cyber Security Authority ; to revise the enforcement powers of\nthe Cyber Security Authority and to provide for related matters.\nSection 3 of Act 10 38 Amended\n1. The Cybersecurity Act, 2020 (Act 1038) referred to in the Act as the \u201cprincipal\nenactment\u201d is amended by substitution for section 3, of\n\u201cObjects of the Authority\n3. The objects of the Authority are to:\n(a) regulate cybersecurity activities in the country;\n(b) prevent, manage and respond to cybersecurity threats and\ncybersecurity incidents ;\n(c) regulate owners of critical information infrastructure in respect of",
|
| 4 |
+
"3. The objects of the Authority are to:\n(a) regulate cybersecurity activities in the country;\n(b) prevent, manage and respond to cybersecurity threats and\ncybersecurity incidents ;\n(c) regulate owners of critical information infrastructure in respect of\ncybersecurity activities, cybersecurity service providers , cybersecurity\nprofessionals and practitioners, and cybersecurity establishments in\nthe country;\n(d) promote the development of cybersecurity in the country to ensure a\nsecured and resilient digital ecosystem;\n(e) prevent and detect cybercrime;\n(f) to facilitate the confiscation of proceeds of cybercrime;\n(g) establish a platform for cross -sector engagement on matters of\ncybersecurity for effective co -ordination and co -operation between\nkey public institutions and the private sector;\n(h) create awareness of cybersecurity matters; an d\n(i) collaborate with States, inter -governmental bodies, international\norganisations and international agencies to promote the cybersecurity\nof the country .\u201d.\nSection 4A inserted\n2. The principal enactment is amended by the insertion after section 4, of\n\u201cFurther Functions of the Authority\nCYBERSECURITY (AMENDMENT) BILL, 2025\n4 4A. The Authority , shall in addition to the functions in section 4, carry out the\nfollowing functions:\n(a) subject to article 88 of the Constitution, to investigate and on the\nauthority of the Attorney -General , prosecute cybercrime under this\nAct;\n(b) establish standards for certifying the security of innovative products ,\nArtificial Intelligence, cloud technology, quantum computing, big",
|
| 5 |
+
"following functions:\n(a) subject to article 88 of the Constitution, to investigate and on the\nauthority of the Attorney -General , prosecute cybercrime under this\nAct;\n(b) establish standards for certifying the security of innovative products ,\nArtificial Intelligence, cloud technology, quantum computing, big\ndata, Internet of Things (IoT) , blockchain -based technology and any\nother emerging technologies ;\n(c) certify the security of innovative products Artificial Intelligence,\ncloud technology, quantum computing, big data, Internet of Things\n(IoT) , blockchain -based technology and any other emerging\ntechnologies , in accordance with the standards established pursuant\nto paragraph ( b);\n(d) accredit the cybersecurity establishments of critical information\ninfrastructure owners, cybersecurity service providers, cybersecurity\npractitioners and professionals and other relevant person s or\ninstitutions ;\n(e) accredit non -profit cybersecurity institutions and cybersecurity\nprofessional bodies ;\n(f) promote the online protection of women, elderly , persons with\ndisabilities and underserved populations ;\n(g) collaborate with relevant institutions to develop mechanisms\nincluding technical security solutions and guidelines on the usage of\nsmart technology and other emerging technologies ; and\n(h) promote the protection of digital right s as it relates to cyber security. \u201d.\nSection 5 of Act 10 38 amended\n3. The principal enactment is amended in section 5 by the addition after subparagraph\n(iv) of paragraph (a) of subsection (1), of\n\u201c(v) Foreign Affairs and Regional Integration ; and\n(vi) Gender, Children and Social Protection .\u201d.\nSection 13 of Act 10 38 amended\n4. The principal enactment is amended in section 13 by:",
|
| 6 |
+
"(iv) of paragraph (a) of subsection (1), of\n\u201c(v) Foreign Affairs and Regional Integration ; and\n(vi) Gender, Children and Social Protection .\u201d.\nSection 13 of Act 10 38 amended\n4. The principal enactment is amended in section 13 by:\n(a) the substitution for paragraph (d) of subsection (2), of\n\u201c(d) the Executive Director of the Data Protection Commission or any\nsuccessor official or entity by whatever name designated, or a\nrepresentative of the Executive Director with the requisite\nknowledge and skills in cybercrime and cybersecurity matters,\nnominated by the Executive Director ;\u201d;\nCYBERSECURITY (AMENDMENT) BILL, 2025\n5 (b) the substitution for paragraph (g) and (k) respectively of subsection (2), of\n\u201c(g) the Director of the National Intelligence Bureau or any successor\nofficial or entity by whatever name designated, or a representative of\nthe Director with the requisite knowledge and skills in cybercrime ,\nand cybersecurity matters, nominated by the Director;\n(k) the Director General of National Signals Bureau or any successor\nofficial or entity by whatever name designated, or a representative of\nthe Director with the requisite knowledge and skills in cybercrime and\ncybersecurity matters, nominated by the Director; \u201d;\n(c) the addition after paragraph (r) of subsection (2), o f\n\u201c(s) Any other person the Minister may nominate, on the advice of the\nAuthority. \u201d.\n(d) the addition after subsection (13), of\n\u201c(14) A member of the JCC who is absent from three consecutive meetings",
|
| 7 |
+
"\u201c(s) Any other person the Minister may nominate, on the advice of the\nAuthority. \u201d.\n(d) the addition after subsection (13), of\n\u201c(14) A member of the JCC who is absent from three consecutive meetings\nwithout sufficient cause ceases to be a member of the JCC .\n(15) Where there is a vacancy, the Minister shall notify the President of\nthe vacancy , and the President shall appoint a person to fill the v acancy for\nthe unexpired term. \u201d.\nSection 1 4 of Act 10 38 amended\n5. The principal enactment is amended in section 14 by the substitution for subsection\n(1), of\n\u201c14 (1). The Joint Cybersecurity Committee shall collaborate with the Authority and\nthe sectors or institutions represented on the Committee for the implementation of\nrelevant cybersecurity measures including but not limited to the following ways:\n(a) identification of cybersecurity risks that may affect critical information\ninfrastructure or the overall cybersecurity of the country ;\n(b) development of cybersecurity policies and guidelines based on identified\nrisks, best practices and international standards to mitigate cybersecurity\nrisks;\n(c) coordination of cybersecurity activities between sectors and entities to\nensure that all cybersecurity efforts are aligned to prevent overlap and gaps;\n(d) facilitate the sharing of information between sectors and entities in a secure\nmanner and in compliance with relevant data protection laws ;\n(e) collaboration with sectors or institutions to tackle cybercrime including the\nprovision of a platform for incidence sharing and incidence response\nmechanisms to facilitate ;\nCYBERSECURITY (AMENDMENT) BILL, 2025",
|
| 8 |
+
"manner and in compliance with relevant data protection laws ;\n(e) collaboration with sectors or institutions to tackle cybercrime including the\nprovision of a platform for incidence sharing and incidence response\nmechanisms to facilitate ;\nCYBERSECURITY (AMENDMENT) BILL, 2025\n6 (f) evaluation of the effectiveness of implemented cybersecurity measures based\non established metrics with a view to continuously improve the cybersecurity\nmeasures ; and\n(g) collaboration with the Authority to educate the public on matters related to\ncybercrime and cybersecurity. \u201d.\nSection 15 A inserted\n6. The principal enactment is amended by the insertion after section 15, of\n\u201cAppointment of Deputy -Director s General\n15A. (1) The President shall in accordance with article 195 of the Constitution\nappoint such Deputy Directors -General as are necessary for the performance of the\nfunctions of the Authority.\n(2) The Deputy Directors -General shall hold office on the terms and\nconditions specified in the letters of appointment.\n(3) The Deputy Directors -General shall act in the absence of the\nDirector -General and perform any other functions as determined by the Board.\n(4) The Deputy Directors -General shall, subject to the provisions of this\nAct:\n(a) assist the Director -General in the discharge of his functions and\nperform such other functions as the Director -General or the Board\nmay delegate to him;\n(b) perform functions related to the object and functions of the\nAuthority on the direction or authority of the Director -General or\nthe Board ; and\n(c) be responsible for the direction of the Authority when the Director -\nGeneral is absent from Ghana or is otherwise unable to perform his\nfunctions. \u201d.\nSection 20A inserted",
|
| 9 |
+
"Authority on the direction or authority of the Director -General or\nthe Board ; and\n(c) be responsible for the direction of the Authority when the Director -\nGeneral is absent from Ghana or is otherwise unable to perform his\nfunctions. \u201d.\nSection 20A inserted\n7. The principal enactment is amended by the insertion after section 20, of\n\u201cTerms and Conditions of Service of Staff of the Authority\n20A. (1) The terms and conditions of service of the staff of the Authority shall\nnot be less favourable than the staff of the security and intelligence agencies .\nCYBERSECURITY (AMENDMENT) BILL, 2025\n7 (2) The conditions of service attached to posts of legal officers of the\nAuthority shall not be less than that of the posts attached to that of legal officers of\nthe Attorney -General of the same rank.\n(3) Staff shall, in addition to monthly salaries, be eligible for the payment\nof any gratuity, allowance, pension, subsidy or benefit to members in respect of\ntheir service or resignation or retirement .\u201d.\nSection 20B inserted\n8. The principal enactment is amended by the insertion after section 20, of\n\u201cPowers of police\n20B. (1) The Director -General, Deputy Director -General and other\nauthorised officers shall exercise the powers of a Police Officer, including the\npowers of arrest, search and seizure and have the same rights, protections\nimmunities conferred on a Police officer in the Criminal and Other Offences\n(Procedure) Act, 1960 (Act 30), the Police Service Act, 1970 (Act 350) and any other\nlaw related to a Police officer in the performance of their functions under the Act. \u201d.\nSection 31 of Act 10 38 amended",
|
| 10 |
+
"(Procedure) Act, 1960 (Act 30), the Police Service Act, 1970 (Act 350) and any other\nlaw related to a Police officer in the performance of their functions under the Act. \u201d.\nSection 31 of Act 10 38 amended\n9. The principal enactment is amended by substitution for section 31, of :\n\u201cSources of money for the Fund\n31. The sources of moneys for the Fund include:\n(a) seed money approved by Parliament;\n(b) moneys which may become lawfully payable to the Authority for the\nFund;\n(c) 50% of all fines arising from criminal penalties under th e Act;\n(f) grants, gifts, donations and other voluntary contributions;\n(g) 12% of the communications service tax for the Fund per annum;\n(h) 9 % corporate tax for the Fund per annum;\n(i) a charge determined by the Authority in accordance with the Fees\nand Charges (Miscellaneous Provisions) Act, 2018 (Act 983) and\nlevied on persons licensed by the Bank of Ghana to carry on business;\n(j) a proportion of the fees charged on all government e lectronic\nservices determined by the Authority ; and\n(h) any other moneys approved by Parliament for the Fund. \u201d.\nSection 35 of Act 10 38 amended\n10. The principal enactment is amended by substitution for section 35, of\n\u201cDesignation of critical information infrastructure\nCYBERSECURITY (AMENDMENT) BILL, 2025\n8 35. (1) The Minister may, on the advice of the Authority, designate a\ncomputer system or computer network as a critical information infrastructure if the\nMinister considers that the computer system or computer network is essential for\n(a) national security,",
|
| 11 |
+
"CYBERSECURITY (AMENDMENT) BILL, 2025\n8 35. (1) The Minister may, on the advice of the Authority, designate a\ncomputer system or computer network as a critical information infrastructure if the\nMinister considers that the computer system or computer network is essential for\n(a) national security,\n(b) the economic and social well -being of citizens , or\n(c) public health and safety.\n(2) Where the Minister designates a computer system or computer\nnetwork as a critical information infrastructure, the Minister shall publish the sector of the\ndesignated critical information infrastructure in the Gazette and, the owner of the Critical\nInformation Infrastructure shall be notified of the designation.\n(3) The Minister shall, in making a determination under subsection (1),\nconsider if the computer system or computer network is necessary for :\n(a) the security, defence or international relations of the country;\n(b) the production, preservation or identity of a confidential source of\ninformation related to the enforcement of criminal law;\n(c) the provision of services directly related to\n(i) communications and telecommunications infrastructure;\n(ii) banking and financial services;\n(iii) public utilities\n(iv) public transportation; and\n(v) public key infrastructure;\n(d) the protection of public safety and public health, including\nsystems related to essential emergency services;\n(e) an international business or communication affecting a citizen of\nGhana or any other international business in which a citizen of\nGhana or the Government has an interest;\n(f) the Legislature, Executive, Judiciary, Public Services or security\nagencies ; or\n(g) digital services;\n(h) services related to the supply chain in the critical information\ninfrastructure ecosystem; and",
|
| 12 |
+
"Ghana or any other international business in which a citizen of\nGhana or the Government has an interest;\n(f) the Legislature, Executive, Judiciary, Public Services or security\nagencies ; or\n(g) digital services;\n(h) services related to the supply chain in the critical information\ninfrastructure ecosystem; and\n(i) any other services determined by the Minister, on the advice of the\nAuthority .\n(4) The Minister shall, by publication in the Gazette , estab lish the\nprocedure for the regulation of a critical information infrastructure. \u201d.\nSection 36 of Act 10 38 amended\n11. The principal enactment is amended by substitution for section 3 6, of\n\u201cRegistration of critical information in frastructure\nCYBERSECURITY (AMENDMENT) BILL, 2025\n9 36. (1) The Owner of the designated critical information infrastructure shall\nregister their critical information infrastructure with the Authority , and shall pay the\nprescribed annual registration and designation fee as determined by the Authority\nor stipulated in a legislative instrument .\n(2) For compliance purposes, the owner of an unregistered critical\ninformation infrastructure shall receive a notification of registration letter from the\nAuthority to comply with the Act.\n(3) During registration, the owner of a critical information infrastructure\nshall nominate a point of contact and submit details of the nominee to the\nAuthority.\n(4) The owner of a critical information infrastructure shall submit to the\nAuthority, verified details of critical information infrastructure, through the\nchannels and modalities determined by the Authority.\n(5) The Authority shall validate the information submitted by the owner\nof a critical information infrastructure, and the owner shall receive a Certificate of\nRegistration upon completion of registration requirements.\n(6) The Authority shall through the issuance of guidelines or directives\ndetermine :",
|
| 13 |
+
"(5) The Authority shall validate the information submitted by the owner\nof a critical information infrastructure, and the owner shall receive a Certificate of\nRegistration upon completion of registration requirements.\n(6) The Authority shall through the issuance of guidelines or directives\ndetermine :\n(a) further requirements for the registration of a critical information\ninfrastructure ;\n(b) the procedure for the registration of a critical information\ninfrastructure; and\n(c) any other matter relating to the registration of a critical information\ninfrastructure.\n(7) Where there is any change in the legal ownership of a designated\ncritical information infrastructure, the owner of the designated critical information\ninfrastructure shall , within seven days after the change, inform the Authority of the\nchange in ownership.\n(8) An owner of a designated critical information infrastructure who\ncontravenes subsectio n (1) and (3) is liable to pay to the Authority the administrative\npenalty specified in the Second Schedule. \u201d.\nSection 37 of Act 10 38 amended\n12. The principal enactment is amended by substitution for section 37 , of\n\u201cWithdrawal of designation of critical information infrastructure\n\u201c37. (1) The Minister may, on the advice of the Authority, withdraw the\ndesignation of a critical information infrastructure at any time if the Minister considers that\nCYBERSECURITY (AMENDMENT) BILL, 2025\n10 the computer or computer network no longer satisfies the criteria of a critical information\ninfrastructure .\n(2) The Minister may publish the withdrawal of critical information\ninfrastructure sector in the Gazette .\n(3) The owner of a designated critical information infrastructure shall\nreceive notification of the withdrawal of the designation by letter from the Minister. \u201d.\nSection 40 of Act 10 38 amended\n13. The principal enactment is amended in section 40 by substitution for subsection",
|
| 14 |
+
"infrastructure sector in the Gazette .\n(3) The owner of a designated critical information infrastructure shall\nreceive notification of the withdrawal of the designation by letter from the Minister. \u201d.\nSection 40 of Act 10 38 amended\n13. The principal enactment is amended in section 40 by substitution for subsection\n(2), of\n\u201c(2) A person who contravenes subsection (1) commits an offence and is liable\non summary conviction to a fine of not less than four thousand penalty units and not more\nthan twenty -five thousand penalty units , or both .\u201d.\nSection 47 of Act 10 38 Amended\n14. The principal enactment is amended in section 47 by:\n(a) the addition after subsection (6), of\n\u201c(7) A Sectoral Computer Emergency Response Team shall within twenty -four\nhours after receiving a report of a cybersecurity incident from the owner of a designated\ncritical information infrastructure, a service provider, a licensee or any relevant person\nreport the cybersecurity incident to the National Computer Emergency Response Team.\u201d.\n(b) the addition after subsection (7), of\n\u201c(8) A Sectoral Computer Emergency Response Team who contravenes\nsubsection (7) is liable to pay to the Authority the administrative penalty specified in the\nSecond Schedule.\u201d.\nSection 49 of Act 1038 Amended\n15. The principal enactment is amended in section 49 by\n(a) the substitution for subsection (1), of\n\u201c(1) A person shall not provide a cybersecurity service for a reward unless that\nperson obtains a licence issued by the Authority in accordance with the Act.\u201d.\n(b) the substitution for subsection (2), of",
|
| 15 |
+
"\u201c(1) A person shall not provide a cybersecurity service for a reward unless that\nperson obtains a licence issued by the Authority in accordance with the Act.\u201d.\n(b) the substitution for subsection (2), of\nCYBERSECURITY (AMENDMENT) BILL, 2025\n11 \u201c(2) A person shall not provide a cybersecurity service on a not -for-profit basis\nunless that person obtains an accreditation issued by the Authority in accordance with the\nAct.\u201d.\n(c) addition after subsection (2), of\n\u201c(3) A person who contravenes subsection (1) and (2) is liable to pay to the\nAuthority the administrative penalty the equivalent to the cost of damage caused and value\nof the financial gain made , or an administrative penalty of not less than fifty thousand\npenalty units and not more than one hundred thousand penalty units. \u201d.\nSection 57 of Act 1038 Amended\n16. The principal enactment is amended by the substitution for section 57, of\n(1) The Authority shall establish a mechanism for the accreditation of\ncybersecurity professionals and practitioners.\n(2) An individual shall not practise as a cybersecurity professional or\ncybersecurity practitioner unless that individual has been accredited by the Authority;\n(3) A person shall not knowingly engage the services of a cybersecurity\nprofessional or cybersecurity practitioner who has not been accredited by the Authorit y;\n(4) A person who contravenes subsection (2) or (3) is liable to pay to the\nAuthority an administrative penalty of not less than two hundred and fifty penalty units\nand not more than twenty thousand penalty units .\nSection 57A inserted",
|
| 16 |
+
"(4) A person who contravenes subsection (2) or (3) is liable to pay to the\nAuthority an administrative penalty of not less than two hundred and fifty penalty units\nand not more than twenty thousand penalty units .\nSection 57A inserted\n17. The principal enactment is amended by the insertion after section 57, of\n\u201cCyber hygiene certification scheme\n57B. (1) The Authority shall establish a scheme for the certification of cybersecurity\nprofessionals and practitioners, and cybersecurity service providers who will be entrusted\nto perform cyber hygiene certification service as an alternative to other international best\npractice security frameworks.\n(2) The Cyber Security Authority shall conduct periodic audits to ensure\ncompliance with the cyber hygiene certification scheme and associated prescribed fees.\n(3) Any complaints regarding non -compliance shall be investigated, and\nappropriate enforcement actions shall be taken. \u201d.\nSection 57B inserted\nCYBERSECURITY (AMENDMENT) BILL, 2025\n12 18. The principal enactment is amended by the insertion after section 58A, of\n\u201cApplication for certification to provide the Cyber hygiene certification services\n57B. (1) A licensed cybersecurity service provider or accredited CP who seeks to\nprovide the Authority\u2019s cyber hygiene certification service shall apply in writing to the\nAuthority.\n(2) The application shall be made in the prescribed form and accompanied by\nthe:\n(a) supporting documentation, and\n(b) prescribed fee that the Authority may determine .\u201d.\nSection 57C inserted\n19. The principal enactment is amended by the insertion after section 58B, of\n\u201cStandardised , Review and Administration of Fees for Cyber Hygiene Certification\nService\n57C. (1) A certified cybersecurity professional or practitioner or cybersecurity service",
|
| 17 |
+
"Section 57C inserted\n19. The principal enactment is amended by the insertion after section 58B, of\n\u201cStandardised , Review and Administration of Fees for Cyber Hygiene Certification\nService\n57C. (1) A certified cybersecurity professional or practitioner or cybersecurity service\nprovider shall charge a fee for cyber hygiene certification services as determined by the\nCyber Security Authority.\n(2) A certified cybersecurity professional or practitioner or cybersecurity service\nprovide r shall not charge fees exceeding the approved flat rate for cyber hygiene\ncertification services.\n(3) A certified cybersecurity professional or practitioner or cybersecurity service\nprovider who fails to comply wit h subsection (2) shall be liable to pay to the Authority an\nadministrative penalty specified in the Second Schedule (not less than two hundred and\nfifty penalty units and not more than five thousand penalty units)\n(4) 30% of the revenue generated by a certified cybersecurity professional or\npractitioner or cybersecurity service provider under the scheme shall be paid into the\ncybersecurity fund.\n(5) The Cyber Security Authority shall, by notice published in the Gazette,\ndetermine the applicable flat rate for cyber hygiene certification services.\n(6) The flat rate shall be reviewed periodically based on, industry trends, and the\naffordability needs of stakeholders. \u201d.\nSection 58A inserted\n20. The principal enactment is amended by the insertion after section 5 8, of\nCYBERSECURITY (AMENDMENT) BILL, 2025\n13 \u201cCertification of the security of innovative and emerging technologies.\n58A. The Authority shall establish a mechanism for the certification of the security of\ninnovative and emerging technologies. \u201d.\nSection 58B inserted",
|
| 18 |
+
"CYBERSECURITY (AMENDMENT) BILL, 2025\n13 \u201cCertification of the security of innovative and emerging technologies.\n58A. The Authority shall establish a mechanism for the certification of the security of\ninnovative and emerging technologies. \u201d.\nSection 58B inserted\n21. The principal enactment is amended by the insertion after section 58A, of\n\u201cAccreditation of non -profit cybersecurity institutions\n58B. The Authority shall establish a mechanism for the accreditation of non -profit\ncybersecurity institutions .\u201d.\nSection 59 of Act 10 38 Amended\n22. The principal enactment is amended in section 59 by the addition after paragraph\n(e) of subsection (1), of\n\u201c(g) certif ication of the security of innovative and emerging technologies including\nArtificial Intelligence, cloud technology, quantum computing, big data, and blockchain -\nbased technology. \u201d.\nSection 59A inserted\n23. The principal enactment is amended by the insertion after section 59, of\n\u201cEnforcement powers\n59A. (1) A person who:\n(a) knowingly fails to comply with or acts in contravention of this Act,\nregulations or directives issued under this Act ;\n(b) knowingly fails to comply with prescribed cybersecurity standards and\nrequirements ;\n(c) provides cybersecurity services without a licence or acts as a cybersecurity\nprofessional or practitioner without accreditation ;\n(d) willfully obstructs, hinders, molests or assaults personnel of the Authority\nduly engaged in the exercise of power conferred on the Authority under this\nAct.\ncommits an offence and is liable on summary conviction to a term of imprisonment\nof not more than five years or to a fine of not more than twenty thousand penalty",
|
| 19 |
+
"duly engaged in the exercise of power conferred on the Authority under this\nAct.\ncommits an offence and is liable on summary conviction to a term of imprisonment\nof not more than five years or to a fine of not more than twenty thousand penalty\nunits, or to both.\nCYBERSECURITY (AMENDMENT) BILL, 2025\n14 (1) Where an offence is committed by a corporate entity, that entity is liable to\na fine of not more than twenty thousand penalty units and each director of\nthat entity shall be deemed to have committed the offence.\n(2) Despite subsection (1), the Authority may, where a person has breached this\nAct, Regulations or directives or where a licensee, an accreditation holder\nhas breached a condition contained in its licence or accreditation :\n(a) warn the person, licensee, or accreditation holder,\n(b) issue a cease -and-desist order,\n(c) apply to the High Court for\n(i) an injunction to restrain the person, licensee, or accreditation\nholder from continuing the breach, or\n(ii) other appropriate order to enforce compliance with this Act,\n(d) propose amendments to the licence or accreditation in accordance\nwith this Act or Regulations\n(e) suspend or terminate the licence or accreditation in accordance with\nthis Act, or\n(f) impose administrative penalties in accordance with the second\nschedule of the Act\n(g) or take any other action that it considers appropriate and that is not\ncontrary to this Act.\n(4) Nothing shall prevent the Authority from pursuing only criminal\nenforcement actions under subsection (1) or only administration\nenforcement actions under subsection (2), or a combination of\nboth. \u201d.\nSection 59B inserted",
|
| 20 |
+
"contrary to this Act.\n(4) Nothing shall prevent the Authority from pursuing only criminal\nenforcement actions under subsection (1) or only administration\nenforcement actions under subsection (2), or a combination of\nboth. \u201d.\nSection 59B inserted\n24. The principal enactment is amended by the insertion after section 59A, of\n\u201cPower to conduct investigations and to prosecute cybercrime \u201d\n59B. (1) The Authority shall upon the occurrence of a cybersecurity incident or a\ncybercrime conduct criminal investigations and prosecute same .\u201d\n(2) The Authority shall have the jurisdiction to prosecute all offences under the\nElectronic Transactions Act, 2008 (Act 772).\n(3) Where a person is convicted of a cybercrime under this Act, the Authority\nmay apply to the Court for an order to confiscate moneys, proceeds, benefits, properties\nand assets purchased by a person with proceeds derived from or in the commission of the\ncybercrime.\nCYBERSECURITY (AMENDMENT) BILL, 2025\n15\n(4) Despite subsection (5), nothing shall prevent the Authority from instituting\na civil action independently or at the same time as the prosecution to recover moneys,\nproceeds, benefits, properties and assets purchased by a person with proceeds derived from\nor in the commission of the cybercrime.\n(5) In relation to subsection (6) if criminal prosecution fails, civil asset recovery\nshould still proceed, and confiscation orders should have the effect of a civil judgment\nappealable from the High Court to the Court of Appeal. \u201d.\n(6) Where the Authority considers that freezing of property is necessary to\nfacilitate an investigation or prosecution, the Authority shall in writing direct the freezing",
|
| 21 |
+
"should still proceed, and confiscation orders should have the effect of a civil judgment\nappealable from the High Court to the Court of Appeal. \u201d.\n(6) Where the Authority considers that freezing of property is necessary to\nfacilitate an investigation or prosecution, the Authority shall in writing direct the freezing\nof:\n(a) the property of a person or entity being investigated; or\n(b) specified property held by a person or entity other than the person\nor entity being investigated or prosecuted.\n(7) The Authority shall within 14 days after the freezing of the property apply\nto the Court for a confirmation of the freezing.\nSection 59 C inserted\n25. The principal enactment is amended by the insertion after section 59 B, of\n\u201cPower to request for information\n59C. (1) The Authority may, for the purposes of carrying out an investigation in\nrespect of a contravention of the Act, Regulations or any other relevant enactment by the\nowner of a critical information infrastructure, a licensee, a service provider or any other\nperson, by notice in writing, require a person to :\n(a) attend at a time and place specified in the notice; and\n(b) furnish the Authority with information related to a matter relevant to the\ninvestigation.\n(2) A notice under subsection (1) shall indicate the subject matter and purpose\nof the request. \u201d.\n(3) Where a person required to furnish the Authority with an information is\nunder an obligation not to disclose, or asserts a right not to disclose, the Authority shall\napply to the High Court for an order for the production of the information.\n(4) Where an information is furnished to the Authority, the Authority shall\nmake copie s or extract from the Information and request the person",
|
| 22 |
+
"apply to the High Court for an order for the production of the information.\n(4) Where an information is furnished to the Authority, the Authority shall\nmake copie s or extract from the Information and request the person\nCYBERSECURITY (AMENDMENT) BILL, 2025\n16 producing the information to provide an ex planation on the content of the\ninformation where necessary.\n(5) A person who appears before the Authority may be represented by a Counsel\nof the choice of that person at any stage of the process .\n(6) Except as provided in subsection (2) where a person contravenes subsection (1)\n(a) and (b), that person commits an offence and is liable on summary conviction to a fine\nof not less than two hundred and fifty penalty units and not more than ten thousand penalty\nunits or to a term of imprisonment of not less than one year and not more than two years\nor to both. \u201d.\nSection 59D inserted\n26. The principal enactment is amended by the insertion after section 59C, of\n\u201cApplication for Production Order to Collect Computer Data\n59D. (1) An investigative officer may apply ex-parte to the High Court for a\nproduction order to collect computer data.\n(2) A production order under section (1) shall require a person in possession or\ncontrol of a computer or computer system to submit specified computer data which is\nstored in the computer or computer system to the investigative officer.\n(3) An investigative officer who makes an application under section (1) shall\ndemonstrate to the satisfaction of the Court that there are reasonable grounds to believe\nthat the computer data is reasonably required for the purposes of a specific criminal\ninvest igation.",
|
| 23 |
+
"stored in the computer or computer system to the investigative officer.\n(3) An investigative officer who makes an application under section (1) shall\ndemonstrate to the satisfaction of the Court that there are reasonable grounds to believe\nthat the computer data is reasonably required for the purposes of a specific criminal\ninvest igation.\n(4) Where an investigative officer makes an application under section (1), that\ninvestigative officer shall :\n(a) explain why the investigative officer believes the computer data sought, will\nbe available to the person in control of the computer or computer system;\n(b) identify and explain with specificity the type of computer data suspected to\nbe found on the computer or computer system;\n(c) identify and explain with specificity the computer data that may be found on\na computer or computer system that is the subject of an investigation or\nprosecution;\n(d) identify and explain with specificity the offences in respect of which the\nproduction order is sought; and\n(e) indicate what measures shall be taken to ensure that the computer data will\nbe procured\nCYBERSECURITY (AMENDMENT) BILL, 2025\n17\n(i) whilst maintaining the privacy of other users, customers and third\nparties, and\n(ii) without the disclosure of the computer data of any party not part of\nthe investigation. \u201d.\nSection 59 E inserted\n27. The principal enactment is amended by the insertion after section 59 D, of\n\u201cGrant of production order\n59E. (1) The High Court may grant an application for a production order\nunder subsection (1) of section 59D, if the Court is satisfied that\n(a) the investigative officer has complied with subsection (3) and (4) of section\n59D;",
|
| 24 |
+
"59E. (1) The High Court may grant an application for a production order\nunder subsection (1) of section 59D, if the Court is satisfied that\n(a) the investigative officer has complied with subsection (3) and (4) of section\n59D;\n(b) the information requested is commensurate, proportionate and necessary for\nthe purposes of a specific criminal investigation or prosecution;\n(c) measures shall be taken to ensure that the computer data is produced whilst\nmaintaining the privacy of other users, customers and third parties and\nwithout the disclosure of information and data of any party not part of the\ninvestigation; and\n(d) the investigation may be frustrated or seriously prejudiced unless the\nproduction of the information is permitted.\n(2) The Court shall require the service provider to keep confidential the\nproduction order and the execution of the production order under this\nsection.\n(3) A production order granted under this Act shall be served on a domestic\nservice provider, foreign service provider or both, or any other relevant\nperson. \u201d.\nSection 59F inserted\n28. The principal enactment is amended by the insertion after section 59E, of\n\u201cApplication for Warrant to Access, Search and Seize Computer Data, Computer or\nComputer System\n59F. (1) An investigative officer may apply ex -parte to the High Court for a warrant to :\n(a) access data on a computer or computer system;\n(b) search and seize a computer or a computer system; or\nCYBERSECURITY (AMENDMENT) BILL, 2025\n18 (c) make or retain a copy of computer data on a computer or computer system;\n(d) maintain the integrity of computer data on a computer or computer system;",
|
| 25 |
+
"(b) search and seize a computer or a computer system; or\nCYBERSECURITY (AMENDMENT) BILL, 2025\n18 (c) make or retain a copy of computer data on a computer or computer system;\n(d) maintain the integrity of computer data on a computer or computer system;\n(e) remove or render inaccessible computer data on a computer or computer\nsystem\nthat is suspected to contain information relevant to an investigation.\n(2) A warrant under subse ction (1) shall require a person who has knowledge\nabout the functioning of the computer or computer system to provide the\nnecessary information to enable an investigative officer undertake the actions\nrequired under paragraphs (a) to (e) of subsection (1).\n(3) An investigative officer who makes an application under subsection (1) shall\ndemonstrate to the satisfaction of the court that there are reasonable grounds\nto believe that the warrant is reasonably required for the purposes of a specific\ncriminal investiga tion.\n(4) A warrant under subsection (1) shall permit a law enforcement officer to\nexpeditiously extend the search for data from a computer or computer system\nto another computer or computer system or part of it in Ghana where the law\nenforcement officer has reasonable grounds to believe that the data sought is\naccessible from or available to that other computer or computer system or\npart of it in Ghana .\u201d.\nSection 59G inserted\n29. The principal enactment is amended by the insertion after section 59F, of\n\u201cGrant of warrant\n59G. (1) The High Court may grant an application for a warrant under subsection 1\nof section 59F if the Court is satisfied that\n(a) the investigative officer has complied with subsection (3) of section 59F.",
|
| 26 |
+
"\u201cGrant of warrant\n59G. (1) The High Court may grant an application for a warrant under subsection 1\nof section 59F if the Court is satisfied that\n(a) the investigative officer has complied with subsection (3) of section 59F.\n(b) the information requested is commensurate, proportionate and necessary\nfor the purposes of a specific criminal investigation or prosecution;\n(c) measures shall be taken to ensure that the computer data is produced whilst\nmaintaining the privacy of other users, customers and third parties and\nwithout the disclosure of information and data of any party not part of the\ninvestigation; and\n(d) the investigation may be frustrated or seriously prejudiced unless the\nproduction of the information is permitted.\nCYBERSECURITY (AMENDMENT) BILL, 2025\n19\n(2) The Court shall require the service provider to keep confidential the\nwarrant and the execution of the warrant under this section.\n(3) A warrant granted under this section shall be served on a domestic service\nprovider, foreign service provider or both, or any other relevant person. \u201d.\nSection 59H in serted\n30. The principal enactment is amended by the insertion after section 59G, of\n\u201cApplication for preservation order to preserve computer data\n(1) A provider of wire or electronic communication services or a remote\ncomputing service on the written request of a law enforcement agency, shall take the\nnecessary steps to preserve records and other evidence in its possession pending the issue\nof a Court o rder and shall take steps to ensure that the request by the law enforcement\nagency is not disclosed to third parties during the period.\n(2) Where an order from the Court is not obtained and served for fourteen days",
|
| 27 |
+
"of a Court o rder and shall take steps to ensure that the request by the law enforcement\nagency is not disclosed to third parties during the period.\n(2) Where an order from the Court is not obtained and served for fourteen days\nafter the receipt of the written request, the wire or electronic communication services, or\nremote computing service provider is not under any obligation to preserve the evidence.\n(3) An investigative officer authorised by a designated officer may apply ex-parte\nto the Court for an order to require a service provider or any person in control of a\ncomputer or computer system to preserve specified computer data, including traffic data\nthat has been stored by a computer system.\n(4) An investigative officer who makes an application under subsection (1) shall\ndemonstrate to the satisfaction of the Court that the computer data is particularly\nvulnerable to loss or modification. \u201d.\nSection 59I\n31. The principal enactment is amended by the insertion after section 59H, of\n\u201cGrant of preservation order\n59I. (1) The High Court may grant an application for a preservation order under\nsubsection (1) of section 59F if the Court is satisfied that :\n(a) the designated officer has complied with subsection (2) of section 59H;\n(b) the extent of the preservation order is commensurate, proportionate and\nnecessary for the purposes of a specific criminal investigation or\nprosecution;\nCYBERSECURITY (AMENDMENT) BILL, 2025\n20 (c) measures shall be taken to ensure that the computer data is preserved whilst\nmaintaining the privacy of other users, customers and third parties and\nwithout the disclosure of information and data of any party not part of the\ninvestigation; and\n(d) the investigation may be frustrated or seriously prejudiced unless the",
|
| 28 |
+
"20 (c) measures shall be taken to ensure that the computer data is preserved whilst\nmaintaining the privacy of other users, customers and third parties and\nwithout the disclosure of information and data of any party not part of the\ninvestigation; and\n(d) the investigation may be frustrated or seriously prejudiced unless the\npreservation order is permitted.\n(2) The preservation order issued under subsection (1)\n(a) is valid for a period of ninety days, and\n(b) may be renewed for a further period of ninety days upon application to the\nCourt.\n(3) The Court shall require the service provider to keep confidential a\npreservation order issued under this regulation. \u201d.\nSection 59J\n32. The principal enactment is amended by the insertion after section 59I, of\n\u201cPower of entry, inspection and audit\n59J. (1) The Authority may designate a person as an inspector to inspect premises.\n(2) A person designated as an inspector by the Authority may\n(a) obtain a warrant to :\n(i) enter and inspect premises; and\n(ii) conduct an audit of a computer system\n(b) at any reasonable time, enter inspect premises and conduct and audit of a\ncomputer system without a warrant where the inspector\n(i) reasonably believes that an inspection of the premises is necessary to\nensure compliance by the owner of a critical information infrastructure,\na licensee or a service provider or any other relevant person with the\nAct or Regulations; and\n(ii) has given the occupier of the premises seven days' notice in writing of\nthe intention to enter , inspect the premises and conduct an audit of a\ncomputer system .\n(3) An inspector shall, before exercising a power of entry and inspection,",
|
| 29 |
+
"Act or Regulations; and\n(ii) has given the occupier of the premises seven days' notice in writing of\nthe intention to enter , inspect the premises and conduct an audit of a\ncomputer system .\n(3) An inspector shall, before exercising a power of entry and inspection,\nCYBERSECURITY (AMENDMENT) BILL, 2025\n21 (a) produce evidence of the identity of the inspector; and\n(b) indicate the purpose of entry and inspection.\n(4) An inspector may\n(a) enter a premises;\n(b) inspect the premises;\n(c) inspect a document or equipment found on the premises;\n(d) require a person on the premises to provide any information, or produce any\ndocument in the possession or control of that person that the inspector\nconsiders relevant to ensure compliance with the Act and Regulation and\n(e) require any person on the premises to provide information in respect of a\ndocument or the location of a document.\n(4) For the purpose of this regulation, \u201cpremises\u201d does not include domestic\npremises. \u201d.\nSection 59K inserted\n33. The principal enactment is amended by the insertion after section 59J, of\n\u201cWitness and Inform ant Protection\n59K. (1) Except with the written, informed, explicit and freely given consent of a\nwitness or inform ant, no witness or inform ant who discloses information relating to\ncybercrime under this Act or administrative infractions under this Act, shall be obliged to\ndisclose his name or address or other personal information , or state any matter which\nmight lead to the ir discovery.\n(2) Unless otherwise provided, t he information and the identity of the witness\nor inform ant shall be held to be secret between the Authority and the witness or informer;",
|
| 30 |
+
"disclose his name or address or other personal information , or state any matter which\nmight lead to the ir discovery.\n(2) Unless otherwise provided, t he information and the identity of the witness\nor inform ant shall be held to be secret between the Authority and the witness or informer;\nand all matters relating to such information should be privileged and sh all not be disclosed\nin any proceedings before any court, tribunal, commission, or authority.\n(3) The Authority take all necessary and reasonable steps to protect the safety\nand welfare of a witness or informant , and such t he protection shall extend to persons by\nvirtue of being related to a witness or info rmant, or on account of a testimony given by a\nwitness or informer , or for any other reason upon which the Authority may consider\nsufficient. \u201d.\nSection 67 of Act 10 38 amended\n34. The principal enactment is amended by :\nCYBERSECURITY (AMENDMENT) BILL, 2025\n22 (a) the substitution for the heading before section 67 \u201cOther Online Sexual\nOffences, of\n\u201cOther cybercrime\u201d.\n(b) the substitution for subsection (2) of section 67 , of\n\u201c(2) A person who contravenes subsection (1) commits an offence and is liable\non summary conviction to a term of imprisonment of not less than three years and not\nmore than ten years or a fine of not less than two hundred and fifty penalty units and not\nmore than twenty -five thousand penalty units or to both.\u201d.\nSection 67A inserted\n35. The principal enactment is amended by the insertion after section 67, of\n\u201cCyberbullying and online harassment\n67A. (1) A person shall not use the internet or any form of electronic medium",
|
| 31 |
+
"more than twenty -five thousand penalty units or to both.\u201d.\nSection 67A inserted\n35. The principal enactment is amended by the insertion after section 67, of\n\u201cCyberbullying and online harassment\n67A. (1) A person shall not use the internet or any form of electronic medium\nor technology to bully a child or an adult.\n(2) A person shall not use a computer or any other electronic medium to\n(a) send a threatening message or a lewd message to a child;\n(b) make sexual advances towards a child; or\n(c) persistently make contact with\n(i) a child, whether or not the contact is acceptable; or\n(ii) another person where the contact is unacceptable.\n(3) A person shall not create a false identity online or a fake social media\nprofile for the purposes of following\n(a) a child, whether or not the contact is acceptable; or\n(b) an adult, where the contact is unacceptable.\n(4) A person shall not use any online computer service or any other\nelectronic device to:\n(a) track the location of a child;\n(b) monitor the real -world activities of a child;\n(c) track the location of another person without the consent of that\nperson;\nCYBERSECURITY (AMENDMENT) BILL, 2025\n23 (d) monitor the real -world activities of another person without the\nconsent of that person; or\n(e) obsessively track the location of\n(i) a child; or\n(ii) another person without the consent of that person.\n(5) Despite subsection (4), a parent or legal guardian may for the",
|
| 32 |
+
"consent of that person; or\n(e) obsessively track the location of\n(i) a child; or\n(ii) another person without the consent of that person.\n(5) Despite subsection (4), a parent or legal guardian may for the\npurposes of ensuring the safety of a child, track the location of the child, or monitor\nthe real -world activities of the child.\n(6) A person shall not use an electronic device or medium to send\n(a) an unwanted,\n(b) an unsolicited,\n(c) a frightening,\n(d) an obscene,\n(e) a harassing, or\n(f) a threatening electronic mail, text message or instant message to a\nchild or an adult.\n(7) A service provider of a user -to-user service including\n(a) a chatroom,\n(b) a social network site,\n(c) an online gaming platform, or\n(d) a virtual, augmented and mixed reality shall protect a child from all\nforms of online violence and cyberbullying from users of the user -\nto-user service.\n(8) A person shall not use an electronic device or an electronic medium\nto deliberately spread false or misleading information with the intent\nto deceive or manipulate a person or a fact.\n(9) A person who contravenes subsections (1), (2), (3), (4) or (6) commits\nan offence and is liable on summary conviction to a fine of not less\nthan two thousand, five hundred penalty units and not more than\nfive thousand penalty units or to a term of impriso nment of not less",
|
| 33 |
+
"an offence and is liable on summary conviction to a fine of not less\nthan two thousand, five hundred penalty units and not more than\nfive thousand penalty units or to a term of impriso nment of not less\nthan one year and not more than three years or to both.\nCYBERSECURITY (AMENDMENT) BILL, 2025\n24 (10) A person who contravenes sub regulation (7) or (8) commits an\noffence and is liable on summary conviction to a fine of not less than\nfive thousand penalty units and not more than twenty -five thousand\npenalty units or to a term of imprisonment of not less than three\nyears and not more than five years or to both.\n(11) For the purpose of this Act, \u201ccyberbullying\u201d includes any digital\ncommunication and any other activity which strips the recipient of\nthe dignity of the recipient, or causes fear or physical or emotional\nharm such as\n(a) repeatedly sending offensive, rude and insulting messages to\na person;\n(b) distributing derogatory information about a child or any other\nperson;\n(c) posting or sending offensive photos of a child or any other\nperson, whether or not the photos\n(i) have been digitally altered, or\n(ii) were taken with the consent of the victim, with the\nintention to humiliate or embarrass the victim;\n(d) breaking into an electronic mail, social networking or any\nelectronic account and using the virtual identity of a victim to\nsend, upload or distribute embarrassing materials to or about\nothers;\n(e) sharing sensitive personal information or any embarrassing\ninformation, or tricking a child or any person to reveal\npersonal or embarrassing information about that child or any\nother person and sharing the information obtained with\nothers;",
|
| 34 |
+
"send, upload or distribute embarrassing materials to or about\nothers;\n(e) sharing sensitive personal information or any embarrassing\ninformation, or tricking a child or any person to reveal\npersonal or embarrassing information about that child or any\nother person and sharing the information obtained with\nothers;\n(f) repeatedly sending threatening or intimidating messages with\nthreats of harm, or engaging in online activities that cause fear\nin a child or puts any other person in fear;\n(g) sending messages to another person telling the person to\ncommit suicide;\n(h) sending grossly offensive, indecent or obscene\ncommunication with the intention of causing emotional\ndistress to a child or any other person; or\n(i) sending electronic messages that denigrate a child or any other\nperson on the basis of\n(i) colour;\nCYBERSECURITY (AMENDMENT) BILL, 2025\n25 (ii) race;\n(iii) ethnic or national origins;\n(iv) religion ;\n(v) gender; or\n(vi) disability. \u201d.\nSection 67B inserted\n36. The principal enactment is amended by the insertion after section 67A, of\n\u201cCyberstalking of a person\n\u201c67B. (1) In furtherance of section 65 of the Act, a person shall not use the\ninternet, chatroom , a social network site, an online gaming platform, or a virtual,\naugmented and mixed reality to\n(a) harass or stalk a person ;\n(b) assume a false identity to anonymously harass or stalk a person;\n(c) create false information about a person; or\n(d) post or disseminate false information about a person.\n(2) A person who contravenes subsection (1) commits an offence and is liable",
|
| 35 |
+
"(c) create false information about a person; or\n(d) post or disseminate false information about a person.\n(2) A person who contravenes subsection (1) commits an offence and is liable\non summary conviction to a term of imprisonment of not less than one year and not more\nthan ten years or a fine of less than one hundred penalty units and not more than ten\nthousand penalty units or to both.\u201d.\nSection 68 of Act 10 38 amended\n37. The principal enactment is amended in section 68 by substitution for subsection\n(2), of\n\u201c(2) A person who contravenes subsection (1) commits an offence and is liable\non summary conviction to a term of imprisonment of not less than three years and not\nmore than ten years or a fine of less than two hundred and fifty penalty units and not more\nthan twenty -five thousand penalty units or to both.\u201d.\nSection 83 of Act 1038 amended\n38. The principal enactment is amended in section 83 by:\n(a) the substitution for paragraph (e) of subsection (4), of\n\u201c(e) requesting for information regarding domain name registration ;\u201d.\n(b) the addition after paragraph (e) of subsection (4) , of\nCYBERSECURITY (AMENDMENT) BILL, 2025\n26 \u201c(f) expedited disclosure of specified stored computer data in a service provider's\npossession or control in an emergency situation without a request for mutual\nassistance ;\u201d.\n(c) the addition after paragraph (f) of subsection (4), of\n\u201c(g) any other matter related to paragraphs (a) to ( f).\u201d.",
|
| 36 |
+
"possession or control in an emergency situation without a request for mutual\nassistance ;\u201d.\n(c) the addition after paragraph (f) of subsection (4), of\n\u201c(g) any other matter related to paragraphs (a) to ( f).\u201d.\n(d) the addition after paragraph (g) of subsection (4), of\n\u201c(5) For the purpose of this section an emergency means, a situation in which\nthere is a significant and imminent risk to the life or safety of any natural person. \u201d.\nSection 90 of Act 10 38 amended\n39. The principal enactment is amended in section 90 by the addition after subsection\n(2), of\n\u201c(3) Despite section 90(1) and section 90A , the Circuit Court shall have the\njurisdiction to try an offence under sections 62 to 68 A of this Act. \u201d.\nSection 91 of Act 10 38 amended\n40. The principal enactment is amended in section 91 by the addition after paragraph\n(h), of\n\u201c(i) the designation of Sectoral Computer Emergency Response Team;\n(j) the accreditation of Sectoral Computer Emergency Response Team;\n(k) the accreditation of cybersecurity establishments ;\n(l) the accreditation of non -profit cybersecurity institution ;\n(m) the protection of children ;\n(n) safeguarding the development and deployment of innovative and emerging\ntechnologies or solutions ;\n(o) licensing of cyber security service providers ;\n(p) accreditation of cybersecurity professionals and practitioners;\n(p) any other matter required for ensuring the cybersecurity of the country .\u201d.\nSection 92 of Act 10 38 amended\n41. The principal enactment is amended in section 92 by :",
|
| 37 |
+
"(p) accreditation of cybersecurity professionals and practitioners;\n(p) any other matter required for ensuring the cybersecurity of the country .\u201d.\nSection 92 of Act 10 38 amended\n41. The principal enactment is amended in section 92 by :\n(a) the substitution for subsection (1), of\n\u201c(1) the Authority may issue directives to an owner of a critical information\ninfrastructure, a cybersecurity service provider or service provider , innovators, developers\nfor the purpose of ensuring the cybersecurity of the country.\n(b) the substitution for subsection (2), of\nCYBERSECURITY (AMENDMENT) BILL, 2025\n27 \u201c(2) An owner of a critical information infrastructure, cybersecurity service\nprovider or service provider, innovator, developer or any relevant person who fails to\ncomply with the directives issued under this section is liable to pay to the Authority the\nadmin istrative penalty specified in the Second Schedule .\u201d.\n(c) the insertion after subsection (2) , of\n\u201c(3) The Authority may issue directives to innovators, developers, service\nproviders and cybersecurity service providers for the purpose of securing the adoption and\ndeployment of innovative and emerging technologies or solutions.\u201d.\nSection 94 of Act 10 38 amended\n42. The principal enactment is amended by the substitution for section 94, of\n\u201c(1) A person who, without lawful authority accesses or retrieves subscriber\ninformation or intercepts traffic data or content data, commits an offence and is liable on\nsummary conviction to a fine of not less than two thousand five hundred penalty units and",
|
| 38 |
+
"\u201c(1) A person who, without lawful authority accesses or retrieves subscriber\ninformation or intercepts traffic data or content data, commits an offence and is liable on\nsummary conviction to a fine of not less than two thousand five hundred penalty units and\nnot more than fifteen thousand penalty units or to a term of imprisonment of not less than\ntwo years and not more than five years or, to both.\n(2) An attempt to gain access to a critical information infrastructure or its\ndependencies, successful or not, constitutes an unlawful access to the critical information\ninfrastructure.\n(3) Any person who attempts to secure access to a critical information\ninfrastructure or its dependencies, successful or not commits an offence and is liable on\nsummary conviction to a term of imprisonment of not less than two years and not more\nthan five years or a fine of not less than four thousand penalty units and not more than\ntwenty -five thousand penalty units or to both.\n(4) The tampering and or destruction of critical information infrastructure or its\ncomponents which affects the confidentiality, integrity or availability of the critical\ninformation infrastructure constitutes unlawful access to critical information infrastructure ,\nand a person shall be subject to the same criminal penalties specified in subsection (1). \u201d.\nSection 94A inserted\n43. The principal enactment is amended by the insertion after section 94, of\n\u201cComputer related Forgery\n95A. (1) A person shall not intentionally and without right, input, alter, delete\nor suppress computer data resulting in inauthentic data with the intent that they be\nconsidered or acted upon for legal purposes as if they were authentic, regardless of whether\nor no t the data are directly readable and intelligible.\u201d",
|
| 39 |
+
"or suppress computer data resulting in inauthentic data with the intent that they be\nconsidered or acted upon for legal purposes as if they were authentic, regardless of whether\nor no t the data are directly readable and intelligible.\u201d\n(2) A person who contravenes subsection (1) commits an offence and\nshall be deemed to have committed the offence of forger y under Chapter Two of the\nCYBERSECURITY (AMENDMENT) BILL, 2025\n28 Criminal Offences Act, 1960 (Act 29) , and shall liable on summary conviction to the same\npunishment for that particular kind of forgery under the law. \u201d.\nSection 94B inserted\n44. The principal enactment is amended by the inser tion after section 94A, of\n\u201cComputer related Fraud\n95B. (1) A person shall not intentionally and without right, cause the loss of\nproperty to another person by means of:\n(a) Any input, alteration, deletion or suppression of computer data;\n(b) Any interference with the functioning of a computer system;\n(c) Any deception as to factual circumstances made through a computer\nsystem that causes a person to do or omit to do anything which that person would not\notherwise do or omit to do; with the fraudulent or dishonest intent of procuring for oneself\nor for another person, without right, a gain in money or other property .\n(2) A person who contravenes subsection (1) commits an offence and\nshall be deemed to have committed the offence of defrauding by false pretence under\nsection 13 1 of the Criminal Offences Act, 1960 (Act 29) , and shall liable on summary",
|
| 40 |
+
"(2) A person who contravenes subsection (1) commits an offence and\nshall be deemed to have committed the offence of defrauding by false pretence under\nsection 13 1 of the Criminal Offences Act, 1960 (Act 29) , and shall liable on summary\nconviction to the same punishment for defrauding by false pretence under that law. \u201d.\nSection 97 of Act 10 38 amended\n45. The principal enactment is amended in section 97 by:\n(a) the insertion before \u201cAuthority \u201d, of\n\u201cArtificial Intelligence is technology that enables computers and machines to\nsimulate human learning, comprehension, problem solving, decision -making, creativity and\nautonomy ;\u201d;\n\u201cblock-chain based technology \u201d means a n advanced decentralised digital record or\ndigital ledger of transactions, that enables recording of transactions, tracking of assets, and\nsharing of records across computer networks in a transparent and immutable way; \u201d.\n\u201cbig data\u201d means a collection of extremely large organized, semi -structured, and\nunstructured information that grows exponentially over time;\u201d ;\n(b) the insertion after \u201cchild\u201d, of\n\u201ccloud technology\u201d means technology that delivers computing services and offers\non-demand access to computing resources such as physical or virtual services, data\nstorage, servers, databases, networking, intelligence, analytics, and software over the\ninternet ;\u201d;\n(c) the insertion after \u201c cloud technology\u201d, of\nCYBERSECURITY (AMENDMENT) BILL, 2025\n29 \u201cconfiscation\u201d, which includes forfeiture where applicable, shall mean the\npermanent deprivation of property by order of a court or other competent\nauthority;\u201d;\n(d) the insertion after \u201cdigital ecosystem\u201d of",
|
| 41 |
+
"CYBERSECURITY (AMENDMENT) BILL, 2025\n29 \u201cconfiscation\u201d, which includes forfeiture where applicable, shall mean the\npermanent deprivation of property by order of a court or other competent\nauthority;\u201d;\n(d) the insertion after \u201cdigital ecosystem\u201d of\n\u201cdigital services\u201d means services delivered electronically whether traditionally or\nby internet, with minimal physical interaction, and includes:\n(a) social media platforms;\n(b) e-commerce platforms;\n(c) video -on-demand or streaming platforms;\n(d) messaging applications,\n(e) fintech software platforms;\n(f) online banking platforms;\n(e) the insertion after \u201cDirector -General\u201d, of\n\u201cEmergency\u201d shall mean a situation in which there is a significant and imminent\nrisk to the life or safety of any natural person;\u201d;\n(f) the insertion after \u201ce -services\u201d, of\n\u201cFreezing\u201d or \u201cseizure\u201d mean temporarily prohibiting the transfer, conversion,\ndisposition or movement of property or temporarily assuming custody or control\nof property on the basis of an order issued by a court or other competent\nauthority;\u201d;\n(g) the insertion after \u201cinterception of warrant \u201d, of\n\u201cInternet of Things (IoT) means a network of interrelated physical devices, vehicles,\nappliances and other physical objects that connect and exchange data with other\ndevices and the cloud across wireless networks , and are typically embedded with\nsensors, processing ability and software and can include mechanical and digital\nmachines and consumer objects ;\u201d;\n(h) the substitution for \u201ccomputer system \u201d, of\n\u201ccomputer system\u201d means an arrangement of interconnected computers that is\ndesigned to perform one or more specific functions, and includes\n(a) an information processing system;",
|
| 42 |
+
"machines and consumer objects ;\u201d;\n(h) the substitution for \u201ccomputer system \u201d, of\n\u201ccomputer system\u201d means an arrangement of interconnected computers that is\ndesigned to perform one or more specific functions, and includes\n(a) an information processing system;\n(b) an operational technology system, a programmable logic controller, a\nsupervisory control and data acquisition system, or a distributed control\nsystem; and\nCYBERSECURITY (AMENDMENT) BILL, 2025\n30 (c) dependency systems that support the functionality of a computer or\ncomputer network ;\u201d;\n(i) the substitution for \u201claw enforcement agency\u201d, of\n\u201claw enforcement agency\u201d means\n(a) The Police\n(b) The Customs Division of the Ghana Revenue Authority and\n(c) Cyber Security Authority\n(d) Any other agency authorised by law to exercise the powers of the Police\n(j) the insertion after \u201cprohibited intimate image and visual recording\u201d, of\n\u201cproperty\u201d mean s assets of every kind, in the country or elsewhere whether\ncorporeal or incorporeal, movable or immovable, tangible or intangible, including\nvirtual assets, and legal documents or instruments evidencing title to, or interest in,\nsuch property assets;\u201d;\n(k) the insertion after \u201cproperty\u201d, of\n\u201cproperty of or in the possession or control of a person\u201d includes a gift made by\nthat person;\n(l) the insertion after \u201cpublish\u201d, of\n\u201cquantum computing \u201d means technology that solves complex problems based on\nprinciples of quantum mechanics;\u201d .\nSection 98 of Act 1058 amended\n46. The principal enactment is amended by substitution for section 98, of\n\u201cRepeals and savings",
|
| 43 |
+
"that person;\n(l) the insertion after \u201cpublish\u201d, of\n\u201cquantum computing \u201d means technology that solves complex problems based on\nprinciples of quantum mechanics;\u201d .\nSection 98 of Act 1058 amended\n46. The principal enactment is amended by substitution for section 98, of\n\u201cRepeals and savings\n(1) Sections 118 and 136 of the Electronic Transaction Act, 2008 (Act 772)\nare repealed.\n(2) Sections 35 to 40 this Act repeals sections 55 to 62 of the Electronic\nTransaction Act, 2008 (Act 772) .\n(3) Despite the repeal of sections, any Regulations, bye -laws, directives,\nnotices, orders, directions, appointments or other acts lawfully made or\ndone under the repealed enactment and in force immediately before the\nCYBERSECURITY (AMENDMENT) BILL, 2025\n31 coming into force of this Act shall be deemed to have been made or done\nunder this Act and shall continue to have effect until instruments issued\nor made until revoked, cancelled, withdrawn or terminated.\u201d.\nSection 99 of Act 10 38 amended\n47. The Extradition Act, 1960 (Act 22) is amended by:\n(a) the addition after subsection (3) of section , of\n\u201c(4) Despite subsection (1), for the purposes of extradition , Cybercrime and\nCyber Offences under the First Schedule to this Act, in the absence of a separate\narrangement, bilateral or multilateral agreement between Ghana and another signatory of\nthe Budapest Convention on Cybercrime or any other convention , extradition of fugitive\ncriminals may still take place between Ghana and another signatory of the Budapest\nConvention on Cybercrime or any other convention and shall be governed by the\nprovisions of the said convention. \u201d.",
|
| 44 |
+
"the Budapest Convention on Cybercrime or any other convention , extradition of fugitive\ncriminals may still take place between Ghana and another signatory of the Budapest\nConvention on Cybercrime or any other convention and shall be governed by the\nprovisions of the said convention. \u201d.\nFirst schedule to Act 1038 amended\n48. The principal enactment is amended by substitution for the preamble, of\n\u201cA service provided for reward or a not -for profit basis intended primarily for or aimed at\nensuring or safeguarding the cybersecurity of a computer or computer system belonging\nto a person and includes.\u201d.\nSecond schedule to Act 10 38 amended\n49. The principal enactment is amended by substitution for administrative penalties for\nsections 36(4), 39(2)(a), 39(2)(b), 39(2)(c) and 92(2) and addition of administrative penalties\nfor sections 36(1) and 39(3)(c) .\nNo. Sections of\nAct creating\ncontravention Contravention Administrative\nPenalty\n1. 36(1) The Owner of the designated critical\ninformation infrastructure shall register\ntheir critical information infrastructure\nwith the Authority, and shall pay the\nprescribed annual registration and\ndesignation fee as determined by the\nAuthority or stipulated i n a legislative\ninstrument. Not less than five\nthousand penalty\nunits, not more than\nfifty thousand\npenalty units .\n2. 36(4) Owner of a registered critical information\ninfrastructure failing to inform the\nAuthority within seven days of the change\nin legal ownership of the registered critical Not less than five\nthousand penalty\nunits, not more than\nCYBERSECURITY (AMENDMENT) BILL, 2025\n32 information infrastructure.\nfifty thousand\npenalty units.\n3. 39(2)(a) Owner of a critical information",
|
| 45 |
+
"Authority within seven days of the change\nin legal ownership of the registered critical Not less than five\nthousand penalty\nunits, not more than\nCYBERSECURITY (AMENDMENT) BILL, 2025\n32 information infrastructure.\nfifty thousand\npenalty units.\n3. 39(2)(a) Owner of a critical information\ninfrastructure failing to report a\ncybersecurity incident .\nNot less than ten\nthousand penalty\nunits, not more than\nfifty thousand\npenalty units.\n4. 39(2)(b) Owner of a critical information\ninfrastructure failing to cause an audit to be\nperformed on the critical information\ninfrastructure .\nNot less than five\nthousand penalty\nunits, not more than\ntwenty thousand\npenalty units.\n5. 39(3)(c) Owner of a critical information\ninfrastructure failing to submit a copy of\nthe audit report to the Authority.\nNot less than five\nthousand penalty\nunits, not more than\ntwenty thousand\npenalty units.\n6. 92(2) Owner of a critical information\ninfrastructure, a cybersecurity service\nprovider or a provider of a digital service\nfailing to comply with a directive issued by\nthe Authority . Not less than ten\nthousand penalty\nunits, not more than\nfifty thousand\npenalty units\nThird schedule to Act 1038 amended\n50. The principal enactment is amended by addition after the \u201cOath of Secrecy\u201d, of\nThe Official Oath\n(section 5)\nI,\n\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026..\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\ndo (in the name of the Almighty God swear) (solemnly affirm) that I will at all times well\nand truly serve the Republic of Ghana in the office of \u2026\u2026.and that I will uphold, preserve,",
|
| 46 |
+
"do (in the name of the Almighty God swear) (solemnly affirm) that I will at all times well\nand truly serve the Republic of Ghana in the office of \u2026\u2026.and that I will uphold, preserve,\nprotect and defend the constituti on of the Republic of Ghana as by law established (so\nhelp me God)."
|
| 47 |
+
]
|
assets/example_bills/cybersecurity-amendment-bill-2025/document.txt
ADDED
|
@@ -0,0 +1,1104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 2 |
+
1 ARRANGEMENT OF SECTIONS
|
| 3 |
+
Section
|
| 4 |
+
1. Section 3 of Act 1038 Amended
|
| 5 |
+
2. Section 4A inserted
|
| 6 |
+
3. Section 5 of Act 1038 amended
|
| 7 |
+
4. Section 13 of Act 1038 amended
|
| 8 |
+
5. Section 14 of Act 1038 amended
|
| 9 |
+
6. Section 15A inserted
|
| 10 |
+
7. Section 20A inserted
|
| 11 |
+
8. Section 20B inserted
|
| 12 |
+
9. Section 31 of Act 1038 amended
|
| 13 |
+
10. Section 35 of Act 1038 amended
|
| 14 |
+
11. Section 36 of Act 1038 amended
|
| 15 |
+
12. Section 37 of Act 1038 amended
|
| 16 |
+
13. Section 40 of Act 1038 amended
|
| 17 |
+
14. Section 47 of Act 1038 amended
|
| 18 |
+
15. Section 49 of Act 1038 amended
|
| 19 |
+
16. Section 57 of Act 1038 amended
|
| 20 |
+
17. Section 57A inserted
|
| 21 |
+
18. Section 57B inserted
|
| 22 |
+
19. Section 57C inserted
|
| 23 |
+
20. Section 58A inserted
|
| 24 |
+
21. Section 58B inserted
|
| 25 |
+
22. Section 59 of Act 1038 amended
|
| 26 |
+
23. Section 59A inserted
|
| 27 |
+
24. Section 59B inserted
|
| 28 |
+
25. Section 59C inserted
|
| 29 |
+
26. Section 59D inserted
|
| 30 |
+
27. Section 59E inserted
|
| 31 |
+
28. Section 59F inserted
|
| 32 |
+
29. Section 59G inserted
|
| 33 |
+
30. Section 59H inserted
|
| 34 |
+
31. Section 59I inserted
|
| 35 |
+
32. Section 59J inserted
|
| 36 |
+
33. Section 59K inserted
|
| 37 |
+
34. Section 67 of Act 1038 amended
|
| 38 |
+
35. Section 67A inserted
|
| 39 |
+
36. Section 67B inserted
|
| 40 |
+
37. Section 68 of Act 1038 amended
|
| 41 |
+
38. Section 83 of Act 1038 amended
|
| 42 |
+
39. Section 90 of Act 1038 amended
|
| 43 |
+
40. Section 91 of Act 1038 amended
|
| 44 |
+
41. Section 92 of Act 1038 amended
|
| 45 |
+
42. Section 94 of Act 1038 amended
|
| 46 |
+
43. Section 94A inserted
|
| 47 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 48 |
+
2 44. Section 94B inserted
|
| 49 |
+
45. Section 97 of Act 1038 amended
|
| 50 |
+
46. Section 98 of Act 1058 amended
|
| 51 |
+
47. Section 99 of Act 1038 amended
|
| 52 |
+
48. First Schedule to Act 1038 amended
|
| 53 |
+
49. Second schedule to Act 1038 amended
|
| 54 |
+
50. Third schedule to Act 1038 amended
|
| 55 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 56 |
+
3 A
|
| 57 |
+
BILL
|
| 58 |
+
ENTITLED
|
| 59 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 60 |
+
AN ACT to amend the Cybersecurity Act, 2020 (Act 1038) to confer powers on the Cyber
|
| 61 |
+
Security Authority to investigate and prosecute cybercrime on the authority of the
|
| 62 |
+
Attorney -General and recover proceeds of cyber crime ; to revise the object and
|
| 63 |
+
functions of the Cyber Security Authority; to revise the governance and
|
| 64 |
+
administration of the Cyber Security Authority ; to revise the enforcement powers of
|
| 65 |
+
the Cyber Security Authority and to provide for related matters.
|
| 66 |
+
Section 3 of Act 10 38 Amended
|
| 67 |
+
1. The Cybersecurity Act, 2020 (Act 1038) referred to in the Act as the “principal
|
| 68 |
+
enactment” is amended by substitution for section 3, of
|
| 69 |
+
“Objects of the Authority
|
| 70 |
+
3. The objects of the Authority are to:
|
| 71 |
+
(a) regulate cybersecurity activities in the country;
|
| 72 |
+
(b) prevent, manage and respond to cybersecurity threats and
|
| 73 |
+
cybersecurity incidents ;
|
| 74 |
+
(c) regulate owners of critical information infrastructure in respect of
|
| 75 |
+
cybersecurity activities, cybersecurity service providers , cybersecurity
|
| 76 |
+
professionals and practitioners, and cybersecurity establishments in
|
| 77 |
+
the country;
|
| 78 |
+
(d) promote the development of cybersecurity in the country to ensure a
|
| 79 |
+
secured and resilient digital ecosystem;
|
| 80 |
+
(e) prevent and detect cybercrime;
|
| 81 |
+
(f) to facilitate the confiscation of proceeds of cybercrime;
|
| 82 |
+
(g) establish a platform for cross -sector engagement on matters of
|
| 83 |
+
cybersecurity for effective co -ordination and co -operation between
|
| 84 |
+
key public institutions and the private sector;
|
| 85 |
+
(h) create awareness of cybersecurity matters; an d
|
| 86 |
+
(i) collaborate with States, inter -governmental bodies, international
|
| 87 |
+
organisations and international agencies to promote the cybersecurity
|
| 88 |
+
of the country .”.
|
| 89 |
+
Section 4A inserted
|
| 90 |
+
2. The principal enactment is amended by the insertion after section 4, of
|
| 91 |
+
“Further Functions of the Authority
|
| 92 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 93 |
+
4 4A. The Authority , shall in addition to the functions in section 4, carry out the
|
| 94 |
+
following functions:
|
| 95 |
+
(a) subject to article 88 of the Constitution, to investigate and on the
|
| 96 |
+
authority of the Attorney -General , prosecute cybercrime under this
|
| 97 |
+
Act;
|
| 98 |
+
(b) establish standards for certifying the security of innovative products ,
|
| 99 |
+
Artificial Intelligence, cloud technology, quantum computing, big
|
| 100 |
+
data, Internet of Things (IoT) , blockchain -based technology and any
|
| 101 |
+
other emerging technologies ;
|
| 102 |
+
(c) certify the security of innovative products Artificial Intelligence,
|
| 103 |
+
cloud technology, quantum computing, big data, Internet of Things
|
| 104 |
+
(IoT) , blockchain -based technology and any other emerging
|
| 105 |
+
technologies , in accordance with the standards established pursuant
|
| 106 |
+
to paragraph ( b);
|
| 107 |
+
(d) accredit the cybersecurity establishments of critical information
|
| 108 |
+
infrastructure owners, cybersecurity service providers, cybersecurity
|
| 109 |
+
practitioners and professionals and other relevant person s or
|
| 110 |
+
institutions ;
|
| 111 |
+
(e) accredit non -profit cybersecurity institutions and cybersecurity
|
| 112 |
+
professional bodies ;
|
| 113 |
+
(f) promote the online protection of women, elderly , persons with
|
| 114 |
+
disabilities and underserved populations ;
|
| 115 |
+
(g) collaborate with relevant institutions to develop mechanisms
|
| 116 |
+
including technical security solutions and guidelines on the usage of
|
| 117 |
+
smart technology and other emerging technologies ; and
|
| 118 |
+
(h) promote the protection of digital right s as it relates to cyber security. ”.
|
| 119 |
+
Section 5 of Act 10 38 amended
|
| 120 |
+
3. The principal enactment is amended in section 5 by the addition after subparagraph
|
| 121 |
+
(iv) of paragraph (a) of subsection (1), of
|
| 122 |
+
“(v) Foreign Affairs and Regional Integration ; and
|
| 123 |
+
(vi) Gender, Children and Social Protection .”.
|
| 124 |
+
Section 13 of Act 10 38 amended
|
| 125 |
+
4. The principal enactment is amended in section 13 by:
|
| 126 |
+
(a) the substitution for paragraph (d) of subsection (2), of
|
| 127 |
+
“(d) the Executive Director of the Data Protection Commission or any
|
| 128 |
+
successor official or entity by whatever name designated, or a
|
| 129 |
+
representative of the Executive Director with the requisite
|
| 130 |
+
knowledge and skills in cybercrime and cybersecurity matters,
|
| 131 |
+
nominated by the Executive Director ;”;
|
| 132 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 133 |
+
5 (b) the substitution for paragraph (g) and (k) respectively of subsection (2), of
|
| 134 |
+
“(g) the Director of the National Intelligence Bureau or any successor
|
| 135 |
+
official or entity by whatever name designated, or a representative of
|
| 136 |
+
the Director with the requisite knowledge and skills in cybercrime ,
|
| 137 |
+
and cybersecurity matters, nominated by the Director;
|
| 138 |
+
(k) the Director General of National Signals Bureau or any successor
|
| 139 |
+
official or entity by whatever name designated, or a representative of
|
| 140 |
+
the Director with the requisite knowledge and skills in cybercrime and
|
| 141 |
+
cybersecurity matters, nominated by the Director; ”;
|
| 142 |
+
(c) the addition after paragraph (r) of subsection (2), o f
|
| 143 |
+
“(s) Any other person the Minister may nominate, on the advice of the
|
| 144 |
+
Authority. ”.
|
| 145 |
+
(d) the addition after subsection (13), of
|
| 146 |
+
“(14) A member of the JCC who is absent from three consecutive meetings
|
| 147 |
+
without sufficient cause ceases to be a member of the JCC .
|
| 148 |
+
(15) Where there is a vacancy, the Minister shall notify the President of
|
| 149 |
+
the vacancy , and the President shall appoint a person to fill the v acancy for
|
| 150 |
+
the unexpired term. ”.
|
| 151 |
+
Section 1 4 of Act 10 38 amended
|
| 152 |
+
5. The principal enactment is amended in section 14 by the substitution for subsection
|
| 153 |
+
(1), of
|
| 154 |
+
“14 (1). The Joint Cybersecurity Committee shall collaborate with the Authority and
|
| 155 |
+
the sectors or institutions represented on the Committee for the implementation of
|
| 156 |
+
relevant cybersecurity measures including but not limited to the following ways:
|
| 157 |
+
(a) identification of cybersecurity risks that may affect critical information
|
| 158 |
+
infrastructure or the overall cybersecurity of the country ;
|
| 159 |
+
(b) development of cybersecurity policies and guidelines based on identified
|
| 160 |
+
risks, best practices and international standards to mitigate cybersecurity
|
| 161 |
+
risks;
|
| 162 |
+
(c) coordination of cybersecurity activities between sectors and entities to
|
| 163 |
+
ensure that all cybersecurity efforts are aligned to prevent overlap and gaps;
|
| 164 |
+
(d) facilitate the sharing of information between sectors and entities in a secure
|
| 165 |
+
manner and in compliance with relevant data protection laws ;
|
| 166 |
+
(e) collaboration with sectors or institutions to tackle cybercrime including the
|
| 167 |
+
provision of a platform for incidence sharing and incidence response
|
| 168 |
+
mechanisms to facilitate ;
|
| 169 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 170 |
+
6 (f) evaluation of the effectiveness of implemented cybersecurity measures based
|
| 171 |
+
on established metrics with a view to continuously improve the cybersecurity
|
| 172 |
+
measures ; and
|
| 173 |
+
(g) collaboration with the Authority to educate the public on matters related to
|
| 174 |
+
cybercrime and cybersecurity. ”.
|
| 175 |
+
Section 15 A inserted
|
| 176 |
+
6. The principal enactment is amended by the insertion after section 15, of
|
| 177 |
+
“Appointment of Deputy -Director s General
|
| 178 |
+
15A. (1) The President shall in accordance with article 195 of the Constitution
|
| 179 |
+
appoint such Deputy Directors -General as are necessary for the performance of the
|
| 180 |
+
functions of the Authority.
|
| 181 |
+
(2) The Deputy Directors -General shall hold office on the terms and
|
| 182 |
+
conditions specified in the letters of appointment.
|
| 183 |
+
(3) The Deputy Directors -General shall act in the absence of the
|
| 184 |
+
Director -General and perform any other functions as determined by the Board.
|
| 185 |
+
(4) The Deputy Directors -General shall, subject to the provisions of this
|
| 186 |
+
Act:
|
| 187 |
+
(a) assist the Director -General in the discharge of his functions and
|
| 188 |
+
perform such other functions as the Director -General or the Board
|
| 189 |
+
may delegate to him;
|
| 190 |
+
(b) perform functions related to the object and functions of the
|
| 191 |
+
Authority on the direction or authority of the Director -General or
|
| 192 |
+
the Board ; and
|
| 193 |
+
(c) be responsible for the direction of the Authority when the Director -
|
| 194 |
+
General is absent from Ghana or is otherwise unable to perform his
|
| 195 |
+
functions. ”.
|
| 196 |
+
Section 20A inserted
|
| 197 |
+
7. The principal enactment is amended by the insertion after section 20, of
|
| 198 |
+
“Terms and Conditions of Service of Staff of the Authority
|
| 199 |
+
20A. (1) The terms and conditions of service of the staff of the Authority shall
|
| 200 |
+
not be less favourable than the staff of the security and intelligence agencies .
|
| 201 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 202 |
+
7 (2) The conditions of service attached to posts of legal officers of the
|
| 203 |
+
Authority shall not be less than that of the posts attached to that of legal officers of
|
| 204 |
+
the Attorney -General of the same rank.
|
| 205 |
+
(3) Staff shall, in addition to monthly salaries, be eligible for the payment
|
| 206 |
+
of any gratuity, allowance, pension, subsidy or benefit to members in respect of
|
| 207 |
+
their service or resignation or retirement .”.
|
| 208 |
+
Section 20B inserted
|
| 209 |
+
8. The principal enactment is amended by the insertion after section 20, of
|
| 210 |
+
“Powers of police
|
| 211 |
+
20B. (1) The Director -General, Deputy Director -General and other
|
| 212 |
+
authorised officers shall exercise the powers of a Police Officer, including the
|
| 213 |
+
powers of arrest, search and seizure and have the same rights, protections
|
| 214 |
+
immunities conferred on a Police officer in the Criminal and Other Offences
|
| 215 |
+
(Procedure) Act, 1960 (Act 30), the Police Service Act, 1970 (Act 350) and any other
|
| 216 |
+
law related to a Police officer in the performance of their functions under the Act. ”.
|
| 217 |
+
Section 31 of Act 10 38 amended
|
| 218 |
+
9. The principal enactment is amended by substitution for section 31, of :
|
| 219 |
+
“Sources of money for the Fund
|
| 220 |
+
31. The sources of moneys for the Fund include:
|
| 221 |
+
(a) seed money approved by Parliament;
|
| 222 |
+
(b) moneys which may become lawfully payable to the Authority for the
|
| 223 |
+
Fund;
|
| 224 |
+
(c) 50% of all fines arising from criminal penalties under th e Act;
|
| 225 |
+
(f) grants, gifts, donations and other voluntary contributions;
|
| 226 |
+
(g) 12% of the communications service tax for the Fund per annum;
|
| 227 |
+
(h) 9 % corporate tax for the Fund per annum;
|
| 228 |
+
(i) a charge determined by the Authority in accordance with the Fees
|
| 229 |
+
and Charges (Miscellaneous Provisions) Act, 2018 (Act 983) and
|
| 230 |
+
levied on persons licensed by the Bank of Ghana to carry on business;
|
| 231 |
+
(j) a proportion of the fees charged on all government e lectronic
|
| 232 |
+
services determined by the Authority ; and
|
| 233 |
+
(h) any other moneys approved by Parliament for the Fund. ”.
|
| 234 |
+
Section 35 of Act 10 38 amended
|
| 235 |
+
10. The principal enactment is amended by substitution for section 35, of
|
| 236 |
+
“Designation of critical information infrastructure
|
| 237 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 238 |
+
8 35. (1) The Minister may, on the advice of the Authority, designate a
|
| 239 |
+
computer system or computer network as a critical information infrastructure if the
|
| 240 |
+
Minister considers that the computer system or computer network is essential for
|
| 241 |
+
(a) national security,
|
| 242 |
+
(b) the economic and social well -being of citizens , or
|
| 243 |
+
(c) public health and safety.
|
| 244 |
+
(2) Where the Minister designates a computer system or computer
|
| 245 |
+
network as a critical information infrastructure, the Minister shall publish the sector of the
|
| 246 |
+
designated critical information infrastructure in the Gazette and, the owner of the Critical
|
| 247 |
+
Information Infrastructure shall be notified of the designation.
|
| 248 |
+
(3) The Minister shall, in making a determination under subsection (1),
|
| 249 |
+
consider if the computer system or computer network is necessary for :
|
| 250 |
+
(a) the security, defence or international relations of the country;
|
| 251 |
+
(b) the production, preservation or identity of a confidential source of
|
| 252 |
+
information related to the enforcement of criminal law;
|
| 253 |
+
(c) the provision of services directly related to
|
| 254 |
+
(i) communications and telecommunications infrastructure;
|
| 255 |
+
(ii) banking and financial services;
|
| 256 |
+
(iii) public utilities
|
| 257 |
+
(iv) public transportation; and
|
| 258 |
+
(v) public key infrastructure;
|
| 259 |
+
(d) the protection of public safety and public health, including
|
| 260 |
+
systems related to essential emergency services;
|
| 261 |
+
(e) an international business or communication affecting a citizen of
|
| 262 |
+
Ghana or any other international business in which a citizen of
|
| 263 |
+
Ghana or the Government has an interest;
|
| 264 |
+
(f) the Legislature, Executive, Judiciary, Public Services or security
|
| 265 |
+
agencies ; or
|
| 266 |
+
(g) digital services;
|
| 267 |
+
(h) services related to the supply chain in the critical information
|
| 268 |
+
infrastructure ecosystem; and
|
| 269 |
+
(i) any other services determined by the Minister, on the advice of the
|
| 270 |
+
Authority .
|
| 271 |
+
(4) The Minister shall, by publication in the Gazette , estab lish the
|
| 272 |
+
procedure for the regulation of a critical information infrastructure. ”.
|
| 273 |
+
Section 36 of Act 10 38 amended
|
| 274 |
+
11. The principal enactment is amended by substitution for section 3 6, of
|
| 275 |
+
“Registration of critical information in frastructure
|
| 276 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 277 |
+
9 36. (1) The Owner of the designated critical information infrastructure shall
|
| 278 |
+
register their critical information infrastructure with the Authority , and shall pay the
|
| 279 |
+
prescribed annual registration and designation fee as determined by the Authority
|
| 280 |
+
or stipulated in a legislative instrument .
|
| 281 |
+
(2) For compliance purposes, the owner of an unregistered critical
|
| 282 |
+
information infrastructure shall receive a notification of registration letter from the
|
| 283 |
+
Authority to comply with the Act.
|
| 284 |
+
(3) During registration, the owner of a critical information infrastructure
|
| 285 |
+
shall nominate a point of contact and submit details of the nominee to the
|
| 286 |
+
Authority.
|
| 287 |
+
(4) The owner of a critical information infrastructure shall submit to the
|
| 288 |
+
Authority, verified details of critical information infrastructure, through the
|
| 289 |
+
channels and modalities determined by the Authority.
|
| 290 |
+
(5) The Authority shall validate the information submitted by the owner
|
| 291 |
+
of a critical information infrastructure, and the owner shall receive a Certificate of
|
| 292 |
+
Registration upon completion of registration requirements.
|
| 293 |
+
(6) The Authority shall through the issuance of guidelines or directives
|
| 294 |
+
determine :
|
| 295 |
+
(a) further requirements for the registration of a critical information
|
| 296 |
+
infrastructure ;
|
| 297 |
+
(b) the procedure for the registration of a critical information
|
| 298 |
+
infrastructure; and
|
| 299 |
+
(c) any other matter relating to the registration of a critical information
|
| 300 |
+
infrastructure.
|
| 301 |
+
(7) Where there is any change in the legal ownership of a designated
|
| 302 |
+
critical information infrastructure, the owner of the designated critical information
|
| 303 |
+
infrastructure shall , within seven days after the change, inform the Authority of the
|
| 304 |
+
change in ownership.
|
| 305 |
+
(8) An owner of a designated critical information infrastructure who
|
| 306 |
+
contravenes subsectio n (1) and (3) is liable to pay to the Authority the administrative
|
| 307 |
+
penalty specified in the Second Schedule. ”.
|
| 308 |
+
Section 37 of Act 10 38 amended
|
| 309 |
+
12. The principal enactment is amended by substitution for section 37 , of
|
| 310 |
+
“Withdrawal of designation of critical information infrastructure
|
| 311 |
+
“37. (1) The Minister may, on the advice of the Authority, withdraw the
|
| 312 |
+
designation of a critical information infrastructure at any time if the Minister considers that
|
| 313 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 314 |
+
10 the computer or computer network no longer satisfies the criteria of a critical information
|
| 315 |
+
infrastructure .
|
| 316 |
+
(2) The Minister may publish the withdrawal of critical information
|
| 317 |
+
infrastructure sector in the Gazette .
|
| 318 |
+
(3) The owner of a designated critical information infrastructure shall
|
| 319 |
+
receive notification of the withdrawal of the designation by letter from the Minister. ”.
|
| 320 |
+
Section 40 of Act 10 38 amended
|
| 321 |
+
13. The principal enactment is amended in section 40 by substitution for subsection
|
| 322 |
+
(2), of
|
| 323 |
+
“(2) A person who contravenes subsection (1) commits an offence and is liable
|
| 324 |
+
on summary conviction to a fine of not less than four thousand penalty units and not more
|
| 325 |
+
than twenty -five thousand penalty units , or both .”.
|
| 326 |
+
Section 47 of Act 10 38 Amended
|
| 327 |
+
14. The principal enactment is amended in section 47 by:
|
| 328 |
+
(a) the addition after subsection (6), of
|
| 329 |
+
“(7) A Sectoral Computer Emergency Response Team shall within twenty -four
|
| 330 |
+
hours after receiving a report of a cybersecurity incident from the owner of a designated
|
| 331 |
+
critical information infrastructure, a service provider, a licensee or any relevant person
|
| 332 |
+
report the cybersecurity incident to the National Computer Emergency Response Team.”.
|
| 333 |
+
(b) the addition after subsection (7), of
|
| 334 |
+
“(8) A Sectoral Computer Emergency Response Team who contravenes
|
| 335 |
+
subsection (7) is liable to pay to the Authority the administrative penalty specified in the
|
| 336 |
+
Second Schedule.”.
|
| 337 |
+
Section 49 of Act 1038 Amended
|
| 338 |
+
15. The principal enactment is amended in section 49 by
|
| 339 |
+
(a) the substitution for subsection (1), of
|
| 340 |
+
“(1) A person shall not provide a cybersecurity service for a reward unless that
|
| 341 |
+
person obtains a licence issued by the Authority in accordance with the Act.”.
|
| 342 |
+
(b) the substitution for subsection (2), of
|
| 343 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 344 |
+
11 “(2) A person shall not provide a cybersecurity service on a not -for-profit basis
|
| 345 |
+
unless that person obtains an accreditation issued by the Authority in accordance with the
|
| 346 |
+
Act.”.
|
| 347 |
+
(c) addition after subsection (2), of
|
| 348 |
+
“(3) A person who contravenes subsection (1) and (2) is liable to pay to the
|
| 349 |
+
Authority the administrative penalty the equivalent to the cost of damage caused and value
|
| 350 |
+
of the financial gain made , or an administrative penalty of not less than fifty thousand
|
| 351 |
+
penalty units and not more than one hundred thousand penalty units. ”.
|
| 352 |
+
Section 57 of Act 1038 Amended
|
| 353 |
+
16. The principal enactment is amended by the substitution for section 57, of
|
| 354 |
+
(1) The Authority shall establish a mechanism for the accreditation of
|
| 355 |
+
cybersecurity professionals and practitioners.
|
| 356 |
+
(2) An individual shall not practise as a cybersecurity professional or
|
| 357 |
+
cybersecurity practitioner unless that individual has been accredited by the Authority;
|
| 358 |
+
(3) A person shall not knowingly engage the services of a cybersecurity
|
| 359 |
+
professional or cybersecurity practitioner who has not been accredited by the Authorit y;
|
| 360 |
+
(4) A person who contravenes subsection (2) or (3) is liable to pay to the
|
| 361 |
+
Authority an administrative penalty of not less than two hundred and fifty penalty units
|
| 362 |
+
and not more than twenty thousand penalty units .
|
| 363 |
+
Section 57A inserted
|
| 364 |
+
17. The principal enactment is amended by the insertion after section 57, of
|
| 365 |
+
“Cyber hygiene certification scheme
|
| 366 |
+
57B. (1) The Authority shall establish a scheme for the certification of cybersecurity
|
| 367 |
+
professionals and practitioners, and cybersecurity service providers who will be entrusted
|
| 368 |
+
to perform cyber hygiene certification service as an alternative to other international best
|
| 369 |
+
practice security frameworks.
|
| 370 |
+
(2) The Cyber Security Authority shall conduct periodic audits to ensure
|
| 371 |
+
compliance with the cyber hygiene certification scheme and associated prescribed fees.
|
| 372 |
+
(3) Any complaints regarding non -compliance shall be investigated, and
|
| 373 |
+
appropriate enforcement actions shall be taken. ”.
|
| 374 |
+
Section 57B inserted
|
| 375 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 376 |
+
12 18. The principal enactment is amended by the insertion after section 58A, of
|
| 377 |
+
“Application for certification to provide the Cyber hygiene certification services
|
| 378 |
+
57B. (1) A licensed cybersecurity service provider or accredited CP who seeks to
|
| 379 |
+
provide the Authority’s cyber hygiene certification service shall apply in writing to the
|
| 380 |
+
Authority.
|
| 381 |
+
(2) The application shall be made in the prescribed form and accompanied by
|
| 382 |
+
the:
|
| 383 |
+
(a) supporting documentation, and
|
| 384 |
+
(b) prescribed fee that the Authority may determine .”.
|
| 385 |
+
Section 57C inserted
|
| 386 |
+
19. The principal enactment is amended by the insertion after section 58B, of
|
| 387 |
+
“Standardised , Review and Administration of Fees for Cyber Hygiene Certification
|
| 388 |
+
Service
|
| 389 |
+
57C. (1) A certified cybersecurity professional or practitioner or cybersecurity service
|
| 390 |
+
provider shall charge a fee for cyber hygiene certification services as determined by the
|
| 391 |
+
Cyber Security Authority.
|
| 392 |
+
(2) A certified cybersecurity professional or practitioner or cybersecurity service
|
| 393 |
+
provide r shall not charge fees exceeding the approved flat rate for cyber hygiene
|
| 394 |
+
certification services.
|
| 395 |
+
(3) A certified cybersecurity professional or practitioner or cybersecurity service
|
| 396 |
+
provider who fails to comply wit h subsection (2) shall be liable to pay to the Authority an
|
| 397 |
+
administrative penalty specified in the Second Schedule (not less than two hundred and
|
| 398 |
+
fifty penalty units and not more than five thousand penalty units)
|
| 399 |
+
(4) 30% of the revenue generated by a certified cybersecurity professional or
|
| 400 |
+
practitioner or cybersecurity service provider under the scheme shall be paid into the
|
| 401 |
+
cybersecurity fund.
|
| 402 |
+
(5) The Cyber Security Authority shall, by notice published in the Gazette,
|
| 403 |
+
determine the applicable flat rate for cyber hygiene certification services.
|
| 404 |
+
(6) The flat rate shall be reviewed periodically based on, industry trends, and the
|
| 405 |
+
affordability needs of stakeholders. ”.
|
| 406 |
+
Section 58A inserted
|
| 407 |
+
20. The principal enactment is amended by the insertion after section 5 8, of
|
| 408 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 409 |
+
13 “Certification of the security of innovative and emerging technologies.
|
| 410 |
+
58A. The Authority shall establish a mechanism for the certification of the security of
|
| 411 |
+
innovative and emerging technologies. ”.
|
| 412 |
+
Section 58B inserted
|
| 413 |
+
21. The principal enactment is amended by the insertion after section 58A, of
|
| 414 |
+
“Accreditation of non -profit cybersecurity institutions
|
| 415 |
+
58B. The Authority shall establish a mechanism for the accreditation of non -profit
|
| 416 |
+
cybersecurity institutions .”.
|
| 417 |
+
Section 59 of Act 10 38 Amended
|
| 418 |
+
22. The principal enactment is amended in section 59 by the addition after paragraph
|
| 419 |
+
(e) of subsection (1), of
|
| 420 |
+
“(g) certif ication of the security of innovative and emerging technologies including
|
| 421 |
+
Artificial Intelligence, cloud technology, quantum computing, big data, and blockchain -
|
| 422 |
+
based technology. ”.
|
| 423 |
+
Section 59A inserted
|
| 424 |
+
23. The principal enactment is amended by the insertion after section 59, of
|
| 425 |
+
“Enforcement powers
|
| 426 |
+
59A. (1) A person who:
|
| 427 |
+
(a) knowingly fails to comply with or acts in contravention of this Act,
|
| 428 |
+
regulations or directives issued under this Act ;
|
| 429 |
+
(b) knowingly fails to comply with prescribed cybersecurity standards and
|
| 430 |
+
requirements ;
|
| 431 |
+
(c) provides cybersecurity services without a licence or acts as a cybersecurity
|
| 432 |
+
professional or practitioner without accreditation ;
|
| 433 |
+
(d) willfully obstructs, hinders, molests or assaults personnel of the Authority
|
| 434 |
+
duly engaged in the exercise of power conferred on the Authority under this
|
| 435 |
+
Act.
|
| 436 |
+
commits an offence and is liable on summary conviction to a term of imprisonment
|
| 437 |
+
of not more than five years or to a fine of not more than twenty thousand penalty
|
| 438 |
+
units, or to both.
|
| 439 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 440 |
+
14 (1) Where an offence is committed by a corporate entity, that entity is liable to
|
| 441 |
+
a fine of not more than twenty thousand penalty units and each director of
|
| 442 |
+
that entity shall be deemed to have committed the offence.
|
| 443 |
+
(2) Despite subsection (1), the Authority may, where a person has breached this
|
| 444 |
+
Act, Regulations or directives or where a licensee, an accreditation holder
|
| 445 |
+
has breached a condition contained in its licence or accreditation :
|
| 446 |
+
(a) warn the person, licensee, or accreditation holder,
|
| 447 |
+
(b) issue a cease -and-desist order,
|
| 448 |
+
(c) apply to the High Court for
|
| 449 |
+
(i) an injunction to restrain the person, licensee, or accreditation
|
| 450 |
+
holder from continuing the breach, or
|
| 451 |
+
(ii) other appropriate order to enforce compliance with this Act,
|
| 452 |
+
(d) propose amendments to the licence or accreditation in accordance
|
| 453 |
+
with this Act or Regulations
|
| 454 |
+
(e) suspend or terminate the licence or accreditation in accordance with
|
| 455 |
+
this Act, or
|
| 456 |
+
(f) impose administrative penalties in accordance with the second
|
| 457 |
+
schedule of the Act
|
| 458 |
+
(g) or take any other action that it considers appropriate and that is not
|
| 459 |
+
contrary to this Act.
|
| 460 |
+
(4) Nothing shall prevent the Authority from pursuing only criminal
|
| 461 |
+
enforcement actions under subsection (1) or only administration
|
| 462 |
+
enforcement actions under subsection (2), or a combination of
|
| 463 |
+
both. ”.
|
| 464 |
+
Section 59B inserted
|
| 465 |
+
24. The principal enactment is amended by the insertion after section 59A, of
|
| 466 |
+
“Power to conduct investigations and to prosecute cybercrime ”
|
| 467 |
+
59B. (1) The Authority shall upon the occurrence of a cybersecurity incident or a
|
| 468 |
+
cybercrime conduct criminal investigations and prosecute same .”
|
| 469 |
+
(2) The Authority shall have the jurisdiction to prosecute all offences under the
|
| 470 |
+
Electronic Transactions Act, 2008 (Act 772).
|
| 471 |
+
(3) Where a person is convicted of a cybercrime under this Act, the Authority
|
| 472 |
+
may apply to the Court for an order to confiscate moneys, proceeds, benefits, properties
|
| 473 |
+
and assets purchased by a person with proceeds derived from or in the commission of the
|
| 474 |
+
cybercrime.
|
| 475 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 476 |
+
15
|
| 477 |
+
(4) Despite subsection (5), nothing shall prevent the Authority from instituting
|
| 478 |
+
a civil action independently or at the same time as the prosecution to recover moneys,
|
| 479 |
+
proceeds, benefits, properties and assets purchased by a person with proceeds derived from
|
| 480 |
+
or in the commission of the cybercrime.
|
| 481 |
+
(5) In relation to subsection (6) if criminal prosecution fails, civil asset recovery
|
| 482 |
+
should still proceed, and confiscation orders should have the effect of a civil judgment
|
| 483 |
+
appealable from the High Court to the Court of Appeal. ”.
|
| 484 |
+
(6) Where the Authority considers that freezing of property is necessary to
|
| 485 |
+
facilitate an investigation or prosecution, the Authority shall in writing direct the freezing
|
| 486 |
+
of:
|
| 487 |
+
(a) the property of a person or entity being investigated; or
|
| 488 |
+
(b) specified property held by a person or entity other than the person
|
| 489 |
+
or entity being investigated or prosecuted.
|
| 490 |
+
(7) The Authority shall within 14 days after the freezing of the property apply
|
| 491 |
+
to the Court for a confirmation of the freezing.
|
| 492 |
+
Section 59 C inserted
|
| 493 |
+
25. The principal enactment is amended by the insertion after section 59 B, of
|
| 494 |
+
“Power to request for information
|
| 495 |
+
59C. (1) The Authority may, for the purposes of carrying out an investigation in
|
| 496 |
+
respect of a contravention of the Act, Regulations or any other relevant enactment by the
|
| 497 |
+
owner of a critical information infrastructure, a licensee, a service provider or any other
|
| 498 |
+
person, by notice in writing, require a person to :
|
| 499 |
+
(a) attend at a time and place specified in the notice; and
|
| 500 |
+
(b) furnish the Authority with information related to a matter relevant to the
|
| 501 |
+
investigation.
|
| 502 |
+
(2) A notice under subsection (1) shall indicate the subject matter and purpose
|
| 503 |
+
of the request. ”.
|
| 504 |
+
(3) Where a person required to furnish the Authority with an information is
|
| 505 |
+
under an obligation not to disclose, or asserts a right not to disclose, the Authority shall
|
| 506 |
+
apply to the High Court for an order for the production of the information.
|
| 507 |
+
(4) Where an information is furnished to the Authority, the Authority shall
|
| 508 |
+
make copie s or extract from the Information and request the person
|
| 509 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 510 |
+
16 producing the information to provide an ex planation on the content of the
|
| 511 |
+
information where necessary.
|
| 512 |
+
(5) A person who appears before the Authority may be represented by a Counsel
|
| 513 |
+
of the choice of that person at any stage of the process .
|
| 514 |
+
(6) Except as provided in subsection (2) where a person contravenes subsection (1)
|
| 515 |
+
(a) and (b), that person commits an offence and is liable on summary conviction to a fine
|
| 516 |
+
of not less than two hundred and fifty penalty units and not more than ten thousand penalty
|
| 517 |
+
units or to a term of imprisonment of not less than one year and not more than two years
|
| 518 |
+
or to both. ”.
|
| 519 |
+
Section 59D inserted
|
| 520 |
+
26. The principal enactment is amended by the insertion after section 59C, of
|
| 521 |
+
“Application for Production Order to Collect Computer Data
|
| 522 |
+
59D. (1) An investigative officer may apply ex-parte to the High Court for a
|
| 523 |
+
production order to collect computer data.
|
| 524 |
+
(2) A production order under section (1) shall require a person in possession or
|
| 525 |
+
control of a computer or computer system to submit specified computer data which is
|
| 526 |
+
stored in the computer or computer system to the investigative officer.
|
| 527 |
+
(3) An investigative officer who makes an application under section (1) shall
|
| 528 |
+
demonstrate to the satisfaction of the Court that there are reasonable grounds to believe
|
| 529 |
+
that the computer data is reasonably required for the purposes of a specific criminal
|
| 530 |
+
invest igation.
|
| 531 |
+
(4) Where an investigative officer makes an application under section (1), that
|
| 532 |
+
investigative officer shall :
|
| 533 |
+
(a) explain why the investigative officer believes the computer data sought, will
|
| 534 |
+
be available to the person in control of the computer or computer system;
|
| 535 |
+
(b) identify and explain with specificity the type of computer data suspected to
|
| 536 |
+
be found on the computer or computer system;
|
| 537 |
+
(c) identify and explain with specificity the computer data that may be found on
|
| 538 |
+
a computer or computer system that is the subject of an investigation or
|
| 539 |
+
prosecution;
|
| 540 |
+
(d) identify and explain with specificity the offences in respect of which the
|
| 541 |
+
production order is sought; and
|
| 542 |
+
(e) indicate what measures shall be taken to ensure that the computer data will
|
| 543 |
+
be procured
|
| 544 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 545 |
+
17
|
| 546 |
+
(i) whilst maintaining the privacy of other users, customers and third
|
| 547 |
+
parties, and
|
| 548 |
+
(ii) without the disclosure of the computer data of any party not part of
|
| 549 |
+
the investigation. ”.
|
| 550 |
+
Section 59 E inserted
|
| 551 |
+
27. The principal enactment is amended by the insertion after section 59 D, of
|
| 552 |
+
“Grant of production order
|
| 553 |
+
59E. (1) The High Court may grant an application for a production order
|
| 554 |
+
under subsection (1) of section 59D, if the Court is satisfied that
|
| 555 |
+
(a) the investigative officer has complied with subsection (3) and (4) of section
|
| 556 |
+
59D;
|
| 557 |
+
(b) the information requested is commensurate, proportionate and necessary for
|
| 558 |
+
the purposes of a specific criminal investigation or prosecution;
|
| 559 |
+
(c) measures shall be taken to ensure that the computer data is produced whilst
|
| 560 |
+
maintaining the privacy of other users, customers and third parties and
|
| 561 |
+
without the disclosure of information and data of any party not part of the
|
| 562 |
+
investigation; and
|
| 563 |
+
(d) the investigation may be frustrated or seriously prejudiced unless the
|
| 564 |
+
production of the information is permitted.
|
| 565 |
+
(2) The Court shall require the service provider to keep confidential the
|
| 566 |
+
production order and the execution of the production order under this
|
| 567 |
+
section.
|
| 568 |
+
(3) A production order granted under this Act shall be served on a domestic
|
| 569 |
+
service provider, foreign service provider or both, or any other relevant
|
| 570 |
+
person. ”.
|
| 571 |
+
Section 59F inserted
|
| 572 |
+
28. The principal enactment is amended by the insertion after section 59E, of
|
| 573 |
+
“Application for Warrant to Access, Search and Seize Computer Data, Computer or
|
| 574 |
+
Computer System
|
| 575 |
+
59F. (1) An investigative officer may apply ex -parte to the High Court for a warrant to :
|
| 576 |
+
(a) access data on a computer or computer system;
|
| 577 |
+
(b) search and seize a computer or a computer system; or
|
| 578 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 579 |
+
18 (c) make or retain a copy of computer data on a computer or computer system;
|
| 580 |
+
(d) maintain the integrity of computer data on a computer or computer system;
|
| 581 |
+
(e) remove or render inaccessible computer data on a computer or computer
|
| 582 |
+
system
|
| 583 |
+
that is suspected to contain information relevant to an investigation.
|
| 584 |
+
(2) A warrant under subse ction (1) shall require a person who has knowledge
|
| 585 |
+
about the functioning of the computer or computer system to provide the
|
| 586 |
+
necessary information to enable an investigative officer undertake the actions
|
| 587 |
+
required under paragraphs (a) to (e) of subsection (1).
|
| 588 |
+
(3) An investigative officer who makes an application under subsection (1) shall
|
| 589 |
+
demonstrate to the satisfaction of the court that there are reasonable grounds
|
| 590 |
+
to believe that the warrant is reasonably required for the purposes of a specific
|
| 591 |
+
criminal investiga tion.
|
| 592 |
+
(4) A warrant under subsection (1) shall permit a law enforcement officer to
|
| 593 |
+
expeditiously extend the search for data from a computer or computer system
|
| 594 |
+
to another computer or computer system or part of it in Ghana where the law
|
| 595 |
+
enforcement officer has reasonable grounds to believe that the data sought is
|
| 596 |
+
accessible from or available to that other computer or computer system or
|
| 597 |
+
part of it in Ghana .”.
|
| 598 |
+
Section 59G inserted
|
| 599 |
+
29. The principal enactment is amended by the insertion after section 59F, of
|
| 600 |
+
“Grant of warrant
|
| 601 |
+
59G. (1) The High Court may grant an application for a warrant under subsection 1
|
| 602 |
+
of section 59F if the Court is satisfied that
|
| 603 |
+
(a) the investigative officer has complied with subsection (3) of section 59F.
|
| 604 |
+
(b) the information requested is commensurate, proportionate and necessary
|
| 605 |
+
for the purposes of a specific criminal investigation or prosecution;
|
| 606 |
+
(c) measures shall be taken to ensure that the computer data is produced whilst
|
| 607 |
+
maintaining the privacy of other users, customers and third parties and
|
| 608 |
+
without the disclosure of information and data of any party not part of the
|
| 609 |
+
investigation; and
|
| 610 |
+
(d) the investigation may be frustrated or seriously prejudiced unless the
|
| 611 |
+
production of the information is permitted.
|
| 612 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 613 |
+
19
|
| 614 |
+
(2) The Court shall require the service provider to keep confidential the
|
| 615 |
+
warrant and the execution of the warrant under this section.
|
| 616 |
+
(3) A warrant granted under this section shall be served on a domestic service
|
| 617 |
+
provider, foreign service provider or both, or any other relevant person. ”.
|
| 618 |
+
Section 59H in serted
|
| 619 |
+
30. The principal enactment is amended by the insertion after section 59G, of
|
| 620 |
+
“Application for preservation order to preserve computer data
|
| 621 |
+
(1) A provider of wire or electronic communication services or a remote
|
| 622 |
+
computing service on the written request of a law enforcement agency, shall take the
|
| 623 |
+
necessary steps to preserve records and other evidence in its possession pending the issue
|
| 624 |
+
of a Court o rder and shall take steps to ensure that the request by the law enforcement
|
| 625 |
+
agency is not disclosed to third parties during the period.
|
| 626 |
+
(2) Where an order from the Court is not obtained and served for fourteen days
|
| 627 |
+
after the receipt of the written request, the wire or electronic communication services, or
|
| 628 |
+
remote computing service provider is not under any obligation to preserve the evidence.
|
| 629 |
+
(3) An investigative officer authorised by a designated officer may apply ex-parte
|
| 630 |
+
to the Court for an order to require a service provider or any person in control of a
|
| 631 |
+
computer or computer system to preserve specified computer data, including traffic data
|
| 632 |
+
that has been stored by a computer system.
|
| 633 |
+
(4) An investigative officer who makes an application under subsection (1) shall
|
| 634 |
+
demonstrate to the satisfaction of the Court that the computer data is particularly
|
| 635 |
+
vulnerable to loss or modification. ”.
|
| 636 |
+
Section 59I
|
| 637 |
+
31. The principal enactment is amended by the insertion after section 59H, of
|
| 638 |
+
“Grant of preservation order
|
| 639 |
+
59I. (1) The High Court may grant an application for a preservation order under
|
| 640 |
+
subsection (1) of section 59F if the Court is satisfied that :
|
| 641 |
+
(a) the designated officer has complied with subsection (2) of section 59H;
|
| 642 |
+
(b) the extent of the preservation order is commensurate, proportionate and
|
| 643 |
+
necessary for the purposes of a specific criminal investigation or
|
| 644 |
+
prosecution;
|
| 645 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 646 |
+
20 (c) measures shall be taken to ensure that the computer data is preserved whilst
|
| 647 |
+
maintaining the privacy of other users, customers and third parties and
|
| 648 |
+
without the disclosure of information and data of any party not part of the
|
| 649 |
+
investigation; and
|
| 650 |
+
(d) the investigation may be frustrated or seriously prejudiced unless the
|
| 651 |
+
preservation order is permitted.
|
| 652 |
+
(2) The preservation order issued under subsection (1)
|
| 653 |
+
(a) is valid for a period of ninety days, and
|
| 654 |
+
(b) may be renewed for a further period of ninety days upon application to the
|
| 655 |
+
Court.
|
| 656 |
+
(3) The Court shall require the service provider to keep confidential a
|
| 657 |
+
preservation order issued under this regulation. ”.
|
| 658 |
+
Section 59J
|
| 659 |
+
32. The principal enactment is amended by the insertion after section 59I, of
|
| 660 |
+
“Power of entry, inspection and audit
|
| 661 |
+
59J. (1) The Authority may designate a person as an inspector to inspect premises.
|
| 662 |
+
(2) A person designated as an inspector by the Authority may
|
| 663 |
+
(a) obtain a warrant to :
|
| 664 |
+
(i) enter and inspect premises; and
|
| 665 |
+
(ii) conduct an audit of a computer system
|
| 666 |
+
(b) at any reasonable time, enter inspect premises and conduct and audit of a
|
| 667 |
+
computer system without a warrant where the inspector
|
| 668 |
+
(i) reasonably believes that an inspection of the premises is necessary to
|
| 669 |
+
ensure compliance by the owner of a critical information infrastructure,
|
| 670 |
+
a licensee or a service provider or any other relevant person with the
|
| 671 |
+
Act or Regulations; and
|
| 672 |
+
(ii) has given the occupier of the premises seven days' notice in writing of
|
| 673 |
+
the intention to enter , inspect the premises and conduct an audit of a
|
| 674 |
+
computer system .
|
| 675 |
+
(3) An inspector shall, before exercising a power of entry and inspection,
|
| 676 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 677 |
+
21 (a) produce evidence of the identity of the inspector; and
|
| 678 |
+
(b) indicate the purpose of entry and inspection.
|
| 679 |
+
(4) An inspector may
|
| 680 |
+
(a) enter a premises;
|
| 681 |
+
(b) inspect the premises;
|
| 682 |
+
(c) inspect a document or equipment found on the premises;
|
| 683 |
+
(d) require a person on the premises to provide any information, or produce any
|
| 684 |
+
document in the possession or control of that person that the inspector
|
| 685 |
+
considers relevant to ensure compliance with the Act and Regulation and
|
| 686 |
+
(e) require any person on the premises to provide information in respect of a
|
| 687 |
+
document or the location of a document.
|
| 688 |
+
(4) For the purpose of this regulation, “premises” does not include domestic
|
| 689 |
+
premises. ”.
|
| 690 |
+
Section 59K inserted
|
| 691 |
+
33. The principal enactment is amended by the insertion after section 59J, of
|
| 692 |
+
“Witness and Inform ant Protection
|
| 693 |
+
59K. (1) Except with the written, informed, explicit and freely given consent of a
|
| 694 |
+
witness or inform ant, no witness or inform ant who discloses information relating to
|
| 695 |
+
cybercrime under this Act or administrative infractions under this Act, shall be obliged to
|
| 696 |
+
disclose his name or address or other personal information , or state any matter which
|
| 697 |
+
might lead to the ir discovery.
|
| 698 |
+
(2) Unless otherwise provided, t he information and the identity of the witness
|
| 699 |
+
or inform ant shall be held to be secret between the Authority and the witness or informer;
|
| 700 |
+
and all matters relating to such information should be privileged and sh all not be disclosed
|
| 701 |
+
in any proceedings before any court, tribunal, commission, or authority.
|
| 702 |
+
(3) The Authority take all necessary and reasonable steps to protect the safety
|
| 703 |
+
and welfare of a witness or informant , and such t he protection shall extend to persons by
|
| 704 |
+
virtue of being related to a witness or info rmant, or on account of a testimony given by a
|
| 705 |
+
witness or informer , or for any other reason upon which the Authority may consider
|
| 706 |
+
sufficient. ”.
|
| 707 |
+
Section 67 of Act 10 38 amended
|
| 708 |
+
34. The principal enactment is amended by :
|
| 709 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 710 |
+
22 (a) the substitution for the heading before section 67 “Other Online Sexual
|
| 711 |
+
Offences, of
|
| 712 |
+
“Other cybercrime”.
|
| 713 |
+
(b) the substitution for subsection (2) of section 67 , of
|
| 714 |
+
“(2) A person who contravenes subsection (1) commits an offence and is liable
|
| 715 |
+
on summary conviction to a term of imprisonment of not less than three years and not
|
| 716 |
+
more than ten years or a fine of not less than two hundred and fifty penalty units and not
|
| 717 |
+
more than twenty -five thousand penalty units or to both.”.
|
| 718 |
+
Section 67A inserted
|
| 719 |
+
35. The principal enactment is amended by the insertion after section 67, of
|
| 720 |
+
“Cyberbullying and online harassment
|
| 721 |
+
67A. (1) A person shall not use the internet or any form of electronic medium
|
| 722 |
+
or technology to bully a child or an adult.
|
| 723 |
+
(2) A person shall not use a computer or any other electronic medium to
|
| 724 |
+
(a) send a threatening message or a lewd message to a child;
|
| 725 |
+
(b) make sexual advances towards a child; or
|
| 726 |
+
(c) persistently make contact with
|
| 727 |
+
(i) a child, whether or not the contact is acceptable; or
|
| 728 |
+
(ii) another person where the contact is unacceptable.
|
| 729 |
+
(3) A person shall not create a false identity online or a fake social media
|
| 730 |
+
profile for the purposes of following
|
| 731 |
+
(a) a child, whether or not the contact is acceptable; or
|
| 732 |
+
(b) an adult, where the contact is unacceptable.
|
| 733 |
+
(4) A person shall not use any online computer service or any other
|
| 734 |
+
electronic device to:
|
| 735 |
+
(a) track the location of a child;
|
| 736 |
+
(b) monitor the real -world activities of a child;
|
| 737 |
+
(c) track the location of another person without the consent of that
|
| 738 |
+
person;
|
| 739 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 740 |
+
23 (d) monitor the real -world activities of another person without the
|
| 741 |
+
consent of that person; or
|
| 742 |
+
(e) obsessively track the location of
|
| 743 |
+
(i) a child; or
|
| 744 |
+
(ii) another person without the consent of that person.
|
| 745 |
+
(5) Despite subsection (4), a parent or legal guardian may for the
|
| 746 |
+
purposes of ensuring the safety of a child, track the location of the child, or monitor
|
| 747 |
+
the real -world activities of the child.
|
| 748 |
+
(6) A person shall not use an electronic device or medium to send
|
| 749 |
+
(a) an unwanted,
|
| 750 |
+
(b) an unsolicited,
|
| 751 |
+
(c) a frightening,
|
| 752 |
+
(d) an obscene,
|
| 753 |
+
(e) a harassing, or
|
| 754 |
+
(f) a threatening electronic mail, text message or instant message to a
|
| 755 |
+
child or an adult.
|
| 756 |
+
(7) A service provider of a user -to-user service including
|
| 757 |
+
(a) a chatroom,
|
| 758 |
+
(b) a social network site,
|
| 759 |
+
(c) an online gaming platform, or
|
| 760 |
+
(d) a virtual, augmented and mixed reality shall protect a child from all
|
| 761 |
+
forms of online violence and cyberbullying from users of the user -
|
| 762 |
+
to-user service.
|
| 763 |
+
(8) A person shall not use an electronic device or an electronic medium
|
| 764 |
+
to deliberately spread false or misleading information with the intent
|
| 765 |
+
to deceive or manipulate a person or a fact.
|
| 766 |
+
(9) A person who contravenes subsections (1), (2), (3), (4) or (6) commits
|
| 767 |
+
an offence and is liable on summary conviction to a fine of not less
|
| 768 |
+
than two thousand, five hundred penalty units and not more than
|
| 769 |
+
five thousand penalty units or to a term of impriso nment of not less
|
| 770 |
+
than one year and not more than three years or to both.
|
| 771 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 772 |
+
24 (10) A person who contravenes sub regulation (7) or (8) commits an
|
| 773 |
+
offence and is liable on summary conviction to a fine of not less than
|
| 774 |
+
five thousand penalty units and not more than twenty -five thousand
|
| 775 |
+
penalty units or to a term of imprisonment of not less than three
|
| 776 |
+
years and not more than five years or to both.
|
| 777 |
+
(11) For the purpose of this Act, “cyberbullying” includes any digital
|
| 778 |
+
communication and any other activity which strips the recipient of
|
| 779 |
+
the dignity of the recipient, or causes fear or physical or emotional
|
| 780 |
+
harm such as
|
| 781 |
+
(a) repeatedly sending offensive, rude and insulting messages to
|
| 782 |
+
a person;
|
| 783 |
+
(b) distributing derogatory information about a child or any other
|
| 784 |
+
person;
|
| 785 |
+
(c) posting or sending offensive photos of a child or any other
|
| 786 |
+
person, whether or not the photos
|
| 787 |
+
(i) have been digitally altered, or
|
| 788 |
+
(ii) were taken with the consent of the victim, with the
|
| 789 |
+
intention to humiliate or embarrass the victim;
|
| 790 |
+
(d) breaking into an electronic mail, social networking or any
|
| 791 |
+
electronic account and using the virtual identity of a victim to
|
| 792 |
+
send, upload or distribute embarrassing materials to or about
|
| 793 |
+
others;
|
| 794 |
+
(e) sharing sensitive personal information or any embarrassing
|
| 795 |
+
information, or tricking a child or any person to reveal
|
| 796 |
+
personal or embarrassing information about that child or any
|
| 797 |
+
other person and sharing the information obtained with
|
| 798 |
+
others;
|
| 799 |
+
(f) repeatedly sending threatening or intimidating messages with
|
| 800 |
+
threats of harm, or engaging in online activities that cause fear
|
| 801 |
+
in a child or puts any other person in fear;
|
| 802 |
+
(g) sending messages to another person telling the person to
|
| 803 |
+
commit suicide;
|
| 804 |
+
(h) sending grossly offensive, indecent or obscene
|
| 805 |
+
communication with the intention of causing emotional
|
| 806 |
+
distress to a child or any other person; or
|
| 807 |
+
(i) sending electronic messages that denigrate a child or any other
|
| 808 |
+
person on the basis of
|
| 809 |
+
(i) colour;
|
| 810 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 811 |
+
25 (ii) race;
|
| 812 |
+
(iii) ethnic or national origins;
|
| 813 |
+
(iv) religion ;
|
| 814 |
+
(v) gender; or
|
| 815 |
+
(vi) disability. ”.
|
| 816 |
+
Section 67B inserted
|
| 817 |
+
36. The principal enactment is amended by the insertion after section 67A, of
|
| 818 |
+
“Cyberstalking of a person
|
| 819 |
+
“67B. (1) In furtherance of section 65 of the Act, a person shall not use the
|
| 820 |
+
internet, chatroom , a social network site, an online gaming platform, or a virtual,
|
| 821 |
+
augmented and mixed reality to
|
| 822 |
+
(a) harass or stalk a person ;
|
| 823 |
+
(b) assume a false identity to anonymously harass or stalk a person;
|
| 824 |
+
(c) create false information about a person; or
|
| 825 |
+
(d) post or disseminate false information about a person.
|
| 826 |
+
(2) A person who contravenes subsection (1) commits an offence and is liable
|
| 827 |
+
on summary conviction to a term of imprisonment of not less than one year and not more
|
| 828 |
+
than ten years or a fine of less than one hundred penalty units and not more than ten
|
| 829 |
+
thousand penalty units or to both.”.
|
| 830 |
+
Section 68 of Act 10 38 amended
|
| 831 |
+
37. The principal enactment is amended in section 68 by substitution for subsection
|
| 832 |
+
(2), of
|
| 833 |
+
“(2) A person who contravenes subsection (1) commits an offence and is liable
|
| 834 |
+
on summary conviction to a term of imprisonment of not less than three years and not
|
| 835 |
+
more than ten years or a fine of less than two hundred and fifty penalty units and not more
|
| 836 |
+
than twenty -five thousand penalty units or to both.”.
|
| 837 |
+
Section 83 of Act 1038 amended
|
| 838 |
+
38. The principal enactment is amended in section 83 by:
|
| 839 |
+
(a) the substitution for paragraph (e) of subsection (4), of
|
| 840 |
+
“(e) requesting for information regarding domain name registration ;”.
|
| 841 |
+
(b) the addition after paragraph (e) of subsection (4) , of
|
| 842 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 843 |
+
26 “(f) expedited disclosure of specified stored computer data in a service provider's
|
| 844 |
+
possession or control in an emergency situation without a request for mutual
|
| 845 |
+
assistance ;”.
|
| 846 |
+
(c) the addition after paragraph (f) of subsection (4), of
|
| 847 |
+
“(g) any other matter related to paragraphs (a) to ( f).”.
|
| 848 |
+
(d) the addition after paragraph (g) of subsection (4), of
|
| 849 |
+
“(5) For the purpose of this section an emergency means, a situation in which
|
| 850 |
+
there is a significant and imminent risk to the life or safety of any natural person. ”.
|
| 851 |
+
Section 90 of Act 10 38 amended
|
| 852 |
+
39. The principal enactment is amended in section 90 by the addition after subsection
|
| 853 |
+
(2), of
|
| 854 |
+
“(3) Despite section 90(1) and section 90A , the Circuit Court shall have the
|
| 855 |
+
jurisdiction to try an offence under sections 62 to 68 A of this Act. ”.
|
| 856 |
+
Section 91 of Act 10 38 amended
|
| 857 |
+
40. The principal enactment is amended in section 91 by the addition after paragraph
|
| 858 |
+
(h), of
|
| 859 |
+
“(i) the designation of Sectoral Computer Emergency Response Team;
|
| 860 |
+
(j) the accreditation of Sectoral Computer Emergency Response Team;
|
| 861 |
+
(k) the accreditation of cybersecurity establishments ;
|
| 862 |
+
(l) the accreditation of non -profit cybersecurity institution ;
|
| 863 |
+
(m) the protection of children ;
|
| 864 |
+
(n) safeguarding the development and deployment of innovative and emerging
|
| 865 |
+
technologies or solutions ;
|
| 866 |
+
(o) licensing of cyber security service providers ;
|
| 867 |
+
(p) accreditation of cybersecurity professionals and practitioners;
|
| 868 |
+
(p) any other matter required for ensuring the cybersecurity of the country .”.
|
| 869 |
+
Section 92 of Act 10 38 amended
|
| 870 |
+
41. The principal enactment is amended in section 92 by :
|
| 871 |
+
(a) the substitution for subsection (1), of
|
| 872 |
+
“(1) the Authority may issue directives to an owner of a critical information
|
| 873 |
+
infrastructure, a cybersecurity service provider or service provider , innovators, developers
|
| 874 |
+
for the purpose of ensuring the cybersecurity of the country.
|
| 875 |
+
(b) the substitution for subsection (2), of
|
| 876 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 877 |
+
27 “(2) An owner of a critical information infrastructure, cybersecurity service
|
| 878 |
+
provider or service provider, innovator, developer or any relevant person who fails to
|
| 879 |
+
comply with the directives issued under this section is liable to pay to the Authority the
|
| 880 |
+
admin istrative penalty specified in the Second Schedule .”.
|
| 881 |
+
(c) the insertion after subsection (2) , of
|
| 882 |
+
“(3) The Authority may issue directives to innovators, developers, service
|
| 883 |
+
providers and cybersecurity service providers for the purpose of securing the adoption and
|
| 884 |
+
deployment of innovative and emerging technologies or solutions.”.
|
| 885 |
+
Section 94 of Act 10 38 amended
|
| 886 |
+
42. The principal enactment is amended by the substitution for section 94, of
|
| 887 |
+
“(1) A person who, without lawful authority accesses or retrieves subscriber
|
| 888 |
+
information or intercepts traffic data or content data, commits an offence and is liable on
|
| 889 |
+
summary conviction to a fine of not less than two thousand five hundred penalty units and
|
| 890 |
+
not more than fifteen thousand penalty units or to a term of imprisonment of not less than
|
| 891 |
+
two years and not more than five years or, to both.
|
| 892 |
+
(2) An attempt to gain access to a critical information infrastructure or its
|
| 893 |
+
dependencies, successful or not, constitutes an unlawful access to the critical information
|
| 894 |
+
infrastructure.
|
| 895 |
+
(3) Any person who attempts to secure access to a critical information
|
| 896 |
+
infrastructure or its dependencies, successful or not commits an offence and is liable on
|
| 897 |
+
summary conviction to a term of imprisonment of not less than two years and not more
|
| 898 |
+
than five years or a fine of not less than four thousand penalty units and not more than
|
| 899 |
+
twenty -five thousand penalty units or to both.
|
| 900 |
+
(4) The tampering and or destruction of critical information infrastructure or its
|
| 901 |
+
components which affects the confidentiality, integrity or availability of the critical
|
| 902 |
+
information infrastructure constitutes unlawful access to critical information infrastructure ,
|
| 903 |
+
and a person shall be subject to the same criminal penalties specified in subsection (1). ”.
|
| 904 |
+
Section 94A inserted
|
| 905 |
+
43. The principal enactment is amended by the insertion after section 94, of
|
| 906 |
+
“Computer related Forgery
|
| 907 |
+
95A. (1) A person shall not intentionally and without right, input, alter, delete
|
| 908 |
+
or suppress computer data resulting in inauthentic data with the intent that they be
|
| 909 |
+
considered or acted upon for legal purposes as if they were authentic, regardless of whether
|
| 910 |
+
or no t the data are directly readable and intelligible.”
|
| 911 |
+
(2) A person who contravenes subsection (1) commits an offence and
|
| 912 |
+
shall be deemed to have committed the offence of forger y under Chapter Two of the
|
| 913 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 914 |
+
28 Criminal Offences Act, 1960 (Act 29) , and shall liable on summary conviction to the same
|
| 915 |
+
punishment for that particular kind of forgery under the law. ”.
|
| 916 |
+
Section 94B inserted
|
| 917 |
+
44. The principal enactment is amended by the inser tion after section 94A, of
|
| 918 |
+
“Computer related Fraud
|
| 919 |
+
95B. (1) A person shall not intentionally and without right, cause the loss of
|
| 920 |
+
property to another person by means of:
|
| 921 |
+
(a) Any input, alteration, deletion or suppression of computer data;
|
| 922 |
+
(b) Any interference with the functioning of a computer system;
|
| 923 |
+
(c) Any deception as to factual circumstances made through a computer
|
| 924 |
+
system that causes a person to do or omit to do anything which that person would not
|
| 925 |
+
otherwise do or omit to do; with the fraudulent or dishonest intent of procuring for oneself
|
| 926 |
+
or for another person, without right, a gain in money or other property .
|
| 927 |
+
(2) A person who contravenes subsection (1) commits an offence and
|
| 928 |
+
shall be deemed to have committed the offence of defrauding by false pretence under
|
| 929 |
+
section 13 1 of the Criminal Offences Act, 1960 (Act 29) , and shall liable on summary
|
| 930 |
+
conviction to the same punishment for defrauding by false pretence under that law. ”.
|
| 931 |
+
Section 97 of Act 10 38 amended
|
| 932 |
+
45. The principal enactment is amended in section 97 by:
|
| 933 |
+
(a) the insertion before “Authority ”, of
|
| 934 |
+
“Artificial Intelligence is technology that enables computers and machines to
|
| 935 |
+
simulate human learning, comprehension, problem solving, decision -making, creativity and
|
| 936 |
+
autonomy ;”;
|
| 937 |
+
“block-chain based technology ” means a n advanced decentralised digital record or
|
| 938 |
+
digital ledger of transactions, that enables recording of transactions, tracking of assets, and
|
| 939 |
+
sharing of records across computer networks in a transparent and immutable way; ”.
|
| 940 |
+
“big data” means a collection of extremely large organized, semi -structured, and
|
| 941 |
+
unstructured information that grows exponentially over time;” ;
|
| 942 |
+
(b) the insertion after “child”, of
|
| 943 |
+
“cloud technology” means technology that delivers computing services and offers
|
| 944 |
+
on-demand access to computing resources such as physical or virtual services, data
|
| 945 |
+
storage, servers, databases, networking, intelligence, analytics, and software over the
|
| 946 |
+
internet ;”;
|
| 947 |
+
(c) the insertion after “ cloud technology”, of
|
| 948 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 949 |
+
29 “confiscation”, which includes forfeiture where applicable, shall mean the
|
| 950 |
+
permanent deprivation of property by order of a court or other competent
|
| 951 |
+
authority;”;
|
| 952 |
+
(d) the insertion after “digital ecosystem” of
|
| 953 |
+
“digital services” means services delivered electronically whether traditionally or
|
| 954 |
+
by internet, with minimal physical interaction, and includes:
|
| 955 |
+
(a) social media platforms;
|
| 956 |
+
(b) e-commerce platforms;
|
| 957 |
+
(c) video -on-demand or streaming platforms;
|
| 958 |
+
(d) messaging applications,
|
| 959 |
+
(e) fintech software platforms;
|
| 960 |
+
(f) online banking platforms;
|
| 961 |
+
(e) the insertion after “Director -General”, of
|
| 962 |
+
“Emergency” shall mean a situation in which there is a significant and imminent
|
| 963 |
+
risk to the life or safety of any natural person;”;
|
| 964 |
+
(f) the insertion after “e -services”, of
|
| 965 |
+
“Freezing” or “seizure” mean temporarily prohibiting the transfer, conversion,
|
| 966 |
+
disposition or movement of property or temporarily assuming custody or control
|
| 967 |
+
of property on the basis of an order issued by a court or other competent
|
| 968 |
+
authority;”;
|
| 969 |
+
(g) the insertion after “interception of warrant ”, of
|
| 970 |
+
“Internet of Things (IoT) means a network of interrelated physical devices, vehicles,
|
| 971 |
+
appliances and other physical objects that connect and exchange data with other
|
| 972 |
+
devices and the cloud across wireless networks , and are typically embedded with
|
| 973 |
+
sensors, processing ability and software and can include mechanical and digital
|
| 974 |
+
machines and consumer objects ;”;
|
| 975 |
+
(h) the substitution for “computer system ”, of
|
| 976 |
+
“computer system” means an arrangement of interconnected computers that is
|
| 977 |
+
designed to perform one or more specific functions, and includes
|
| 978 |
+
(a) an information processing system;
|
| 979 |
+
(b) an operational technology system, a programmable logic controller, a
|
| 980 |
+
supervisory control and data acquisition system, or a distributed control
|
| 981 |
+
system; and
|
| 982 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 983 |
+
30 (c) dependency systems that support the functionality of a computer or
|
| 984 |
+
computer network ;”;
|
| 985 |
+
(i) the substitution for “law enforcement agency”, of
|
| 986 |
+
“law enforcement agency” means
|
| 987 |
+
(a) The Police
|
| 988 |
+
(b) The Customs Division of the Ghana Revenue Authority and
|
| 989 |
+
(c) Cyber Security Authority
|
| 990 |
+
(d) Any other agency authorised by law to exercise the powers of the Police
|
| 991 |
+
(j) the insertion after “prohibited intimate image and visual recording”, of
|
| 992 |
+
“property” mean s assets of every kind, in the country or elsewhere whether
|
| 993 |
+
corporeal or incorporeal, movable or immovable, tangible or intangible, including
|
| 994 |
+
virtual assets, and legal documents or instruments evidencing title to, or interest in,
|
| 995 |
+
such property assets;”;
|
| 996 |
+
(k) the insertion after “property”, of
|
| 997 |
+
“property of or in the possession or control of a person” includes a gift made by
|
| 998 |
+
that person;
|
| 999 |
+
(l) the insertion after “publish”, of
|
| 1000 |
+
“quantum computing ” means technology that solves complex problems based on
|
| 1001 |
+
principles of quantum mechanics;” .
|
| 1002 |
+
Section 98 of Act 1058 amended
|
| 1003 |
+
46. The principal enactment is amended by substitution for section 98, of
|
| 1004 |
+
“Repeals and savings
|
| 1005 |
+
(1) Sections 118 and 136 of the Electronic Transaction Act, 2008 (Act 772)
|
| 1006 |
+
are repealed.
|
| 1007 |
+
(2) Sections 35 to 40 this Act repeals sections 55 to 62 of the Electronic
|
| 1008 |
+
Transaction Act, 2008 (Act 772) .
|
| 1009 |
+
(3) Despite the repeal of sections, any Regulations, bye -laws, directives,
|
| 1010 |
+
notices, orders, directions, appointments or other acts lawfully made or
|
| 1011 |
+
done under the repealed enactment and in force immediately before the
|
| 1012 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 1013 |
+
31 coming into force of this Act shall be deemed to have been made or done
|
| 1014 |
+
under this Act and shall continue to have effect until instruments issued
|
| 1015 |
+
or made until revoked, cancelled, withdrawn or terminated.”.
|
| 1016 |
+
Section 99 of Act 10 38 amended
|
| 1017 |
+
47. The Extradition Act, 1960 (Act 22) is amended by:
|
| 1018 |
+
(a) the addition after subsection (3) of section , of
|
| 1019 |
+
“(4) Despite subsection (1), for the purposes of extradition , Cybercrime and
|
| 1020 |
+
Cyber Offences under the First Schedule to this Act, in the absence of a separate
|
| 1021 |
+
arrangement, bilateral or multilateral agreement between Ghana and another signatory of
|
| 1022 |
+
the Budapest Convention on Cybercrime or any other convention , extradition of fugitive
|
| 1023 |
+
criminals may still take place between Ghana and another signatory of the Budapest
|
| 1024 |
+
Convention on Cybercrime or any other convention and shall be governed by the
|
| 1025 |
+
provisions of the said convention. ”.
|
| 1026 |
+
First schedule to Act 1038 amended
|
| 1027 |
+
48. The principal enactment is amended by substitution for the preamble, of
|
| 1028 |
+
“A service provided for reward or a not -for profit basis intended primarily for or aimed at
|
| 1029 |
+
ensuring or safeguarding the cybersecurity of a computer or computer system belonging
|
| 1030 |
+
to a person and includes.”.
|
| 1031 |
+
Second schedule to Act 10 38 amended
|
| 1032 |
+
49. The principal enactment is amended by substitution for administrative penalties for
|
| 1033 |
+
sections 36(4), 39(2)(a), 39(2)(b), 39(2)(c) and 92(2) and addition of administrative penalties
|
| 1034 |
+
for sections 36(1) and 39(3)(c) .
|
| 1035 |
+
No. Sections of
|
| 1036 |
+
Act creating
|
| 1037 |
+
contravention Contravention Administrative
|
| 1038 |
+
Penalty
|
| 1039 |
+
1. 36(1) The Owner of the designated critical
|
| 1040 |
+
information infrastructure shall register
|
| 1041 |
+
their critical information infrastructure
|
| 1042 |
+
with the Authority, and shall pay the
|
| 1043 |
+
prescribed annual registration and
|
| 1044 |
+
designation fee as determined by the
|
| 1045 |
+
Authority or stipulated i n a legislative
|
| 1046 |
+
instrument. Not less than five
|
| 1047 |
+
thousand penalty
|
| 1048 |
+
units, not more than
|
| 1049 |
+
fifty thousand
|
| 1050 |
+
penalty units .
|
| 1051 |
+
2. 36(4) Owner of a registered critical information
|
| 1052 |
+
infrastructure failing to inform the
|
| 1053 |
+
Authority within seven days of the change
|
| 1054 |
+
in legal ownership of the registered critical Not less than five
|
| 1055 |
+
thousand penalty
|
| 1056 |
+
units, not more than
|
| 1057 |
+
CYBERSECURITY (AMENDMENT) BILL, 2025
|
| 1058 |
+
32 information infrastructure.
|
| 1059 |
+
fifty thousand
|
| 1060 |
+
penalty units.
|
| 1061 |
+
3. 39(2)(a) Owner of a critical information
|
| 1062 |
+
infrastructure failing to report a
|
| 1063 |
+
cybersecurity incident .
|
| 1064 |
+
Not less than ten
|
| 1065 |
+
thousand penalty
|
| 1066 |
+
units, not more than
|
| 1067 |
+
fifty thousand
|
| 1068 |
+
penalty units.
|
| 1069 |
+
4. 39(2)(b) Owner of a critical information
|
| 1070 |
+
infrastructure failing to cause an audit to be
|
| 1071 |
+
performed on the critical information
|
| 1072 |
+
infrastructure .
|
| 1073 |
+
Not less than five
|
| 1074 |
+
thousand penalty
|
| 1075 |
+
units, not more than
|
| 1076 |
+
twenty thousand
|
| 1077 |
+
penalty units.
|
| 1078 |
+
5. 39(3)(c) Owner of a critical information
|
| 1079 |
+
infrastructure failing to submit a copy of
|
| 1080 |
+
the audit report to the Authority.
|
| 1081 |
+
Not less than five
|
| 1082 |
+
thousand penalty
|
| 1083 |
+
units, not more than
|
| 1084 |
+
twenty thousand
|
| 1085 |
+
penalty units.
|
| 1086 |
+
6. 92(2) Owner of a critical information
|
| 1087 |
+
infrastructure, a cybersecurity service
|
| 1088 |
+
provider or a provider of a digital service
|
| 1089 |
+
failing to comply with a directive issued by
|
| 1090 |
+
the Authority . Not less than ten
|
| 1091 |
+
thousand penalty
|
| 1092 |
+
units, not more than
|
| 1093 |
+
fifty thousand
|
| 1094 |
+
penalty units
|
| 1095 |
+
Third schedule to Act 1038 amended
|
| 1096 |
+
50. The principal enactment is amended by addition after the “Oath of Secrecy”, of
|
| 1097 |
+
The Official Oath
|
| 1098 |
+
(section 5)
|
| 1099 |
+
I,
|
| 1100 |
+
……………………………………………………………..……………………………
|
| 1101 |
+
do (in the name of the Almighty God swear) (solemnly affirm) that I will at all times well
|
| 1102 |
+
and truly serve the Republic of Ghana in the office of …….and that I will uphold, preserve,
|
| 1103 |
+
protect and defend the constituti on of the Republic of Ghana as by law established (so
|
| 1104 |
+
help me God).
|
assets/example_bills/cybersecurity-amendment-bill-2025/metadata.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generated_at": "2026-06-01T11:48:29.371678+00:00",
|
| 3 |
+
"provider": "qwen",
|
| 4 |
+
"model": "Qwen/Qwen3-32B:cheapest",
|
| 5 |
+
"source_url": "https://www.csa.gov.gh/resources/Cybersecurity%20%28Amendment%29%20Draft%20Bill%202025%20final%2015102025.pdf",
|
| 6 |
+
"document_hash": "d879a4473b3631906b7c4e965195d53f4693a722ce7644e187ab76fca33ef763",
|
| 7 |
+
"chunk_size": 350,
|
| 8 |
+
"chunk_overlap": 60,
|
| 9 |
+
"embedding_model": "sentence-transformers/all-MiniLM-L6-v2"
|
| 10 |
+
}
|
assets/example_bills/manifest.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "cybersecurity-amendment-bill-2025",
|
| 4 |
+
"title": "Cybersecurity (Amendment) Bill, 2025",
|
| 5 |
+
"source_url": "https://www.csa.gov.gh/resources/Cybersecurity%20%28Amendment%29%20Draft%20Bill%202025%20final%2015102025.pdf",
|
| 6 |
+
"document_hash": "d879a4473b3631906b7c4e965195d53f4693a722ce7644e187ab76fca33ef763",
|
| 7 |
+
"asset_dir": "cybersecurity-amendment-bill-2025"
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"id": "data-harmonisation-bill",
|
| 11 |
+
"title": "Data Harmonisation Bill",
|
| 12 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/Data-Harmonisation-Bill.pdf",
|
| 13 |
+
"document_hash": "",
|
| 14 |
+
"asset_dir": "data-harmonisation-bill"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"id": "data-protection-bill-2025",
|
| 18 |
+
"title": "Data Protection Bill, 2025",
|
| 19 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/DATA-PROTECTION-BILL.pdf",
|
| 20 |
+
"document_hash": "",
|
| 21 |
+
"asset_dir": "data-protection-bill-2025"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"id": "digital-economy-and-innovation-development-fund-bill-2025",
|
| 25 |
+
"title": "Digital Economy and Innovation Development Fund Bill, 2025",
|
| 26 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/DIGITAL-ECONOMY-AND-INNOVATION-DEVELOPMENT-FUND-BILL.pdf",
|
| 27 |
+
"document_hash": "",
|
| 28 |
+
"asset_dir": "digital-economy-and-innovation-development-fund-bill-2025"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"id": "electronic-communications-bill-2025",
|
| 32 |
+
"title": "Electronic Communications Bill, 2025",
|
| 33 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/NCA-Electronic-Communications-Bill-2025.pdf",
|
| 34 |
+
"document_hash": "",
|
| 35 |
+
"asset_dir": "electronic-communications-bill-2025"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"id": "electronic-transactions-bill-2025",
|
| 39 |
+
"title": "Electronic Transactions Bill, 2025",
|
| 40 |
+
"source_url": "https://nita.gov.gh/wp-content/uploads/2025/NITA-ETA-public-consultation-2025.pdf",
|
| 41 |
+
"document_hash": "",
|
| 42 |
+
"asset_dir": "electronic-transactions-bill-2025"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"id": "emerging-technologies-bill-2025",
|
| 46 |
+
"title": "Emerging Technologies Bill, 2025",
|
| 47 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/MoCDTI-EMERGING-TECHNOLOGIES-DRAFT-BILL-2025-2.pdf",
|
| 48 |
+
"document_hash": "",
|
| 49 |
+
"asset_dir": "emerging-technologies-bill-2025"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"id": "ghana-domain-name-registry-bill-2025",
|
| 53 |
+
"title": "Ghana Domain Name Registry Bill, 2025",
|
| 54 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/Ghana-Domain-Name-Registry-Bill.pdf",
|
| 55 |
+
"document_hash": "",
|
| 56 |
+
"asset_dir": "ghana-domain-name-registry-bill-2025"
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"id": "ghana-meteorological-authority-bill-2025",
|
| 60 |
+
"title": "Ghana Meteorological Authority Bill, 2025",
|
| 61 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/GHANA-METEOROLOGICAL-AUTHORITY-BILL.pdf",
|
| 62 |
+
"document_hash": "",
|
| 63 |
+
"asset_dir": "ghana-meteorological-authority-bill-2025"
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"id": "kofi-annan-centre-of-excellence-in-ict-bill-2025",
|
| 67 |
+
"title": "Kofi Annan Centre of Excellence in ICT Bill, 2025",
|
| 68 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/Kofi-Annan-Centre-of-Excellence-Bill.pdf",
|
| 69 |
+
"document_hash": "",
|
| 70 |
+
"asset_dir": "kofi-annan-centre-of-excellence-in-ict-bill-2025"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"id": "mdhi-bill-2025",
|
| 74 |
+
"title": "MDHI Bill, 2025",
|
| 75 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/MISINFORMATION-DISINFORMATION-HATE-SPEECH-1.pdf",
|
| 76 |
+
"document_hash": "",
|
| 77 |
+
"asset_dir": "mdhi-bill-2025"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"id": "national-communications-authority-bill-2025",
|
| 81 |
+
"title": "National Communications Authority Bill, 2025",
|
| 82 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/NCA-National-Communications-Authority-Bill-2025.pdf",
|
| 83 |
+
"document_hash": "",
|
| 84 |
+
"asset_dir": "national-communications-authority-bill-2025"
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"id": "national-information-technology-authority-bill-2025",
|
| 88 |
+
"title": "National Information Technology Authority Bill, 2025",
|
| 89 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/NITA-NATIONAL-INFORMATION-TECHNOLOGY-AUTHORITY-BILL_-10-07-25.pdf",
|
| 90 |
+
"document_hash": "",
|
| 91 |
+
"asset_dir": "national-information-technology-authority-bill-2025"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"id": "postal-courier-and-logistics-commission-bill-2025",
|
| 95 |
+
"title": "Postal, Courier and Logistics Commission Bill, 2025",
|
| 96 |
+
"source_url": "https://moc.gov.gh/wp-content/uploads/2023/03/POSTAL-COURIER-LOGISTICS-COMMISSION-BILL.pdf",
|
| 97 |
+
"document_hash": "",
|
| 98 |
+
"asset_dir": "postal-courier-and-logistics-commission-bill-2025"
|
| 99 |
+
}
|
| 100 |
+
]
|
services/__init__.py
ADDED
|
File without changes
|
services/example_bills.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared example bill manifest helpers."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from urllib.parse import urlsplit, urlunsplit
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 13 |
+
EXAMPLE_BILL_ASSETS_ROOT = PROJECT_ROOT / "assets" / "example_bills"
|
| 14 |
+
EXAMPLE_BILL_MANIFEST_PATH = EXAMPLE_BILL_ASSETS_ROOT / "manifest.json"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass(frozen=True)
|
| 18 |
+
class ExampleBill:
|
| 19 |
+
id: str
|
| 20 |
+
title: str
|
| 21 |
+
source_url: str
|
| 22 |
+
document_hash: str
|
| 23 |
+
asset_dir: str
|
| 24 |
+
|
| 25 |
+
@property
|
| 26 |
+
def root_dir(self) -> Path:
|
| 27 |
+
return EXAMPLE_BILL_ASSETS_ROOT / self.asset_dir
|
| 28 |
+
|
| 29 |
+
@property
|
| 30 |
+
def analysis_path(self) -> Path:
|
| 31 |
+
return self.root_dir / "analysis.json"
|
| 32 |
+
|
| 33 |
+
@property
|
| 34 |
+
def document_path(self) -> Path:
|
| 35 |
+
return self.root_dir / "document.txt"
|
| 36 |
+
|
| 37 |
+
@property
|
| 38 |
+
def chunks_path(self) -> Path:
|
| 39 |
+
return self.root_dir / "chunks.json"
|
| 40 |
+
|
| 41 |
+
@property
|
| 42 |
+
def metadata_path(self) -> Path:
|
| 43 |
+
return self.root_dir / "metadata.json"
|
| 44 |
+
|
| 45 |
+
@property
|
| 46 |
+
def vector_store_dir(self) -> Path:
|
| 47 |
+
return self.root_dir / "vector_store"
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def normalize_example_url(url: str) -> str:
|
| 51 |
+
stripped = url.strip()
|
| 52 |
+
parsed = urlsplit(stripped)
|
| 53 |
+
normalized_path = parsed.path.rstrip("/") or parsed.path
|
| 54 |
+
return urlunsplit((parsed.scheme.lower(), parsed.netloc.lower(), normalized_path, parsed.query, ""))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@lru_cache(maxsize=1)
|
| 58 |
+
def load_example_bills() -> tuple[ExampleBill, ...]:
|
| 59 |
+
payload = json.loads(EXAMPLE_BILL_MANIFEST_PATH.read_text(encoding="utf-8"))
|
| 60 |
+
return tuple(ExampleBill(**item) for item in payload)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def reload_example_bills() -> tuple[ExampleBill, ...]:
|
| 64 |
+
load_example_bills.cache_clear()
|
| 65 |
+
return load_example_bills()
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def example_bill_titles() -> list[str]:
|
| 69 |
+
return [bill.title for bill in load_example_bills()]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def example_bill_urls_by_title() -> dict[str, str]:
|
| 73 |
+
return {bill.title: bill.source_url for bill in load_example_bills()}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def example_bill_by_title(title: str) -> ExampleBill | None:
|
| 77 |
+
for bill in load_example_bills():
|
| 78 |
+
if bill.title == title:
|
| 79 |
+
return bill
|
| 80 |
+
return None
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def example_bill_by_url(url: str | None) -> ExampleBill | None:
|
| 84 |
+
if not url:
|
| 85 |
+
return None
|
| 86 |
+
normalized = normalize_example_url(url)
|
| 87 |
+
for bill in load_example_bills():
|
| 88 |
+
if normalize_example_url(bill.source_url) == normalized:
|
| 89 |
+
return bill
|
| 90 |
+
return None
|
services/ingest.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities for ingesting and normalizing documents."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import io
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import tempfile
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
from typing import Iterable
|
| 11 |
+
|
| 12 |
+
import requests
|
| 13 |
+
from docx import Document as DocxDocument
|
| 14 |
+
from lxml import html
|
| 15 |
+
from readability import Document as ReadabilityDocument
|
| 16 |
+
from pypdf import PdfReader
|
| 17 |
+
|
| 18 |
+
from config import MAX_UPLOAD_SIZE_MB, TIMEOUT_SECONDS
|
| 19 |
+
|
| 20 |
+
SUPPORTED_EXTENSIONS = {
|
| 21 |
+
".pdf": "application/pdf",
|
| 22 |
+
".txt": "text/plain",
|
| 23 |
+
".md": "text/markdown",
|
| 24 |
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
_CONTROL_CHARS_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class IngestionError(RuntimeError):
|
| 31 |
+
pass
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _ensure_size_limit(file_size: int) -> None:
|
| 35 |
+
max_bytes = MAX_UPLOAD_SIZE_MB * 1024 * 1024
|
| 36 |
+
if file_size > max_bytes:
|
| 37 |
+
raise IngestionError(
|
| 38 |
+
f"File exceeds maximum size of {MAX_UPLOAD_SIZE_MB} MB."
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def extract_text_from_pdf(data: bytes) -> str:
|
| 43 |
+
reader = PdfReader(io.BytesIO(data))
|
| 44 |
+
text_chunks: list[str] = []
|
| 45 |
+
for page in reader.pages:
|
| 46 |
+
chunk = page.extract_text() or ""
|
| 47 |
+
text_chunks.append(chunk.strip())
|
| 48 |
+
return _normalize_text(_repair_known_collapsed_terms("\n".join(text_chunks)))
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def extract_text_from_docx(data: bytes) -> str:
|
| 52 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
|
| 53 |
+
tmp.write(data)
|
| 54 |
+
tmp_path = tmp.name
|
| 55 |
+
try:
|
| 56 |
+
document = DocxDocument(tmp_path)
|
| 57 |
+
paragraphs = [p.text.strip() for p in document.paragraphs if p.text.strip()]
|
| 58 |
+
return _normalize_text("\n".join(paragraphs))
|
| 59 |
+
finally:
|
| 60 |
+
os.unlink(tmp_path)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def extract_text_from_text(data: bytes) -> str:
|
| 64 |
+
return _normalize_text(data.decode("utf-8", errors="ignore"))
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def ingest_file(filename: str, file_bytes: bytes) -> str:
|
| 68 |
+
_ensure_size_limit(len(file_bytes))
|
| 69 |
+
ext = os.path.splitext(filename)[1].lower()
|
| 70 |
+
if ext not in SUPPORTED_EXTENSIONS:
|
| 71 |
+
raise IngestionError(f"Unsupported file type: {ext}")
|
| 72 |
+
|
| 73 |
+
if ext == ".pdf":
|
| 74 |
+
return extract_text_from_pdf(file_bytes)
|
| 75 |
+
if ext == ".docx":
|
| 76 |
+
return extract_text_from_docx(file_bytes)
|
| 77 |
+
return extract_text_from_text(file_bytes)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def fetch_url_content(url: str) -> str:
|
| 81 |
+
response = requests.get(url, timeout=TIMEOUT_SECONDS)
|
| 82 |
+
response.raise_for_status()
|
| 83 |
+
content_type = (response.headers.get("content-type") or "").lower()
|
| 84 |
+
path = urlparse(url).path.lower()
|
| 85 |
+
|
| 86 |
+
if "application/pdf" in content_type or path.endswith(".pdf"):
|
| 87 |
+
return extract_text_from_pdf(response.content)
|
| 88 |
+
if (
|
| 89 |
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" in content_type
|
| 90 |
+
or path.endswith(".docx")
|
| 91 |
+
):
|
| 92 |
+
return extract_text_from_docx(response.content)
|
| 93 |
+
if any(text_type in content_type for text_type in ("text/plain", "text/markdown")) or any(
|
| 94 |
+
path.endswith(ext) for ext in (".txt", ".md")
|
| 95 |
+
):
|
| 96 |
+
return extract_text_from_text(response.content)
|
| 97 |
+
|
| 98 |
+
readable = ReadabilityDocument(_strip_xml_control_chars(response.text))
|
| 99 |
+
summary_html = _strip_xml_control_chars(readable.summary(html_partial=True))
|
| 100 |
+
parsed = html.fromstring(summary_html)
|
| 101 |
+
title = readable.short_title() or readable.title() or ""
|
| 102 |
+
text_content = parsed.text_content()
|
| 103 |
+
return _normalize_text("\n".join([title, text_content]))
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def combine_sources(sources: Iterable[str]) -> str:
|
| 107 |
+
return _normalize_text("\n\n".join(filter(None, sources)))
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _normalize_text(value: str) -> str:
|
| 111 |
+
value = _strip_xml_control_chars(value)
|
| 112 |
+
clean_lines = [line.strip() for line in value.splitlines()]
|
| 113 |
+
return "\n".join(line for line in clean_lines if line)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def _strip_xml_control_chars(value: str) -> str:
|
| 117 |
+
return _CONTROL_CHARS_RE.sub("", value)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _repair_known_collapsed_terms(value: str) -> str:
|
| 121 |
+
replacements = {
|
| 122 |
+
"NATIONALINFORMATIONTECHNOLOGYAUTHORITY": "National Information Technology Authority",
|
| 123 |
+
"NationalInformationTechnologyAuthority": "National Information Technology Authority",
|
| 124 |
+
}
|
| 125 |
+
for collapsed, expanded in replacements.items():
|
| 126 |
+
value = value.replace(collapsed, expanded)
|
| 127 |
+
return value
|
services/precomputed_assets.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helpers for bundled example-bill analysis assets."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
from services.example_bills import ExampleBill, example_bill_by_url
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass(frozen=True)
|
| 14 |
+
class PrecomputedExampleAsset:
|
| 15 |
+
bill: ExampleBill
|
| 16 |
+
analysis_payload: dict[str, Any]
|
| 17 |
+
document_text: str
|
| 18 |
+
chunks: list[str]
|
| 19 |
+
metadata: dict[str, Any]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def load_precomputed_asset_for_url(url: str | None) -> PrecomputedExampleAsset | None:
|
| 23 |
+
bill = example_bill_by_url(url)
|
| 24 |
+
if bill is None:
|
| 25 |
+
return None
|
| 26 |
+
return load_precomputed_asset(bill)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def load_precomputed_asset(bill: ExampleBill) -> PrecomputedExampleAsset | None:
|
| 30 |
+
if not bill.analysis_path.exists():
|
| 31 |
+
return None
|
| 32 |
+
if not bill.document_path.exists():
|
| 33 |
+
return None
|
| 34 |
+
if not bill.chunks_path.exists():
|
| 35 |
+
return None
|
| 36 |
+
if not bill.vector_store_dir.exists():
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
analysis_payload = json.loads(bill.analysis_path.read_text(encoding="utf-8"))
|
| 40 |
+
document_text = bill.document_path.read_text(encoding="utf-8")
|
| 41 |
+
chunks = json.loads(bill.chunks_path.read_text(encoding="utf-8"))
|
| 42 |
+
metadata = {}
|
| 43 |
+
if bill.metadata_path.exists():
|
| 44 |
+
metadata = json.loads(bill.metadata_path.read_text(encoding="utf-8"))
|
| 45 |
+
|
| 46 |
+
return PrecomputedExampleAsset(
|
| 47 |
+
bill=bill,
|
| 48 |
+
analysis_payload=analysis_payload,
|
| 49 |
+
document_text=document_text,
|
| 50 |
+
chunks=chunks,
|
| 51 |
+
metadata=metadata,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def write_precomputed_asset(
|
| 56 |
+
bill: ExampleBill,
|
| 57 |
+
*,
|
| 58 |
+
analysis_payload: dict[str, Any],
|
| 59 |
+
document_text: str,
|
| 60 |
+
chunks: list[str],
|
| 61 |
+
metadata: dict[str, Any],
|
| 62 |
+
) -> None:
|
| 63 |
+
bill.root_dir.mkdir(parents=True, exist_ok=True)
|
| 64 |
+
bill.analysis_path.write_text(json.dumps(analysis_payload, indent=2), encoding="utf-8")
|
| 65 |
+
bill.document_path.write_text(document_text, encoding="utf-8")
|
| 66 |
+
bill.chunks_path.write_text(json.dumps(chunks, indent=2), encoding="utf-8")
|
| 67 |
+
bill.metadata_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
|
services/providers.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Provider utilities for handling user-supplied API keys."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Any, Dict, Optional
|
| 7 |
+
|
| 8 |
+
from anthropic import Anthropic
|
| 9 |
+
from openai import OpenAI
|
| 10 |
+
|
| 11 |
+
from config import (
|
| 12 |
+
ANTHROPIC_API_KEY,
|
| 13 |
+
COHERE_API_KEY,
|
| 14 |
+
DEFAULT_PROVIDER,
|
| 15 |
+
DEFAULT_COHERE_KEY,
|
| 16 |
+
GEMINI_API_KEY,
|
| 17 |
+
DEFAULT_QWEN_MODEL,
|
| 18 |
+
HF_TOKEN,
|
| 19 |
+
OPENAI_API_KEY,
|
| 20 |
+
PROVIDER_METADATA,
|
| 21 |
+
ProviderConfig,
|
| 22 |
+
ProviderLiteral,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class ProviderClient:
|
| 28 |
+
"""Container for a provider client and default model."""
|
| 29 |
+
|
| 30 |
+
name: ProviderLiteral
|
| 31 |
+
client: Any
|
| 32 |
+
default_model: str
|
| 33 |
+
api_key: str
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
_PROVIDER_MODEL_MAP: Dict[ProviderLiteral, str] = {
|
| 37 |
+
"qwen": DEFAULT_QWEN_MODEL,
|
| 38 |
+
"openai": "gpt-5.5",
|
| 39 |
+
"anthropic": "claude-sonnet-4-6",
|
| 40 |
+
"gemini": "gemini-2.5-flash",
|
| 41 |
+
"cohere": "command-a-reasoning-08-2025",
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
_DEFAULT_PROVIDER_KEYS: Dict[ProviderLiteral, Optional[str]] = {
|
| 46 |
+
"qwen": HF_TOKEN,
|
| 47 |
+
"openai": OPENAI_API_KEY,
|
| 48 |
+
"anthropic": ANTHROPIC_API_KEY,
|
| 49 |
+
"gemini": GEMINI_API_KEY,
|
| 50 |
+
"cohere": COHERE_API_KEY or DEFAULT_COHERE_KEY,
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def get_default_api_key(provider: ProviderLiteral) -> Optional[str]:
|
| 55 |
+
return _DEFAULT_PROVIDER_KEYS.get(provider)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_provider_config(name: ProviderLiteral) -> ProviderConfig:
|
| 59 |
+
for config in PROVIDER_METADATA:
|
| 60 |
+
if config.name == name:
|
| 61 |
+
return config
|
| 62 |
+
raise ValueError(f"Unsupported provider: {name}")
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def validate_api_key(provider: ProviderLiteral, api_key: str | None) -> tuple[bool, Optional[str]]:
|
| 66 |
+
resolved_key = api_key or get_default_api_key(provider)
|
| 67 |
+
if not resolved_key:
|
| 68 |
+
return False, "API key is required."
|
| 69 |
+
|
| 70 |
+
config = get_provider_config(provider)
|
| 71 |
+
if config.key_prefix:
|
| 72 |
+
if not resolved_key.startswith(config.key_prefix):
|
| 73 |
+
return False, f"Expected key to start with '{config.key_prefix}'."
|
| 74 |
+
|
| 75 |
+
if len(resolved_key.strip()) < 10:
|
| 76 |
+
return False, "API key looks too short."
|
| 77 |
+
|
| 78 |
+
return True, None
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def instantiate_client(provider: ProviderLiteral, api_key: str) -> ProviderClient:
|
| 82 |
+
if provider == "qwen":
|
| 83 |
+
client = OpenAI(api_key=api_key, base_url="https://router.huggingface.co/v1")
|
| 84 |
+
elif provider == "openai":
|
| 85 |
+
client = OpenAI(api_key=api_key)
|
| 86 |
+
elif provider == "anthropic":
|
| 87 |
+
client = Anthropic(api_key=api_key)
|
| 88 |
+
elif provider == "gemini":
|
| 89 |
+
try:
|
| 90 |
+
import google.generativeai as genai
|
| 91 |
+
except ImportError as exc: # pragma: no cover - defensive path
|
| 92 |
+
raise RuntimeError(
|
| 93 |
+
"google-generativeai must be installed to use the Gemini provider"
|
| 94 |
+
) from exc
|
| 95 |
+
|
| 96 |
+
genai.configure(api_key=api_key)
|
| 97 |
+
client = genai
|
| 98 |
+
elif provider == "cohere":
|
| 99 |
+
try:
|
| 100 |
+
import cohere # type: ignore[import-not-found]
|
| 101 |
+
except ImportError as exc: # pragma: no cover - defensive path
|
| 102 |
+
raise RuntimeError("cohere must be installed to use the Cohere provider") from exc
|
| 103 |
+
|
| 104 |
+
client = cohere.Client(api_key=api_key)
|
| 105 |
+
else:
|
| 106 |
+
raise ValueError(f"Unsupported provider: {provider}")
|
| 107 |
+
|
| 108 |
+
default_model = _PROVIDER_MODEL_MAP[provider]
|
| 109 |
+
return ProviderClient(name=provider, client=client, default_model=default_model, api_key=api_key)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def list_providers() -> list[ProviderConfig]:
|
| 113 |
+
return sorted(PROVIDER_METADATA, key=lambda config: config.name != DEFAULT_PROVIDER)
|
services/rag_pipeline.py
ADDED
|
@@ -0,0 +1,1276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""RAG pipeline utilities for embeddings, summaries, and Q&A."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import inspect
|
| 7 |
+
import json
|
| 8 |
+
import re
|
| 9 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 10 |
+
from dataclasses import dataclass, field
|
| 11 |
+
from functools import lru_cache
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Any, Callable, Dict, Iterable, List, ForwardRef, Optional, Protocol, cast
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from pydantic.v1 import typing as _pydantic_typing # type: ignore[attr-defined]
|
| 17 |
+
except ImportError: # pragma: no cover - optional dependency path
|
| 18 |
+
_pydantic_typing = None
|
| 19 |
+
else:
|
| 20 |
+
_forward_sig = inspect.signature(ForwardRef._evaluate)
|
| 21 |
+
_recursive_guard_param = _forward_sig.parameters.get("recursive_guard")
|
| 22 |
+
if _recursive_guard_param is not None:
|
| 23 |
+
def _evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
| 24 |
+
evaluator = cast(Any, type_)._evaluate
|
| 25 |
+
if "type_params" in evaluator.__code__.co_varnames:
|
| 26 |
+
return evaluator(globalns, localns, None, recursive_guard=set())
|
| 27 |
+
return evaluator(globalns, localns, recursive_guard=set())
|
| 28 |
+
|
| 29 |
+
_pydantic_typing.evaluate_forwardref = _evaluate_forwardref # type: ignore[attr-defined]
|
| 30 |
+
|
| 31 |
+
from langchain_community.embeddings import SentenceTransformerEmbeddings
|
| 32 |
+
from langchain_community.vectorstores import FAISS
|
| 33 |
+
from langchain_core.documents import Document
|
| 34 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 35 |
+
from pydantic import BaseModel, Field
|
| 36 |
+
from transformers import AutoTokenizer
|
| 37 |
+
|
| 38 |
+
from config import (
|
| 39 |
+
ANTHROPIC_THINKING_BUDGET,
|
| 40 |
+
DEFAULT_CHUNK_OVERLAP,
|
| 41 |
+
DEFAULT_CHUNK_SIZE,
|
| 42 |
+
DEFAULT_EMBEDDING_MODEL,
|
| 43 |
+
OPENAI_REASONING_EFFORT,
|
| 44 |
+
PROVIDER_FULL_DOCUMENT_QA_TOKEN_BUDGETS,
|
| 45 |
+
SCAN_BATCH_SIZE,
|
| 46 |
+
SCAN_CHUNK_OVERLAP,
|
| 47 |
+
SCAN_CHUNK_SIZE,
|
| 48 |
+
SCAN_MAX_WINDOWS,
|
| 49 |
+
SCAN_TOP_K,
|
| 50 |
+
TOP_K_RETRIEVAL,
|
| 51 |
+
)
|
| 52 |
+
from services.precomputed_assets import PrecomputedExampleAsset, load_precomputed_asset_for_url
|
| 53 |
+
from services.providers import ProviderClient
|
| 54 |
+
|
| 55 |
+
OBJECTIVITY_INSTRUCTION = (
|
| 56 |
+
"Be objective. Reason things out. Based on the available context, and wherever the question "
|
| 57 |
+
"lends itself, consider multiple relevant perspectives, including opposing perspectives, "
|
| 58 |
+
"before responding."
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class SWOTBlock(BaseModel):
|
| 63 |
+
strengths: List[str] = Field(default_factory=list)
|
| 64 |
+
weaknesses: List[str] = Field(default_factory=list)
|
| 65 |
+
opportunities: List[str] = Field(default_factory=list)
|
| 66 |
+
threats: List[str] = Field(default_factory=list)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class ImplementationItem(BaseModel):
|
| 70 |
+
stakeholder: str = ""
|
| 71 |
+
obligation: str = ""
|
| 72 |
+
implementation_burden: str = ""
|
| 73 |
+
risk_or_note: str = ""
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class CritiqueItem(BaseModel):
|
| 77 |
+
issue: str = ""
|
| 78 |
+
why_it_matters: str = ""
|
| 79 |
+
recommendation: str = ""
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class AnalysisResult(BaseModel):
|
| 83 |
+
executive_summary: str = ""
|
| 84 |
+
bill_summary: List[str] = Field(default_factory=list)
|
| 85 |
+
implementation: List[ImplementationItem] = Field(default_factory=list)
|
| 86 |
+
critique: List[CritiqueItem] = Field(default_factory=list)
|
| 87 |
+
swot: SWOTBlock = Field(default_factory=SWOTBlock)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class Citation(BaseModel):
|
| 91 |
+
ref_id: int
|
| 92 |
+
snippet: str
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class AnswerResult(BaseModel):
|
| 96 |
+
answer: str
|
| 97 |
+
citations: List[Citation]
|
| 98 |
+
provenance: str = "analysis_based"
|
| 99 |
+
needs_deeper_consent: bool = False
|
| 100 |
+
deeper_answer_available: bool = False
|
| 101 |
+
consent_prompt: str = ""
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class ScanChunk(BaseModel):
|
| 105 |
+
chunk_id: int
|
| 106 |
+
text: str
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class ScanMatch(BaseModel):
|
| 110 |
+
chunk_id: int
|
| 111 |
+
relevance_score: int = Field(ge=0, le=3)
|
| 112 |
+
evidence_snippet: str = ""
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class ScanResult(BaseModel):
|
| 116 |
+
matches: List[ScanMatch] = Field(default_factory=list)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class TokenizerLike(Protocol):
|
| 120 |
+
def encode(self, text: str, *args: Any, **kwargs: Any) -> list[int]:
|
| 121 |
+
...
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@dataclass
|
| 125 |
+
class AnalysisSnippet:
|
| 126 |
+
ref_id: int
|
| 127 |
+
section: str
|
| 128 |
+
text: str
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@dataclass
|
| 132 |
+
class CachedDocumentArtifacts:
|
| 133 |
+
document_hash: str
|
| 134 |
+
document_text: str
|
| 135 |
+
chunks: list[str]
|
| 136 |
+
analysis: AnalysisResult
|
| 137 |
+
vector_store: FAISS | None
|
| 138 |
+
analysis_snippets: list[AnalysisSnippet] = field(default_factory=list)
|
| 139 |
+
source_url: str | None = None
|
| 140 |
+
example_bill_id: str | None = None
|
| 141 |
+
is_precomputed: bool = False
|
| 142 |
+
metadata: dict[str, Any] = field(default_factory=dict)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
_DOCUMENT_CACHE: dict[str, CachedDocumentArtifacts] = {}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
@lru_cache(maxsize=1)
|
| 149 |
+
def _embedding_model(model_name: str = DEFAULT_EMBEDDING_MODEL) -> SentenceTransformerEmbeddings:
|
| 150 |
+
return SentenceTransformerEmbeddings(model_name=model_name)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
@lru_cache(maxsize=1)
|
| 154 |
+
def _embedding_tokenizer(model_name: str = DEFAULT_EMBEDDING_MODEL) -> TokenizerLike:
|
| 155 |
+
return AutoTokenizer.from_pretrained(model_name)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def warm_embedding_stack(model_name: str = DEFAULT_EMBEDDING_MODEL) -> None:
|
| 159 |
+
_embedding_tokenizer(model_name)
|
| 160 |
+
_embedding_model(model_name)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _split_with_tokenizer(text: str, chunk_size: int, chunk_overlap: int) -> List[str]:
|
| 164 |
+
tokenizer = _embedding_tokenizer()
|
| 165 |
+
splitter = RecursiveCharacterTextSplitter(
|
| 166 |
+
chunk_size=chunk_size,
|
| 167 |
+
chunk_overlap=chunk_overlap,
|
| 168 |
+
length_function=lambda value: len(tokenizer.encode(value, add_special_tokens=False)),
|
| 169 |
+
separators=["\n\n", "\n", ". ", "; ", ", ", " ", ""],
|
| 170 |
+
)
|
| 171 |
+
return splitter.split_text(text)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def _token_count(text: str) -> int:
|
| 175 |
+
return len(_embedding_tokenizer().encode(text, add_special_tokens=False))
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def _full_document_budget(provider_client: ProviderClient) -> int:
|
| 179 |
+
return PROVIDER_FULL_DOCUMENT_QA_TOKEN_BUDGETS.get(provider_client.name, 0)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _supports_openai_reasoning(model_name: str) -> bool:
|
| 183 |
+
return model_name.startswith(("gpt-5", "o1", "o3", "o4"))
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def _supports_anthropic_extended_thinking(model_name: str) -> bool:
|
| 187 |
+
return model_name.startswith(
|
| 188 |
+
(
|
| 189 |
+
"claude-3-7-sonnet",
|
| 190 |
+
"claude-sonnet-4",
|
| 191 |
+
"claude-opus-4",
|
| 192 |
+
)
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def _openai_reasoning_kwargs(provider_client: ProviderClient) -> dict[str, Any]:
|
| 197 |
+
if provider_client.name != "openai" or not _supports_openai_reasoning(provider_client.default_model):
|
| 198 |
+
return {}
|
| 199 |
+
return {"reasoning": {"effort": OPENAI_REASONING_EFFORT}}
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def _anthropic_thinking_kwargs(provider_client: ProviderClient) -> dict[str, Any]:
|
| 203 |
+
if provider_client.name != "anthropic" or not _supports_anthropic_extended_thinking(provider_client.default_model):
|
| 204 |
+
return {}
|
| 205 |
+
return {
|
| 206 |
+
"extra_body": {
|
| 207 |
+
"thinking": {
|
| 208 |
+
"type": "enabled",
|
| 209 |
+
"budget_tokens": ANTHROPIC_THINKING_BUDGET,
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def split_into_chunks(text: str, chunk_size: int = DEFAULT_CHUNK_SIZE, chunk_overlap: int = DEFAULT_CHUNK_OVERLAP) -> List[str]:
|
| 216 |
+
return _split_with_tokenizer(text, chunk_size=chunk_size, chunk_overlap=chunk_overlap)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def split_into_scan_chunks(
|
| 220 |
+
text: str,
|
| 221 |
+
chunk_size: int = SCAN_CHUNK_SIZE,
|
| 222 |
+
chunk_overlap: int = SCAN_CHUNK_OVERLAP,
|
| 223 |
+
) -> List[ScanChunk]:
|
| 224 |
+
chunks = _split_with_tokenizer(text, chunk_size=chunk_size, chunk_overlap=chunk_overlap)
|
| 225 |
+
return [ScanChunk(chunk_id=idx, text=chunk) for idx, chunk in enumerate(chunks, start=1)]
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def build_vector_store(chunks: Iterable[str]) -> FAISS:
|
| 229 |
+
docs = [Document(page_content=chunk, metadata={"chunk_id": idx}) for idx, chunk in enumerate(chunks, start=1)]
|
| 230 |
+
embeddings = _embedding_model()
|
| 231 |
+
return FAISS.from_documents(docs, embedding=embeddings)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def document_hash_for_text(text: str) -> str:
|
| 235 |
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def document_cache_key(document_hash: str) -> str:
|
| 239 |
+
return f"{document_hash}:{DEFAULT_CHUNK_SIZE}:{DEFAULT_CHUNK_OVERLAP}:{DEFAULT_EMBEDDING_MODEL}"
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def get_cached_document(document_hash: str) -> CachedDocumentArtifacts | None:
|
| 243 |
+
return _DOCUMENT_CACHE.get(document_cache_key(document_hash))
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def cache_document(artifacts: CachedDocumentArtifacts) -> CachedDocumentArtifacts:
|
| 247 |
+
_DOCUMENT_CACHE[document_cache_key(artifacts.document_hash)] = artifacts
|
| 248 |
+
return artifacts
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def save_vector_store(vector_store: FAISS, output_dir: Path) -> None:
|
| 252 |
+
output_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 253 |
+
vector_store.save_local(str(output_dir))
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def load_vector_store(vector_store_dir: Path) -> FAISS:
|
| 257 |
+
return FAISS.load_local(
|
| 258 |
+
str(vector_store_dir),
|
| 259 |
+
_embedding_model(),
|
| 260 |
+
allow_dangerous_deserialization=True,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def hydrate_precomputed_example(asset: PrecomputedExampleAsset) -> CachedDocumentArtifacts:
|
| 265 |
+
analysis = AnalysisResult.model_validate(asset.analysis_payload)
|
| 266 |
+
vector_store = load_vector_store(asset.bill.vector_store_dir)
|
| 267 |
+
artifacts = CachedDocumentArtifacts(
|
| 268 |
+
document_hash=asset.bill.document_hash or document_hash_for_text(asset.document_text),
|
| 269 |
+
document_text=asset.document_text,
|
| 270 |
+
chunks=asset.chunks,
|
| 271 |
+
analysis=analysis,
|
| 272 |
+
vector_store=vector_store,
|
| 273 |
+
analysis_snippets=build_analysis_snippets(analysis),
|
| 274 |
+
source_url=asset.bill.source_url,
|
| 275 |
+
example_bill_id=asset.bill.id,
|
| 276 |
+
is_precomputed=True,
|
| 277 |
+
metadata=asset.metadata,
|
| 278 |
+
)
|
| 279 |
+
return cache_document(artifacts)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _analysis_schema() -> Dict[str, Any]:
|
| 283 |
+
return {
|
| 284 |
+
"type": "object",
|
| 285 |
+
"properties": {
|
| 286 |
+
"executive_summary": {"type": "string"},
|
| 287 |
+
"bill_summary": {"type": "array", "items": {"type": "string"}},
|
| 288 |
+
"implementation": {
|
| 289 |
+
"type": "array",
|
| 290 |
+
"items": {
|
| 291 |
+
"type": "object",
|
| 292 |
+
"properties": {
|
| 293 |
+
"stakeholder": {"type": "string"},
|
| 294 |
+
"obligation": {"type": "string"},
|
| 295 |
+
"implementation_burden": {"type": "string"},
|
| 296 |
+
"risk_or_note": {"type": "string"},
|
| 297 |
+
},
|
| 298 |
+
"required": [
|
| 299 |
+
"stakeholder",
|
| 300 |
+
"obligation",
|
| 301 |
+
"implementation_burden",
|
| 302 |
+
"risk_or_note",
|
| 303 |
+
],
|
| 304 |
+
"additionalProperties": False,
|
| 305 |
+
},
|
| 306 |
+
},
|
| 307 |
+
"critique": {
|
| 308 |
+
"type": "array",
|
| 309 |
+
"items": {
|
| 310 |
+
"type": "object",
|
| 311 |
+
"properties": {
|
| 312 |
+
"issue": {"type": "string"},
|
| 313 |
+
"why_it_matters": {"type": "string"},
|
| 314 |
+
"recommendation": {"type": "string"},
|
| 315 |
+
},
|
| 316 |
+
"required": ["issue", "why_it_matters", "recommendation"],
|
| 317 |
+
"additionalProperties": False,
|
| 318 |
+
},
|
| 319 |
+
},
|
| 320 |
+
"swot": {
|
| 321 |
+
"type": "object",
|
| 322 |
+
"properties": {
|
| 323 |
+
"strengths": {"type": "array", "items": {"type": "string"}},
|
| 324 |
+
"weaknesses": {"type": "array", "items": {"type": "string"}},
|
| 325 |
+
"opportunities": {"type": "array", "items": {"type": "string"}},
|
| 326 |
+
"threats": {"type": "array", "items": {"type": "string"}},
|
| 327 |
+
},
|
| 328 |
+
"required": ["strengths", "weaknesses", "opportunities", "threats"],
|
| 329 |
+
"additionalProperties": False,
|
| 330 |
+
},
|
| 331 |
+
},
|
| 332 |
+
"required": [
|
| 333 |
+
"executive_summary",
|
| 334 |
+
"bill_summary",
|
| 335 |
+
"implementation",
|
| 336 |
+
"critique",
|
| 337 |
+
"swot",
|
| 338 |
+
],
|
| 339 |
+
"additionalProperties": False,
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _analysis_summary_schema() -> Dict[str, Any]:
|
| 344 |
+
return {
|
| 345 |
+
"type": "object",
|
| 346 |
+
"properties": {
|
| 347 |
+
"executive_summary": {"type": "string"},
|
| 348 |
+
"bill_summary": {"type": "array", "items": {"type": "string"}},
|
| 349 |
+
},
|
| 350 |
+
"required": ["executive_summary", "bill_summary"],
|
| 351 |
+
"additionalProperties": False,
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def _implementation_schema() -> Dict[str, Any]:
|
| 356 |
+
return {
|
| 357 |
+
"type": "object",
|
| 358 |
+
"properties": {
|
| 359 |
+
"implementation": _analysis_schema()["properties"]["implementation"],
|
| 360 |
+
},
|
| 361 |
+
"required": ["implementation"],
|
| 362 |
+
"additionalProperties": False,
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def _critique_schema() -> Dict[str, Any]:
|
| 367 |
+
return {
|
| 368 |
+
"type": "object",
|
| 369 |
+
"properties": {
|
| 370 |
+
"critique": _analysis_schema()["properties"]["critique"],
|
| 371 |
+
},
|
| 372 |
+
"required": ["critique"],
|
| 373 |
+
"additionalProperties": False,
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def _swot_schema() -> Dict[str, Any]:
|
| 378 |
+
return {
|
| 379 |
+
"type": "object",
|
| 380 |
+
"properties": {
|
| 381 |
+
"swot": _analysis_schema()["properties"]["swot"],
|
| 382 |
+
},
|
| 383 |
+
"required": ["swot"],
|
| 384 |
+
"additionalProperties": False,
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _analysis_answer_schema() -> Dict[str, Any]:
|
| 389 |
+
return {
|
| 390 |
+
"type": "object",
|
| 391 |
+
"properties": {
|
| 392 |
+
"answer": {"type": "string"},
|
| 393 |
+
"is_sufficient": {"type": "boolean"},
|
| 394 |
+
},
|
| 395 |
+
"required": ["answer", "is_sufficient"],
|
| 396 |
+
"additionalProperties": False,
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _generate_json_payload(
|
| 401 |
+
provider_client: ProviderClient,
|
| 402 |
+
*,
|
| 403 |
+
prompt: str,
|
| 404 |
+
user_text: str,
|
| 405 |
+
schema: dict[str, Any],
|
| 406 |
+
schema_name: str,
|
| 407 |
+
max_tokens: int = 1500,
|
| 408 |
+
) -> str:
|
| 409 |
+
if provider_client.name == "openai":
|
| 410 |
+
response = provider_client.client.responses.create(
|
| 411 |
+
model=provider_client.default_model,
|
| 412 |
+
input=[
|
| 413 |
+
{"role": "system", "content": prompt},
|
| 414 |
+
{"role": "user", "content": user_text},
|
| 415 |
+
],
|
| 416 |
+
response_format={
|
| 417 |
+
"type": "json_schema",
|
| 418 |
+
"json_schema": {"name": schema_name, "schema": schema},
|
| 419 |
+
},
|
| 420 |
+
**_openai_reasoning_kwargs(provider_client),
|
| 421 |
+
)
|
| 422 |
+
return _extract_openai_json(response)
|
| 423 |
+
if provider_client.name == "qwen":
|
| 424 |
+
response = provider_client.client.chat.completions.create(
|
| 425 |
+
model=provider_client.default_model,
|
| 426 |
+
messages=[
|
| 427 |
+
{"role": "system", "content": prompt},
|
| 428 |
+
{
|
| 429 |
+
"role": "user",
|
| 430 |
+
"content": (
|
| 431 |
+
f"{user_text}\n\n"
|
| 432 |
+
"Return only valid JSON matching this schema:\n"
|
| 433 |
+
f"{json.dumps(schema)}"
|
| 434 |
+
),
|
| 435 |
+
},
|
| 436 |
+
],
|
| 437 |
+
temperature=0,
|
| 438 |
+
)
|
| 439 |
+
return _strip_json_fences(_extract_chat_completion_text(response))
|
| 440 |
+
if provider_client.name == "anthropic":
|
| 441 |
+
response = provider_client.client.messages.create(
|
| 442 |
+
model=provider_client.default_model,
|
| 443 |
+
max_tokens=max_tokens,
|
| 444 |
+
system=prompt,
|
| 445 |
+
messages=[{"role": "user", "content": [{"type": "text", "text": user_text}]}],
|
| 446 |
+
response_format={
|
| 447 |
+
"type": "json_schema",
|
| 448 |
+
"json_schema": {"name": schema_name, "schema": schema},
|
| 449 |
+
},
|
| 450 |
+
**_anthropic_thinking_kwargs(provider_client),
|
| 451 |
+
)
|
| 452 |
+
return response.content[0].text
|
| 453 |
+
if provider_client.name == "cohere":
|
| 454 |
+
response = provider_client.client.chat(
|
| 455 |
+
message=user_text,
|
| 456 |
+
model=provider_client.default_model,
|
| 457 |
+
preamble=prompt,
|
| 458 |
+
max_tokens=max_tokens,
|
| 459 |
+
temperature=0,
|
| 460 |
+
response_format={
|
| 461 |
+
"type": "json_object",
|
| 462 |
+
"schema": schema,
|
| 463 |
+
},
|
| 464 |
+
)
|
| 465 |
+
return _strip_json_fences(_extract_cohere_text(response))
|
| 466 |
+
|
| 467 |
+
genai = provider_client.client
|
| 468 |
+
model = genai.GenerativeModel(
|
| 469 |
+
model_name=provider_client.default_model,
|
| 470 |
+
system_instruction=prompt,
|
| 471 |
+
generation_config=genai.GenerationConfig(
|
| 472 |
+
temperature=0,
|
| 473 |
+
response_mime_type="application/json",
|
| 474 |
+
),
|
| 475 |
+
)
|
| 476 |
+
response = model.generate_content(user_text)
|
| 477 |
+
return _extract_gemini_text(response)
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def generate_analysis_progress(provider_client: ProviderClient, document_text: str) -> Iterable[tuple[str, AnalysisResult]]:
|
| 481 |
+
limited_text = document_text[:30000]
|
| 482 |
+
base_prompt = (
|
| 483 |
+
"You are a senior policy analyst helping citizens, civil society, startups, workers, consumers, "
|
| 484 |
+
"operators, and affected industries understand a government bill. Produce a clear briefing-style analysis, "
|
| 485 |
+
"not a generic summary. Be objective, text-disciplined, and grounded in the document. Separate what the bill "
|
| 486 |
+
"expressly provides from what is a reasonable inference about likely effects. Do not attribute powers, "
|
| 487 |
+
"safeguards, funding mechanisms, oversight tools, or policy instruments to the bill unless they are plainly "
|
| 488 |
+
"supported by the text. If support for a point is weak, omit it rather than embellish it. Focus on what the "
|
| 489 |
+
"bill changes, what ordinary people or practitioners in the affected field should be excited or worried "
|
| 490 |
+
"about, how businesses or industry participants may need to adapt, and what this could mean for growth, "
|
| 491 |
+
"friction, competition, cost, access, compliance, innovation, investor confidence, public-service delivery, "
|
| 492 |
+
"or digital infrastructure quality in the affected sector and, where relevant, the national economy. "
|
| 493 |
+
"Pay particular attention to ground-level effects on workers, professionals, entrepreneurs, recent "
|
| 494 |
+
"graduates, students, consumers, startups, SMEs, public institutions, and vulnerable or underserved groups "
|
| 495 |
+
"when the bill plausibly affects them, but do not force demographic groups into the analysis where the fit is "
|
| 496 |
+
"weak. Do not center implementation implications on lawmakers, legislators, or sponsoring government "
|
| 497 |
+
"agencies unless a direct downstream burden on the public or private sector depends on them. "
|
| 498 |
+
"For implementation items, identify the affected stakeholder group, the practical obligation or change in "
|
| 499 |
+
"behavior they may face, the likely implementation burden, and any practical risk or note. If a stakeholder "
|
| 500 |
+
"has no formal legal obligation, describe the real-world adjustment, exposure, opportunity, or compliance "
|
| 501 |
+
"expectation in plain language. Prefer concrete operational, lived-experience, and economic consequences over "
|
| 502 |
+
"abstract commentary. For critique items, use the structure: issue, why it matters, recommendation, and "
|
| 503 |
+
"focus on ambiguity, overlap, overreach, implementation bottlenecks, concentration of discretion, compliance "
|
| 504 |
+
"burden, and risks to competition, innovation, affordability, or access where relevant. Keep recommendations "
|
| 505 |
+
"tightly connected to the specific weakness identified. For SWOT, keep each item concise, specific, and "
|
| 506 |
+
"grounded in the bill or a clear downstream effect that follows from the bill. Avoid vague statements. "
|
| 507 |
+
"Avoid duplicating the same point across sections. "
|
| 508 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 509 |
+
)
|
| 510 |
+
|
| 511 |
+
partial = AnalysisResult()
|
| 512 |
+
summary_payload = _generate_json_payload(
|
| 513 |
+
provider_client,
|
| 514 |
+
prompt=base_prompt + " Return JSON for executive_summary and bill_summary only.",
|
| 515 |
+
user_text=f"Document content:\n\n{limited_text}",
|
| 516 |
+
schema=_analysis_summary_schema(),
|
| 517 |
+
schema_name="BillAnalysisSummary",
|
| 518 |
+
)
|
| 519 |
+
summary_result = json.loads(_strip_json_fences(summary_payload))
|
| 520 |
+
partial.executive_summary = summary_result.get("executive_summary", "")
|
| 521 |
+
partial.bill_summary = summary_result.get("bill_summary", [])
|
| 522 |
+
yield "Generating executive summary...", partial.model_copy(deep=True)
|
| 523 |
+
|
| 524 |
+
implementation_payload = _generate_json_payload(
|
| 525 |
+
provider_client,
|
| 526 |
+
prompt=base_prompt + " Return JSON for implementation implications only.",
|
| 527 |
+
user_text=f"Document content:\n\n{limited_text}",
|
| 528 |
+
schema=_implementation_schema(),
|
| 529 |
+
schema_name="BillAnalysisImplementation",
|
| 530 |
+
)
|
| 531 |
+
implementation_result = json.loads(_strip_json_fences(implementation_payload))
|
| 532 |
+
partial.implementation = [ImplementationItem.model_validate(item) for item in implementation_result.get("implementation", [])]
|
| 533 |
+
yield "Generating implementation implications...", partial.model_copy(deep=True)
|
| 534 |
+
|
| 535 |
+
critique_payload = _generate_json_payload(
|
| 536 |
+
provider_client,
|
| 537 |
+
prompt=base_prompt + " Return JSON for critique only.",
|
| 538 |
+
user_text=f"Document content:\n\n{limited_text}",
|
| 539 |
+
schema=_critique_schema(),
|
| 540 |
+
schema_name="BillAnalysisCritique",
|
| 541 |
+
)
|
| 542 |
+
critique_result = json.loads(_strip_json_fences(critique_payload))
|
| 543 |
+
partial.critique = [CritiqueItem.model_validate(item) for item in critique_result.get("critique", [])]
|
| 544 |
+
yield "Generating critique...", partial.model_copy(deep=True)
|
| 545 |
+
|
| 546 |
+
swot_payload = _generate_json_payload(
|
| 547 |
+
provider_client,
|
| 548 |
+
prompt=base_prompt + " Return JSON for SWOT only.",
|
| 549 |
+
user_text=f"Document content:\n\n{limited_text}",
|
| 550 |
+
schema=_swot_schema(),
|
| 551 |
+
schema_name="BillAnalysisSwot",
|
| 552 |
+
)
|
| 553 |
+
swot_result = json.loads(_strip_json_fences(swot_payload))
|
| 554 |
+
partial.swot = SWOTBlock.model_validate(swot_result.get("swot", {}))
|
| 555 |
+
yield "Generating SWOT...", partial.model_copy(deep=True)
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
def generate_analysis_once(provider_client: ProviderClient, document_text: str) -> AnalysisResult:
|
| 559 |
+
limited_text = document_text[:30000]
|
| 560 |
+
prompt = (
|
| 561 |
+
"You are a senior policy analyst helping citizens, civil society, startups, workers, consumers, "
|
| 562 |
+
"operators, and affected industries understand a government bill. Produce a clear briefing-style analysis, "
|
| 563 |
+
"not a generic summary. Be objective, text-disciplined, and grounded in the document. Separate what the bill "
|
| 564 |
+
"expressly provides from what is a reasonable inference about likely effects. Do not attribute powers, "
|
| 565 |
+
"safeguards, funding mechanisms, oversight tools, or policy instruments to the bill unless they are plainly "
|
| 566 |
+
"supported by the text. If support for a point is weak, omit it rather than embellish it. Focus on what the "
|
| 567 |
+
"bill changes, what ordinary people or practitioners in the affected field should be excited or worried "
|
| 568 |
+
"about, how businesses or industry participants may need to adapt, and what this could mean for growth, "
|
| 569 |
+
"friction, competition, cost, access, compliance, innovation, investor confidence, public-service delivery, "
|
| 570 |
+
"or digital infrastructure quality in the affected sector and, where relevant, the national economy. "
|
| 571 |
+
"Pay particular attention to ground-level effects on workers, professionals, entrepreneurs, recent "
|
| 572 |
+
"graduates, students, consumers, startups, SMEs, public institutions, and vulnerable or underserved groups "
|
| 573 |
+
"when the bill plausibly affects them, but do not force demographic groups into the analysis where the fit is "
|
| 574 |
+
"weak. Do not center implementation implications on lawmakers, legislators, or sponsoring government "
|
| 575 |
+
"agencies unless a direct downstream burden on the public or private sector depends on them. "
|
| 576 |
+
"For implementation items, identify the affected stakeholder group, the practical obligation or change in "
|
| 577 |
+
"behavior they may face, the likely implementation burden, and any practical risk or note. If a stakeholder "
|
| 578 |
+
"has no formal legal obligation, describe the real-world adjustment, exposure, opportunity, or compliance "
|
| 579 |
+
"expectation in plain language. Prefer concrete operational, lived-experience, and economic consequences over "
|
| 580 |
+
"abstract commentary. For critique items, use the structure: issue, why it matters, recommendation, and "
|
| 581 |
+
"focus on ambiguity, overlap, overreach, implementation bottlenecks, concentration of discretion, compliance "
|
| 582 |
+
"burden, and risks to competition, innovation, affordability, or access where relevant. Keep recommendations "
|
| 583 |
+
"tightly connected to the specific weakness identified. For SWOT, keep each item concise, specific, and "
|
| 584 |
+
"grounded in the bill or a clear downstream effect that follows from the bill. "
|
| 585 |
+
"Avoid vague statements. Avoid duplicating the same point across sections. "
|
| 586 |
+
"Respond using JSON that matches the provided schema. "
|
| 587 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 588 |
+
)
|
| 589 |
+
payload = _generate_json_payload(
|
| 590 |
+
provider_client,
|
| 591 |
+
prompt=prompt,
|
| 592 |
+
user_text=f"Document content:\n\n{limited_text}",
|
| 593 |
+
schema=_analysis_schema(),
|
| 594 |
+
schema_name="BillAnalysis",
|
| 595 |
+
)
|
| 596 |
+
return AnalysisResult.model_validate_json(_strip_json_fences(payload))
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
def generate_analysis(provider_client: ProviderClient, document_text: str) -> AnalysisResult:
|
| 600 |
+
final = AnalysisResult()
|
| 601 |
+
for _, partial in generate_analysis_progress(provider_client, document_text):
|
| 602 |
+
final = partial
|
| 603 |
+
return final
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
def prepare_document_artifacts(
|
| 607 |
+
document_text: str,
|
| 608 |
+
*,
|
| 609 |
+
provider_factory: Callable[[], Any] | None = None,
|
| 610 |
+
) -> tuple[str, list[str], FAISS]:
|
| 611 |
+
document_hash = document_hash_for_text(document_text)
|
| 612 |
+
cached = get_cached_document(document_hash)
|
| 613 |
+
if cached is not None and cached.vector_store is not None:
|
| 614 |
+
return cached.document_hash, cached.chunks, cached.vector_store
|
| 615 |
+
|
| 616 |
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
| 617 |
+
chunks_future = executor.submit(split_into_chunks, document_text)
|
| 618 |
+
provider_future = executor.submit(provider_factory) if provider_factory is not None else None
|
| 619 |
+
chunks = chunks_future.result()
|
| 620 |
+
vector_store = build_vector_store(chunks)
|
| 621 |
+
if provider_future is not None:
|
| 622 |
+
provider_future.result()
|
| 623 |
+
return document_hash, chunks, vector_store
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def get_precomputed_example_artifacts(url: str | None) -> CachedDocumentArtifacts | None:
|
| 627 |
+
asset = load_precomputed_asset_for_url(url)
|
| 628 |
+
if asset is None:
|
| 629 |
+
return None
|
| 630 |
+
if asset.bill.document_hash:
|
| 631 |
+
cached = get_cached_document(asset.bill.document_hash)
|
| 632 |
+
if cached is not None:
|
| 633 |
+
return cached
|
| 634 |
+
return hydrate_precomputed_example(asset)
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def build_cached_document_artifacts(
|
| 638 |
+
*,
|
| 639 |
+
document_text: str,
|
| 640 |
+
chunks: list[str],
|
| 641 |
+
analysis: AnalysisResult,
|
| 642 |
+
vector_store: FAISS | None,
|
| 643 |
+
source_url: str | None = None,
|
| 644 |
+
) -> CachedDocumentArtifacts:
|
| 645 |
+
return cache_document(
|
| 646 |
+
CachedDocumentArtifacts(
|
| 647 |
+
document_hash=document_hash_for_text(document_text),
|
| 648 |
+
document_text=document_text,
|
| 649 |
+
chunks=chunks,
|
| 650 |
+
analysis=analysis,
|
| 651 |
+
vector_store=vector_store,
|
| 652 |
+
analysis_snippets=build_analysis_snippets(analysis),
|
| 653 |
+
source_url=source_url,
|
| 654 |
+
)
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
|
| 658 |
+
def build_analysis_snippets(analysis: AnalysisResult) -> list[AnalysisSnippet]:
|
| 659 |
+
snippets: list[AnalysisSnippet] = []
|
| 660 |
+
next_ref = 1
|
| 661 |
+
|
| 662 |
+
def push(section: str, text: str) -> None:
|
| 663 |
+
nonlocal next_ref
|
| 664 |
+
cleaned = " ".join(text.split()).strip()
|
| 665 |
+
if not cleaned:
|
| 666 |
+
return
|
| 667 |
+
snippets.append(AnalysisSnippet(ref_id=next_ref, section=section, text=cleaned))
|
| 668 |
+
next_ref += 1
|
| 669 |
+
|
| 670 |
+
push("Executive Summary", analysis.executive_summary)
|
| 671 |
+
for item in analysis.bill_summary:
|
| 672 |
+
push("Bill Summary", item)
|
| 673 |
+
for item in analysis.implementation:
|
| 674 |
+
push(
|
| 675 |
+
"Implementation",
|
| 676 |
+
f"{item.stakeholder}: {item.obligation}. Burden: {item.implementation_burden}. Risk/Note: {item.risk_or_note}",
|
| 677 |
+
)
|
| 678 |
+
for item in analysis.critique:
|
| 679 |
+
push(
|
| 680 |
+
"Critique",
|
| 681 |
+
f"{item.issue}. Why it matters: {item.why_it_matters}. Recommendation: {item.recommendation}",
|
| 682 |
+
)
|
| 683 |
+
for label, values in (
|
| 684 |
+
("Strength", analysis.swot.strengths),
|
| 685 |
+
("Weakness", analysis.swot.weaknesses),
|
| 686 |
+
("Opportunity", analysis.swot.opportunities),
|
| 687 |
+
("Threat", analysis.swot.threats),
|
| 688 |
+
):
|
| 689 |
+
for value in values:
|
| 690 |
+
push(f"SWOT {label}", value)
|
| 691 |
+
return snippets
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
def _question_terms(text: str) -> set[str]:
|
| 695 |
+
return {term for term in re.findall(r"[a-z0-9]{3,}", text.lower())}
|
| 696 |
+
|
| 697 |
+
|
| 698 |
+
def search_analysis_snippets(snippets: list[AnalysisSnippet], question: str, top_k: int = 5) -> tuple[str, list[Citation]] | None:
|
| 699 |
+
if not snippets:
|
| 700 |
+
return None
|
| 701 |
+
terms = _question_terms(question)
|
| 702 |
+
if not terms:
|
| 703 |
+
return None
|
| 704 |
+
|
| 705 |
+
ranked: list[tuple[int, AnalysisSnippet]] = []
|
| 706 |
+
for snippet in snippets:
|
| 707 |
+
snippet_terms = _question_terms(snippet.text)
|
| 708 |
+
score = len(terms & snippet_terms)
|
| 709 |
+
if score > 0:
|
| 710 |
+
ranked.append((score, snippet))
|
| 711 |
+
if not ranked:
|
| 712 |
+
return None
|
| 713 |
+
|
| 714 |
+
ranked.sort(key=lambda item: (-item[0], item[1].ref_id))
|
| 715 |
+
selected = [snippet for _, snippet in ranked[:top_k]]
|
| 716 |
+
context_blocks: list[str] = []
|
| 717 |
+
citations: list[Citation] = []
|
| 718 |
+
for idx, snippet in enumerate(selected, start=1):
|
| 719 |
+
citations.append(Citation(ref_id=idx, snippet=snippet.text))
|
| 720 |
+
context_blocks.append(f"[ref{idx}] {snippet.section}\n{snippet.text}")
|
| 721 |
+
return "\n\n".join(context_blocks), citations
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def _answer_question_from_analysis_context(
|
| 725 |
+
provider_client: ProviderClient,
|
| 726 |
+
context_text: str,
|
| 727 |
+
question: str,
|
| 728 |
+
) -> dict[str, Any]:
|
| 729 |
+
prompt = (
|
| 730 |
+
"You answer follow-up questions about a government bill using only the provided analysis summary snippets. "
|
| 731 |
+
"Answer directly and clearly. Set is_sufficient to true only if the analysis snippets materially answer the "
|
| 732 |
+
"question. If they do not fully answer it, say what is missing and set is_sufficient to false. "
|
| 733 |
+
"Do not pretend you have read the full bill if you only have analysis snippets. "
|
| 734 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 735 |
+
)
|
| 736 |
+
payload = _generate_json_payload(
|
| 737 |
+
provider_client,
|
| 738 |
+
prompt=prompt,
|
| 739 |
+
user_text="Analysis snippets:\n" + context_text + "\n\nQuestion:\n" + question,
|
| 740 |
+
schema=_analysis_answer_schema(),
|
| 741 |
+
schema_name="AnalysisAnswer",
|
| 742 |
+
max_tokens=900,
|
| 743 |
+
)
|
| 744 |
+
return json.loads(_strip_json_fences(payload))
|
| 745 |
+
|
| 746 |
+
|
| 747 |
+
def answer_query(
|
| 748 |
+
provider_client: ProviderClient,
|
| 749 |
+
analysis: AnalysisResult | None,
|
| 750 |
+
vector_store: FAISS | None,
|
| 751 |
+
question: str,
|
| 752 |
+
doc_text: str | None = None,
|
| 753 |
+
allow_full_document: bool = False,
|
| 754 |
+
) -> AnswerResult:
|
| 755 |
+
if analysis is not None:
|
| 756 |
+
snippets = build_analysis_snippets(analysis)
|
| 757 |
+
analysis_context = search_analysis_snippets(snippets, question)
|
| 758 |
+
if analysis_context is not None:
|
| 759 |
+
context_text, citations = analysis_context
|
| 760 |
+
analysis_answer = _answer_question_from_analysis_context(provider_client, context_text, question)
|
| 761 |
+
return AnswerResult(
|
| 762 |
+
answer=analysis_answer["answer"].strip(),
|
| 763 |
+
citations=citations,
|
| 764 |
+
provenance="analysis_based",
|
| 765 |
+
needs_deeper_consent=not analysis_answer.get("is_sufficient", False),
|
| 766 |
+
deeper_answer_available=bool(doc_text or vector_store is not None),
|
| 767 |
+
consent_prompt=(
|
| 768 |
+
"This answer is based on the summary and analysis. "
|
| 769 |
+
"If you want, I can run a deeper full-document answer next."
|
| 770 |
+
),
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
if allow_full_document:
|
| 774 |
+
return answer_query_from_full_document(provider_client, vector_store, question, doc_text=doc_text)
|
| 775 |
+
|
| 776 |
+
return AnswerResult(
|
| 777 |
+
answer=(
|
| 778 |
+
"I couldn't fully answer that from the summary and analysis alone. "
|
| 779 |
+
"If you want, I can run a deeper full-document answer."
|
| 780 |
+
),
|
| 781 |
+
citations=[],
|
| 782 |
+
provenance="analysis_based",
|
| 783 |
+
needs_deeper_consent=True,
|
| 784 |
+
deeper_answer_available=bool(doc_text or vector_store is not None),
|
| 785 |
+
consent_prompt="A deeper full-document answer is available.",
|
| 786 |
+
)
|
| 787 |
+
|
| 788 |
+
|
| 789 |
+
def answer_query_from_full_document(
|
| 790 |
+
provider_client: ProviderClient,
|
| 791 |
+
vector_store: FAISS | None,
|
| 792 |
+
question: str,
|
| 793 |
+
*,
|
| 794 |
+
doc_text: str | None = None,
|
| 795 |
+
) -> AnswerResult:
|
| 796 |
+
context_text = "No context available."
|
| 797 |
+
citations: list[Citation] = []
|
| 798 |
+
|
| 799 |
+
if doc_text:
|
| 800 |
+
full_document_answer = _answer_question_from_full_document(provider_client, doc_text, question)
|
| 801 |
+
if full_document_answer is not None:
|
| 802 |
+
return AnswerResult(
|
| 803 |
+
answer=full_document_answer.strip(),
|
| 804 |
+
citations=[],
|
| 805 |
+
provenance="full_document",
|
| 806 |
+
)
|
| 807 |
+
|
| 808 |
+
scan_context = _scan_document_for_context(provider_client, doc_text, question)
|
| 809 |
+
if scan_context:
|
| 810 |
+
context_text, citations = scan_context
|
| 811 |
+
|
| 812 |
+
if not citations and vector_store is not None:
|
| 813 |
+
context_text, citations = _retrieve_context_from_vector_store(vector_store, question)
|
| 814 |
+
|
| 815 |
+
answer = _generate_answer_from_context(provider_client, context_text, question)
|
| 816 |
+
return AnswerResult(answer=answer.strip(), citations=citations, provenance="full_document")
|
| 817 |
+
|
| 818 |
+
|
| 819 |
+
def _answer_question_from_full_document(
|
| 820 |
+
provider_client: ProviderClient,
|
| 821 |
+
doc_text: str,
|
| 822 |
+
question: str,
|
| 823 |
+
) -> str | None:
|
| 824 |
+
if _token_count(doc_text) > _full_document_budget(provider_client):
|
| 825 |
+
return None
|
| 826 |
+
|
| 827 |
+
prompt = (
|
| 828 |
+
"You answer questions about government bills. Read the full document carefully, "
|
| 829 |
+
"reason through the relevant provisions, and answer the user's question logically. "
|
| 830 |
+
"Base your answer only on the provided document. If the answer is not supported by the document, say you are unsure. "
|
| 831 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 832 |
+
)
|
| 833 |
+
user_text = "Full document:\n" + doc_text + "\n\nQuestion:\n" + question
|
| 834 |
+
|
| 835 |
+
if provider_client.name == "openai":
|
| 836 |
+
response = provider_client.client.responses.create(
|
| 837 |
+
model=provider_client.default_model,
|
| 838 |
+
input=[
|
| 839 |
+
{"role": "system", "content": prompt},
|
| 840 |
+
{"role": "user", "content": user_text},
|
| 841 |
+
],
|
| 842 |
+
**_openai_reasoning_kwargs(provider_client),
|
| 843 |
+
)
|
| 844 |
+
return _extract_openai_text(response)
|
| 845 |
+
if provider_client.name == "qwen":
|
| 846 |
+
response = provider_client.client.chat.completions.create(
|
| 847 |
+
model=provider_client.default_model,
|
| 848 |
+
messages=[
|
| 849 |
+
{"role": "system", "content": prompt},
|
| 850 |
+
{"role": "user", "content": user_text},
|
| 851 |
+
],
|
| 852 |
+
temperature=0,
|
| 853 |
+
)
|
| 854 |
+
return _extract_chat_completion_text(response)
|
| 855 |
+
if provider_client.name == "anthropic":
|
| 856 |
+
response = provider_client.client.messages.create(
|
| 857 |
+
model=provider_client.default_model,
|
| 858 |
+
max_tokens=1000,
|
| 859 |
+
system=prompt,
|
| 860 |
+
messages=[{"role": "user", "content": [{"type": "text", "text": user_text}]}],
|
| 861 |
+
**_anthropic_thinking_kwargs(provider_client),
|
| 862 |
+
)
|
| 863 |
+
return response.content[0].text
|
| 864 |
+
if provider_client.name == "cohere":
|
| 865 |
+
response = provider_client.client.chat(
|
| 866 |
+
message=user_text,
|
| 867 |
+
model=provider_client.default_model,
|
| 868 |
+
preamble=prompt,
|
| 869 |
+
max_tokens=1000,
|
| 870 |
+
temperature=0,
|
| 871 |
+
)
|
| 872 |
+
return _extract_cohere_text(response)
|
| 873 |
+
|
| 874 |
+
genai = provider_client.client
|
| 875 |
+
model = genai.GenerativeModel(
|
| 876 |
+
model_name=provider_client.default_model,
|
| 877 |
+
system_instruction=prompt,
|
| 878 |
+
generation_config=genai.GenerationConfig(
|
| 879 |
+
temperature=0,
|
| 880 |
+
response_mime_type="text/plain",
|
| 881 |
+
),
|
| 882 |
+
)
|
| 883 |
+
response = model.generate_content(user_text)
|
| 884 |
+
return _extract_gemini_text(response)
|
| 885 |
+
|
| 886 |
+
|
| 887 |
+
def _retrieve_context_from_vector_store(vector_store: FAISS, question: str) -> tuple[str, list[Citation]]:
|
| 888 |
+
retrieved_docs = vector_store.similarity_search(question, k=TOP_K_RETRIEVAL)
|
| 889 |
+
context_blocks: list[str] = []
|
| 890 |
+
citations: list[Citation] = []
|
| 891 |
+
for idx, doc in enumerate(retrieved_docs, start=1):
|
| 892 |
+
snippet = doc.page_content.strip()
|
| 893 |
+
context_blocks.append(f"[{idx}] {snippet}")
|
| 894 |
+
citations.append(Citation(ref_id=idx, snippet=snippet))
|
| 895 |
+
|
| 896 |
+
context_text = "\n\n".join(context_blocks) if context_blocks else "No context available."
|
| 897 |
+
return context_text, citations
|
| 898 |
+
|
| 899 |
+
|
| 900 |
+
def _generate_answer_from_context(provider_client: ProviderClient, context_text: str, question: str) -> str:
|
| 901 |
+
prompt = (
|
| 902 |
+
"You answer questions about government bills. Use the context snippets provided. "
|
| 903 |
+
"Cite supporting snippets using [ref#] references matching the snippet number. "
|
| 904 |
+
"If unsure, say you are unsure. "
|
| 905 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 906 |
+
)
|
| 907 |
+
|
| 908 |
+
if provider_client.name == "openai":
|
| 909 |
+
response = provider_client.client.responses.create(
|
| 910 |
+
model=provider_client.default_model,
|
| 911 |
+
input=[
|
| 912 |
+
{"role": "system", "content": prompt},
|
| 913 |
+
{
|
| 914 |
+
"role": "user",
|
| 915 |
+
"content": (
|
| 916 |
+
"Context:\n" + context_text + "\n\nQuestion:\n" + question
|
| 917 |
+
),
|
| 918 |
+
},
|
| 919 |
+
],
|
| 920 |
+
**_openai_reasoning_kwargs(provider_client),
|
| 921 |
+
)
|
| 922 |
+
answer = _extract_openai_text(response)
|
| 923 |
+
elif provider_client.name == "qwen":
|
| 924 |
+
response = provider_client.client.chat.completions.create(
|
| 925 |
+
model=provider_client.default_model,
|
| 926 |
+
messages=[
|
| 927 |
+
{"role": "system", "content": prompt},
|
| 928 |
+
{
|
| 929 |
+
"role": "user",
|
| 930 |
+
"content": (
|
| 931 |
+
"Context:\n" + context_text + "\n\nQuestion:\n" + question
|
| 932 |
+
),
|
| 933 |
+
},
|
| 934 |
+
],
|
| 935 |
+
temperature=0,
|
| 936 |
+
)
|
| 937 |
+
answer = _extract_chat_completion_text(response)
|
| 938 |
+
elif provider_client.name == "anthropic":
|
| 939 |
+
response = provider_client.client.messages.create(
|
| 940 |
+
model=provider_client.default_model,
|
| 941 |
+
max_tokens=800,
|
| 942 |
+
system=prompt,
|
| 943 |
+
messages=[
|
| 944 |
+
{
|
| 945 |
+
"role": "user",
|
| 946 |
+
"content": [
|
| 947 |
+
{
|
| 948 |
+
"type": "text",
|
| 949 |
+
"text": (
|
| 950 |
+
"Context:\n" + context_text + "\n\nQuestion:\n" + question
|
| 951 |
+
),
|
| 952 |
+
}
|
| 953 |
+
],
|
| 954 |
+
}
|
| 955 |
+
],
|
| 956 |
+
**_anthropic_thinking_kwargs(provider_client),
|
| 957 |
+
)
|
| 958 |
+
answer = response.content[0].text
|
| 959 |
+
elif provider_client.name == "cohere":
|
| 960 |
+
response = provider_client.client.chat(
|
| 961 |
+
message=(
|
| 962 |
+
"Context:\n" + context_text + "\n\nQuestion:\n" + question
|
| 963 |
+
),
|
| 964 |
+
model=provider_client.default_model,
|
| 965 |
+
preamble=prompt,
|
| 966 |
+
max_tokens=800,
|
| 967 |
+
temperature=0,
|
| 968 |
+
)
|
| 969 |
+
answer = _extract_cohere_text(response)
|
| 970 |
+
else: # gemini
|
| 971 |
+
genai = provider_client.client
|
| 972 |
+
model = genai.GenerativeModel(
|
| 973 |
+
model_name=provider_client.default_model,
|
| 974 |
+
system_instruction=prompt,
|
| 975 |
+
generation_config=genai.GenerationConfig(
|
| 976 |
+
temperature=0,
|
| 977 |
+
response_mime_type="text/plain",
|
| 978 |
+
),
|
| 979 |
+
)
|
| 980 |
+
response = model.generate_content(
|
| 981 |
+
"Context:\n"
|
| 982 |
+
+ context_text
|
| 983 |
+
+ "\n\nQuestion:\n"
|
| 984 |
+
+ question
|
| 985 |
+
)
|
| 986 |
+
answer = _extract_gemini_text(response)
|
| 987 |
+
|
| 988 |
+
return answer
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
def _scan_document_for_context(
|
| 992 |
+
provider_client: ProviderClient,
|
| 993 |
+
doc_text: str,
|
| 994 |
+
question: str,
|
| 995 |
+
) -> Optional[tuple[str, list[Citation]]]:
|
| 996 |
+
scan_chunks = split_into_scan_chunks(doc_text)
|
| 997 |
+
if not scan_chunks or len(scan_chunks) > SCAN_MAX_WINDOWS:
|
| 998 |
+
return None
|
| 999 |
+
|
| 1000 |
+
ranked_matches: list[ScanMatch] = []
|
| 1001 |
+
for batch_start in range(0, len(scan_chunks), SCAN_BATCH_SIZE):
|
| 1002 |
+
batch = scan_chunks[batch_start : batch_start + SCAN_BATCH_SIZE]
|
| 1003 |
+
parsed_result = _run_scan_batch_with_retry(provider_client, batch, question)
|
| 1004 |
+
if parsed_result is None:
|
| 1005 |
+
return None
|
| 1006 |
+
ranked_matches.extend(parsed_result.matches)
|
| 1007 |
+
|
| 1008 |
+
merged_matches = _merge_scan_matches(ranked_matches)
|
| 1009 |
+
if not merged_matches or merged_matches[0].relevance_score <= 0:
|
| 1010 |
+
return None
|
| 1011 |
+
|
| 1012 |
+
chunk_by_id = {chunk.chunk_id: chunk for chunk in scan_chunks}
|
| 1013 |
+
selected_matches = merged_matches[:SCAN_TOP_K]
|
| 1014 |
+
context_blocks: list[str] = []
|
| 1015 |
+
citations: list[Citation] = []
|
| 1016 |
+
for ref_id, match in enumerate(selected_matches, start=1):
|
| 1017 |
+
chunk = chunk_by_id.get(match.chunk_id)
|
| 1018 |
+
if chunk is None:
|
| 1019 |
+
continue
|
| 1020 |
+
snippet = match.evidence_snippet.strip() or _truncate_snippet(chunk.text)
|
| 1021 |
+
citations.append(Citation(ref_id=ref_id, snippet=snippet))
|
| 1022 |
+
context_blocks.append(
|
| 1023 |
+
f"[ref{ref_id}] Chunk {match.chunk_id} (score {match.relevance_score})\n"
|
| 1024 |
+
f"Evidence: {snippet}\n"
|
| 1025 |
+
f"Full context:\n{chunk.text.strip()}"
|
| 1026 |
+
)
|
| 1027 |
+
|
| 1028 |
+
if not citations:
|
| 1029 |
+
return None
|
| 1030 |
+
return "\n\n".join(context_blocks), citations
|
| 1031 |
+
|
| 1032 |
+
|
| 1033 |
+
def _run_scan_batch_with_retry(
|
| 1034 |
+
provider_client: ProviderClient,
|
| 1035 |
+
batch: list[ScanChunk],
|
| 1036 |
+
question: str,
|
| 1037 |
+
) -> ScanResult | None:
|
| 1038 |
+
for _ in range(2):
|
| 1039 |
+
payload = _scan_batch(provider_client, batch, question)
|
| 1040 |
+
try:
|
| 1041 |
+
return ScanResult.model_validate_json(_strip_json_fences(payload))
|
| 1042 |
+
except Exception: # noqa: BLE001
|
| 1043 |
+
continue
|
| 1044 |
+
return None
|
| 1045 |
+
|
| 1046 |
+
|
| 1047 |
+
def _scan_batch(provider_client: ProviderClient, batch: list[ScanChunk], question: str) -> str:
|
| 1048 |
+
schema = {
|
| 1049 |
+
"type": "object",
|
| 1050 |
+
"properties": {
|
| 1051 |
+
"matches": {
|
| 1052 |
+
"type": "array",
|
| 1053 |
+
"items": {
|
| 1054 |
+
"type": "object",
|
| 1055 |
+
"properties": {
|
| 1056 |
+
"chunk_id": {"type": "integer"},
|
| 1057 |
+
"relevance_score": {"type": "integer", "minimum": 0, "maximum": 3},
|
| 1058 |
+
"evidence_snippet": {"type": "string"},
|
| 1059 |
+
},
|
| 1060 |
+
"required": ["chunk_id", "relevance_score", "evidence_snippet"],
|
| 1061 |
+
"additionalProperties": False,
|
| 1062 |
+
},
|
| 1063 |
+
}
|
| 1064 |
+
},
|
| 1065 |
+
"required": ["matches"],
|
| 1066 |
+
"additionalProperties": False,
|
| 1067 |
+
}
|
| 1068 |
+
prompt = (
|
| 1069 |
+
"You are scanning chunks from a government bill to find context relevant to a user question. "
|
| 1070 |
+
"Return only JSON matching the schema. Include only chunks that are at least somewhat relevant. "
|
| 1071 |
+
"Use relevance_score from 0 to 3, where 3 is highly relevant and 0 is irrelevant. "
|
| 1072 |
+
"Keep evidence_snippet short and copied from the chunk. "
|
| 1073 |
+
+ OBJECTIVITY_INSTRUCTION
|
| 1074 |
+
)
|
| 1075 |
+
chunk_text = "\n\n".join(f"Chunk {chunk.chunk_id}:\n{chunk.text}" for chunk in batch)
|
| 1076 |
+
user_text = (
|
| 1077 |
+
f"Question:\n{question}\n\n"
|
| 1078 |
+
f"Chunks:\n{chunk_text}\n\n"
|
| 1079 |
+
f"Return JSON matching this schema:\n{json.dumps(schema)}"
|
| 1080 |
+
)
|
| 1081 |
+
|
| 1082 |
+
if provider_client.name == "openai":
|
| 1083 |
+
response = provider_client.client.responses.create(
|
| 1084 |
+
model=provider_client.default_model,
|
| 1085 |
+
input=[
|
| 1086 |
+
{"role": "system", "content": prompt},
|
| 1087 |
+
{"role": "user", "content": user_text},
|
| 1088 |
+
],
|
| 1089 |
+
response_format={
|
| 1090 |
+
"type": "json_schema",
|
| 1091 |
+
"json_schema": {"name": "ChunkScan", "schema": schema},
|
| 1092 |
+
},
|
| 1093 |
+
**_openai_reasoning_kwargs(provider_client),
|
| 1094 |
+
)
|
| 1095 |
+
return _extract_openai_json(response)
|
| 1096 |
+
if provider_client.name == "qwen":
|
| 1097 |
+
response = provider_client.client.chat.completions.create(
|
| 1098 |
+
model=provider_client.default_model,
|
| 1099 |
+
messages=[
|
| 1100 |
+
{"role": "system", "content": prompt},
|
| 1101 |
+
{"role": "user", "content": user_text},
|
| 1102 |
+
],
|
| 1103 |
+
temperature=0,
|
| 1104 |
+
)
|
| 1105 |
+
return _extract_chat_completion_text(response)
|
| 1106 |
+
if provider_client.name == "anthropic":
|
| 1107 |
+
response = provider_client.client.messages.create(
|
| 1108 |
+
model=provider_client.default_model,
|
| 1109 |
+
max_tokens=1200,
|
| 1110 |
+
system=prompt,
|
| 1111 |
+
messages=[{"role": "user", "content": [{"type": "text", "text": user_text}]}],
|
| 1112 |
+
**_anthropic_thinking_kwargs(provider_client),
|
| 1113 |
+
)
|
| 1114 |
+
return response.content[0].text
|
| 1115 |
+
if provider_client.name == "cohere":
|
| 1116 |
+
response = provider_client.client.chat(
|
| 1117 |
+
message=user_text,
|
| 1118 |
+
model=provider_client.default_model,
|
| 1119 |
+
preamble=prompt,
|
| 1120 |
+
max_tokens=1200,
|
| 1121 |
+
temperature=0,
|
| 1122 |
+
response_format={
|
| 1123 |
+
"type": "json_object",
|
| 1124 |
+
"schema": schema,
|
| 1125 |
+
},
|
| 1126 |
+
)
|
| 1127 |
+
return _extract_cohere_text(response)
|
| 1128 |
+
|
| 1129 |
+
genai = provider_client.client
|
| 1130 |
+
model = genai.GenerativeModel(
|
| 1131 |
+
model_name=provider_client.default_model,
|
| 1132 |
+
system_instruction=prompt,
|
| 1133 |
+
generation_config=genai.GenerationConfig(
|
| 1134 |
+
temperature=0,
|
| 1135 |
+
response_mime_type="application/json",
|
| 1136 |
+
),
|
| 1137 |
+
)
|
| 1138 |
+
response = model.generate_content(user_text)
|
| 1139 |
+
return _extract_gemini_text(response)
|
| 1140 |
+
|
| 1141 |
+
|
| 1142 |
+
def _merge_scan_matches(matches: list[ScanMatch]) -> list[ScanMatch]:
|
| 1143 |
+
by_chunk_id: dict[int, ScanMatch] = {}
|
| 1144 |
+
for match in matches:
|
| 1145 |
+
existing = by_chunk_id.get(match.chunk_id)
|
| 1146 |
+
candidate_snippet = match.evidence_snippet.strip()
|
| 1147 |
+
if existing is None or match.relevance_score > existing.relevance_score:
|
| 1148 |
+
by_chunk_id[match.chunk_id] = match
|
| 1149 |
+
elif (
|
| 1150 |
+
existing.relevance_score == match.relevance_score
|
| 1151 |
+
and candidate_snippet
|
| 1152 |
+
and len(candidate_snippet) > len(existing.evidence_snippet.strip())
|
| 1153 |
+
):
|
| 1154 |
+
by_chunk_id[match.chunk_id] = match
|
| 1155 |
+
return sorted(
|
| 1156 |
+
by_chunk_id.values(),
|
| 1157 |
+
key=lambda item: (-item.relevance_score, item.chunk_id),
|
| 1158 |
+
)
|
| 1159 |
+
|
| 1160 |
+
|
| 1161 |
+
def _truncate_snippet(text: str, limit: int = 220) -> str:
|
| 1162 |
+
compact = " ".join(text.split())
|
| 1163 |
+
if len(compact) <= limit:
|
| 1164 |
+
return compact
|
| 1165 |
+
return compact[: limit - 3].rstrip() + "..."
|
| 1166 |
+
|
| 1167 |
+
|
| 1168 |
+
def _extract_openai_json(response: Any) -> str:
|
| 1169 |
+
buffer = []
|
| 1170 |
+
output_items = getattr(response, "output", None)
|
| 1171 |
+
if output_items:
|
| 1172 |
+
for item in output_items:
|
| 1173 |
+
for block in item.content:
|
| 1174 |
+
if getattr(block, "type", None) == "output_json_schema":
|
| 1175 |
+
buffer.append(block.text)
|
| 1176 |
+
elif getattr(block, "type", None) == "output_text":
|
| 1177 |
+
buffer.append(block.text)
|
| 1178 |
+
if buffer:
|
| 1179 |
+
return "".join(buffer)
|
| 1180 |
+
if hasattr(response, "output_text"):
|
| 1181 |
+
return response.output_text
|
| 1182 |
+
return ""
|
| 1183 |
+
|
| 1184 |
+
|
| 1185 |
+
def _extract_openai_text(response: Any) -> str:
|
| 1186 |
+
buffer = []
|
| 1187 |
+
output_items = getattr(response, "output", None)
|
| 1188 |
+
if output_items:
|
| 1189 |
+
for item in output_items:
|
| 1190 |
+
for block in item.content:
|
| 1191 |
+
if getattr(block, "type", None) in ("output_text", "output_message"):
|
| 1192 |
+
buffer.append(block.text)
|
| 1193 |
+
if buffer:
|
| 1194 |
+
return "".join(buffer)
|
| 1195 |
+
if hasattr(response, "output_text"):
|
| 1196 |
+
return response.output_text
|
| 1197 |
+
return ""
|
| 1198 |
+
|
| 1199 |
+
|
| 1200 |
+
def _extract_chat_completion_text(response: Any) -> str:
|
| 1201 |
+
choices = getattr(response, "choices", None) or []
|
| 1202 |
+
if not choices:
|
| 1203 |
+
return ""
|
| 1204 |
+
message = getattr(choices[0], "message", None)
|
| 1205 |
+
if message is None:
|
| 1206 |
+
return ""
|
| 1207 |
+
content = getattr(message, "content", None)
|
| 1208 |
+
if isinstance(content, str):
|
| 1209 |
+
return _strip_thinking_block(content)
|
| 1210 |
+
if isinstance(content, list):
|
| 1211 |
+
parts = []
|
| 1212 |
+
for item in content:
|
| 1213 |
+
text = getattr(item, "text", None)
|
| 1214 |
+
if text:
|
| 1215 |
+
parts.append(text)
|
| 1216 |
+
return _strip_thinking_block("".join(parts))
|
| 1217 |
+
return ""
|
| 1218 |
+
|
| 1219 |
+
|
| 1220 |
+
def _extract_gemini_text(response: Any) -> str:
|
| 1221 |
+
text = getattr(response, "text", None)
|
| 1222 |
+
if text:
|
| 1223 |
+
return text
|
| 1224 |
+
candidates = getattr(response, "candidates", None)
|
| 1225 |
+
if candidates:
|
| 1226 |
+
parts = []
|
| 1227 |
+
for candidate in candidates:
|
| 1228 |
+
content = getattr(candidate, "content", None)
|
| 1229 |
+
if not content:
|
| 1230 |
+
continue
|
| 1231 |
+
for part in getattr(content, "parts", []) or []:
|
| 1232 |
+
segment = getattr(part, "text", None)
|
| 1233 |
+
if segment:
|
| 1234 |
+
parts.append(segment)
|
| 1235 |
+
if parts:
|
| 1236 |
+
return "".join(parts)
|
| 1237 |
+
return ""
|
| 1238 |
+
|
| 1239 |
+
|
| 1240 |
+
def _extract_cohere_text(response: Any) -> str:
|
| 1241 |
+
message = getattr(response, "message", None)
|
| 1242 |
+
if message:
|
| 1243 |
+
content = getattr(message, "content", None) or []
|
| 1244 |
+
parts = []
|
| 1245 |
+
for item in content:
|
| 1246 |
+
text = getattr(item, "text", None)
|
| 1247 |
+
if text:
|
| 1248 |
+
parts.append(text)
|
| 1249 |
+
if parts:
|
| 1250 |
+
return "".join(parts)
|
| 1251 |
+
text = getattr(response, "text", None)
|
| 1252 |
+
if text:
|
| 1253 |
+
return text
|
| 1254 |
+
return ""
|
| 1255 |
+
|
| 1256 |
+
|
| 1257 |
+
def _strip_json_fences(value: str) -> str:
|
| 1258 |
+
stripped = value.strip()
|
| 1259 |
+
if stripped.startswith("```json"):
|
| 1260 |
+
stripped = stripped[7:]
|
| 1261 |
+
elif stripped.startswith("```"):
|
| 1262 |
+
stripped = stripped[3:]
|
| 1263 |
+
if stripped.endswith("```"):
|
| 1264 |
+
stripped = stripped[:-3]
|
| 1265 |
+
return stripped.strip()
|
| 1266 |
+
|
| 1267 |
+
|
| 1268 |
+
def _strip_thinking_block(value: str) -> str:
|
| 1269 |
+
stripped = value.strip()
|
| 1270 |
+
without_think = re.sub(r"(?is)<think>.*?</think>", "", stripped).strip()
|
| 1271 |
+
if without_think:
|
| 1272 |
+
return without_think
|
| 1273 |
+
if "</think>" in stripped:
|
| 1274 |
+
_, _, tail = stripped.rpartition("</think>")
|
| 1275 |
+
return tail.strip()
|
| 1276 |
+
return stripped
|