Update app.py
Browse files
app.py
CHANGED
|
@@ -1,18 +1,15 @@
|
|
| 1 |
"""
|
| 2 |
-
Governance-GPT Quiz
|
| 3 |
Author: Rishabh Sharma · 2025-09-14
|
| 4 |
-
One-file Gradio Space → 15-Q Likert survey → bucket scores → FLAN-T5 summary → PDF
|
| 5 |
"""
|
| 6 |
|
| 7 |
-
import datetime, tempfile, warnings
|
| 8 |
import gradio as gr
|
| 9 |
import pandas as pd
|
| 10 |
-
from fpdf import FPDF
|
| 11 |
-
from transformers import pipeline
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
# 1. Load a lightweight model once (≈ 80 M params, CPU-friendly)
|
| 15 |
-
# ───────────────────────────────────────────────────────────
|
| 16 |
warnings.filterwarnings("ignore", category=UserWarning)
|
| 17 |
summariser = pipeline(
|
| 18 |
task="text2text-generation",
|
|
@@ -21,9 +18,7 @@ summariser = pipeline(
|
|
| 21 |
max_new_tokens=150,
|
| 22 |
)
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
# 2. Question bank & buckets
|
| 26 |
-
# ───────────────────────────────────────────────────────────
|
| 27 |
QUESTIONS = [
|
| 28 |
"Governance framework is documented and communicated across the organisation.",
|
| 29 |
"Roles & responsibilities for AI oversight are clearly assigned.",
|
|
@@ -62,24 +57,19 @@ TIERS = {
|
|
| 62 |
"Optimized": (4.51, 5.00),
|
| 63 |
}
|
| 64 |
|
| 65 |
-
#
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
for tier, (low, high) in TIERS.items():
|
| 70 |
-
if low <= avg <= high:
|
| 71 |
return tier
|
| 72 |
return "Unclassified"
|
| 73 |
|
| 74 |
def latin1(txt: str) -> str:
|
| 75 |
-
"""
|
|
|
|
| 76 |
return txt.encode("latin-1", "replace").decode("latin-1")
|
| 77 |
|
| 78 |
def llm_remediation(product: str, bucket_avgs: dict, overall_tier: str) -> str:
|
| 79 |
-
"""
|
| 80 |
-
Ask FLAN-T5-small to write a short markdown summary.
|
| 81 |
-
Uses only ASCII bullets ('-') so the PDF core font can render them.
|
| 82 |
-
"""
|
| 83 |
bucket_lines = "\n".join(f"- {b}: {v:.2f}" for b, v in bucket_avgs.items())
|
| 84 |
prompt = (
|
| 85 |
"You are an AI governance consultant.\n"
|
|
@@ -87,41 +77,42 @@ def llm_remediation(product: str, bucket_avgs: dict, overall_tier: str) -> str:
|
|
| 87 |
f"Overall tier: {overall_tier}\n"
|
| 88 |
"Bucket scores (1-5):\n"
|
| 89 |
f"{bucket_lines}\n\n"
|
| 90 |
-
"Write a concise markdown summary (
|
| 91 |
"- One overall assessment sentence.\n"
|
| 92 |
"- 3-5 bullet remediation actions referencing bucket names.\n"
|
| 93 |
"Return only the summary."
|
| 94 |
)
|
| 95 |
|
| 96 |
-
|
| 97 |
-
raw = summariser(prompt)[0]["generated_text"].strip()
|
| 98 |
|
| 99 |
-
#
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
-
# Replace
|
| 104 |
-
|
| 105 |
-
return summary if summary else "Summary unavailable."
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
pdf = FPDF()
|
| 111 |
pdf.set_auto_page_break(auto=True, margin=15)
|
| 112 |
pdf.add_page()
|
| 113 |
|
| 114 |
pdf.set_font("Helvetica", "B", 16)
|
| 115 |
-
pdf.cell(0, 10, latin1(f"AI Governance Maturity Report
|
| 116 |
|
| 117 |
pdf.set_font("Helvetica", "", 12)
|
| 118 |
-
pdf.cell(0, 8,
|
| 119 |
pdf.ln(4)
|
| 120 |
|
| 121 |
pdf.set_font("Helvetica", "B", 12)
|
| 122 |
-
pdf.cell(0, 8, latin1(f"Overall Score: {overall_avg:.2f}
|
| 123 |
pdf.set_font("Helvetica", "", 11)
|
| 124 |
-
pdf.multi_cell(0, 6, latin1(
|
| 125 |
pdf.ln(4)
|
| 126 |
|
| 127 |
pdf.set_font("Helvetica", "B", 11)
|
|
@@ -139,19 +130,16 @@ def build_pdf(product: str, bucket_df: pd.DataFrame,
|
|
| 139 |
pdf.cell(35, 8, f"{overall_avg:.2f}", 1)
|
| 140 |
pdf.cell(35, 8, overall_tier, 1, ln=1)
|
| 141 |
|
| 142 |
-
pdf.output(
|
| 143 |
|
| 144 |
-
#
|
| 145 |
-
# 4. Gradio callback
|
| 146 |
-
# ───────────────────────────────────────────────────────────
|
| 147 |
def generate_report(product_name, *scores):
|
| 148 |
product = product_name.strip() or "your product"
|
| 149 |
scores = list(scores)
|
| 150 |
|
| 151 |
-
# Bucket averages
|
| 152 |
bucket_avgs = {
|
| 153 |
-
|
| 154 |
-
for
|
| 155 |
}
|
| 156 |
overall_avg = sum(scores) / len(scores)
|
| 157 |
overall_tier = score_to_tier(overall_avg)
|
|
@@ -169,24 +157,22 @@ def generate_report(product_name, *scores):
|
|
| 169 |
|
| 170 |
return summary_md, tmp_pdf.name
|
| 171 |
|
| 172 |
-
#
|
| 173 |
-
# 5. Gradio UI
|
| 174 |
-
# ───────────────────────────────────────────────────────────
|
| 175 |
with gr.Blocks(title="Governance-GPT Quiz") as demo:
|
| 176 |
gr.Markdown(
|
| 177 |
"""
|
| 178 |
# Governance-GPT Quiz
|
| 179 |
Enter your **product / system name**, rate each statement from **1 (Strongly Disagree)** to **5 (Strongly Agree)**,
|
| 180 |
-
|
| 181 |
"""
|
| 182 |
)
|
| 183 |
|
| 184 |
-
product_inp = gr.Textbox(label="Product / System Name", placeholder="e.g. AcmeAI
|
| 185 |
-
sliders = [gr.Slider(1, 5,
|
| 186 |
|
| 187 |
btn = gr.Button("Generate PDF Report")
|
| 188 |
summary_out = gr.Markdown()
|
| 189 |
-
pdf_out = gr.File(label="⬇️ Download
|
| 190 |
|
| 191 |
btn.click(fn=generate_report,
|
| 192 |
inputs=[product_inp] + sliders,
|
|
|
|
| 1 |
"""
|
| 2 |
+
Governance-GPT Quiz – ASCII-safe, LLM-powered PDF
|
| 3 |
Author: Rishabh Sharma · 2025-09-14
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
import datetime, tempfile, re, warnings
|
| 7 |
import gradio as gr
|
| 8 |
import pandas as pd
|
| 9 |
+
from fpdf import FPDF
|
| 10 |
+
from transformers import pipeline
|
| 11 |
|
| 12 |
+
# ── Load a lightweight CPU model ───────────────────────────────────
|
|
|
|
|
|
|
| 13 |
warnings.filterwarnings("ignore", category=UserWarning)
|
| 14 |
summariser = pipeline(
|
| 15 |
task="text2text-generation",
|
|
|
|
| 18 |
max_new_tokens=150,
|
| 19 |
)
|
| 20 |
|
| 21 |
+
# ── Questions & buckets ────────────────────────────────────────────
|
|
|
|
|
|
|
| 22 |
QUESTIONS = [
|
| 23 |
"Governance framework is documented and communicated across the organisation.",
|
| 24 |
"Roles & responsibilities for AI oversight are clearly assigned.",
|
|
|
|
| 57 |
"Optimized": (4.51, 5.00),
|
| 58 |
}
|
| 59 |
|
| 60 |
+
# ── Helpers ─────────────────────────────────────────────────────────
|
| 61 |
+
def score_to_tier(avg):
|
| 62 |
+
for tier, (lo, hi) in TIERS.items():
|
| 63 |
+
if lo <= avg <= hi:
|
|
|
|
|
|
|
| 64 |
return tier
|
| 65 |
return "Unclassified"
|
| 66 |
|
| 67 |
def latin1(txt: str) -> str:
|
| 68 |
+
"""Replace common Unicode chars with ASCII equivalents, then enforce Latin-1."""
|
| 69 |
+
txt = txt.replace("–", "-").replace("—", "-").replace("•", "-")
|
| 70 |
return txt.encode("latin-1", "replace").decode("latin-1")
|
| 71 |
|
| 72 |
def llm_remediation(product: str, bucket_avgs: dict, overall_tier: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
bucket_lines = "\n".join(f"- {b}: {v:.2f}" for b, v in bucket_avgs.items())
|
| 74 |
prompt = (
|
| 75 |
"You are an AI governance consultant.\n"
|
|
|
|
| 77 |
f"Overall tier: {overall_tier}\n"
|
| 78 |
"Bucket scores (1-5):\n"
|
| 79 |
f"{bucket_lines}\n\n"
|
| 80 |
+
"Write a concise markdown summary (<=120 words) with:\n"
|
| 81 |
"- One overall assessment sentence.\n"
|
| 82 |
"- 3-5 bullet remediation actions referencing bucket names.\n"
|
| 83 |
"Return only the summary."
|
| 84 |
)
|
| 85 |
|
| 86 |
+
raw = summariser(prompt)[0]["generated_text"]
|
|
|
|
| 87 |
|
| 88 |
+
# ── Strip any prompt echo or leftover instruction lines ──
|
| 89 |
+
lines = [
|
| 90 |
+
ln for ln in raw.splitlines()
|
| 91 |
+
if not re.search(r"bullet remediation|overall assessment|Write a concise", ln, re.I)
|
| 92 |
+
]
|
| 93 |
+
cleaned = "\n".join(lines).strip()
|
| 94 |
|
| 95 |
+
# Replace bullets with ASCII dash (in case model outputs •)
|
| 96 |
+
cleaned = cleaned.replace("•", "- ")
|
|
|
|
| 97 |
|
| 98 |
+
return cleaned or "LLM summary unavailable."
|
| 99 |
+
|
| 100 |
+
def build_pdf(product, bucket_df, overall_avg, overall_tier, pdf_path, summary):
|
| 101 |
pdf = FPDF()
|
| 102 |
pdf.set_auto_page_break(auto=True, margin=15)
|
| 103 |
pdf.add_page()
|
| 104 |
|
| 105 |
pdf.set_font("Helvetica", "B", 16)
|
| 106 |
+
pdf.cell(0, 10, latin1(f"AI Governance Maturity Report - {product}"), ln=1, align="C")
|
| 107 |
|
| 108 |
pdf.set_font("Helvetica", "", 12)
|
| 109 |
+
pdf.cell(0, 8, datetime.date.today().isoformat(), ln=1, align="C")
|
| 110 |
pdf.ln(4)
|
| 111 |
|
| 112 |
pdf.set_font("Helvetica", "B", 12)
|
| 113 |
+
pdf.cell(0, 8, latin1(f"Overall Score: {overall_avg:.2f} | Tier: {overall_tier}"), ln=1)
|
| 114 |
pdf.set_font("Helvetica", "", 11)
|
| 115 |
+
pdf.multi_cell(0, 6, latin1(summary))
|
| 116 |
pdf.ln(4)
|
| 117 |
|
| 118 |
pdf.set_font("Helvetica", "B", 11)
|
|
|
|
| 130 |
pdf.cell(35, 8, f"{overall_avg:.2f}", 1)
|
| 131 |
pdf.cell(35, 8, overall_tier, 1, ln=1)
|
| 132 |
|
| 133 |
+
pdf.output(pdf_path)
|
| 134 |
|
| 135 |
+
# ── Gradio callback ────────────────────────────────────────────────
|
|
|
|
|
|
|
| 136 |
def generate_report(product_name, *scores):
|
| 137 |
product = product_name.strip() or "your product"
|
| 138 |
scores = list(scores)
|
| 139 |
|
|
|
|
| 140 |
bucket_avgs = {
|
| 141 |
+
b: sum(scores[i] for i in idxs) / len(idxs)
|
| 142 |
+
for b, idxs in BUCKETS.items()
|
| 143 |
}
|
| 144 |
overall_avg = sum(scores) / len(scores)
|
| 145 |
overall_tier = score_to_tier(overall_avg)
|
|
|
|
| 157 |
|
| 158 |
return summary_md, tmp_pdf.name
|
| 159 |
|
| 160 |
+
# ── UI ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
| 161 |
with gr.Blocks(title="Governance-GPT Quiz") as demo:
|
| 162 |
gr.Markdown(
|
| 163 |
"""
|
| 164 |
# Governance-GPT Quiz
|
| 165 |
Enter your **product / system name**, rate each statement from **1 (Strongly Disagree)** to **5 (Strongly Agree)**,
|
| 166 |
+
then download an LLM-generated remediation plan and bucket-level PDF.
|
| 167 |
"""
|
| 168 |
)
|
| 169 |
|
| 170 |
+
product_inp = gr.Textbox(label="Product / System Name", placeholder="e.g. AcmeAI Recommender")
|
| 171 |
+
sliders = [gr.Slider(1, 5, 3, 1, label=q) for q in QUESTIONS]
|
| 172 |
|
| 173 |
btn = gr.Button("Generate PDF Report")
|
| 174 |
summary_out = gr.Markdown()
|
| 175 |
+
pdf_out = gr.File(label="⬇️ Download PDF")
|
| 176 |
|
| 177 |
btn.click(fn=generate_report,
|
| 178 |
inputs=[product_inp] + sliders,
|