Skip to content

Commit

Permalink
good progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ab-smith committed Dec 7, 2024
1 parent 56f7905 commit 451fb8b
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 26 deletions.
173 changes: 166 additions & 7 deletions backend/core/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,66 @@
from docxtpl import InlineImage
from docx.shared import Cm
import matplotlib.pyplot as plt
import numpy as np

matplotlib.use("Agg")


def plot_bar(data):
def plot_horizontal_bar(data, colors=None, title=None):
"""
Create a horizontal bar chart from the input data
Args:
data (list): List of dictionaries with 'category' and 'value' keys
colors (list, optional): Custom color palette
title (str, optional): Chart title
Returns:
io.BytesIO: Buffer containing the horizontal bar chart image
"""
# Prepare data
categories = [item["category"] for item in data]
values = [item["value"] for item in data]

# Default color palette
default_colors = [
"#2196F3", # Blue
"#4CAF50", # Green
"#FFC107", # Amber
"#F44336", # Red
"#9C27B0", # Purple
]

# Create the plot
plt.figure(figsize=(10, 6))
plt.bar(
[item["category"] for item in data],
[item["value"] for item in data],
)

# Choose colors
plot_colors = colors if colors is not None else default_colors[: len(categories)]

# Create horizontal bar plot
plt.barh(categories, values, color=plot_colors)

# Add value labels at the end of each bar
for i, v in enumerate(values):
plt.text(v, i, f" {v}", va="center")

# Customize the plot
plt.xlabel("Value")
plt.ylabel("Category")

# Add title if provided
if title:
plt.title(title)

# Adjust layout
plt.tight_layout()

# Save the plot
chart_buffer = io.BytesIO()
plt.savefig(chart_buffer, format="png")
plt.savefig(chart_buffer, format="png", dpi=300)
chart_buffer.seek(0)
plt.close()

return chart_buffer


Expand Down Expand Up @@ -74,6 +119,72 @@ def plot_donut(data, colors=None):
return chart_buffer


def plot_spider_chart(data, colors=None, title=None):
"""
Create a spider/radar chart from the input data
Args:
data (list): List of dictionaries with 'category' and 'value' keys
colors (list, optional): Custom color palette
title (str, optional): Chart title
Returns:
io.BytesIO: Buffer containing the spider chart image
"""
# Prepare data
categories = [item["category"] for item in data]
values = [item["value"] for item in data]

# Number of variables
N = len(categories)

# Default color palette
default_colors = [
"#2196F3", # Blue
"#4CAF50", # Green
"#FFC107", # Amber
"#F44336", # Red
"#9C27B0", # Purple
]

# Compute angle for each axis
angles = [n / float(N) * 2 * np.pi for n in range(N)]

# Close the plot by appending the first value and angle
values += values[:1]
angles += angles[:1]

# Create the plot
plt.figure(figsize=(8, 8))
ax = plt.subplot(111, polar=True)

# Choose colors
plot_colors = colors if colors is not None else default_colors[: len(categories)]

# Plot data
ax.plot(angles, values, "o-", linewidth=2, color=plot_colors[0])
ax.fill(angles, values, alpha=0.25, color=plot_colors[0])

# Fix axis to go in the right order and start at 12 o'clock
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)

# Draw axis lines for each angle and label
plt.xticks(angles[:-1], categories)

# Set y-axis limits (optional, adjust as needed)
ax.set_ylim(0, max(values) * 1.1)

# Save the plot
plt.tight_layout()
chart_buffer = io.BytesIO()
plt.savefig(chart_buffer, format="png", dpi=300, bbox_inches="tight")
chart_buffer.seek(0)
plt.close()

return chart_buffer


def gen_audit_context(id, doc):
context = dict()
audit = ComplianceAssessment.objects.get(id=id)
Expand All @@ -92,7 +203,49 @@ def gen_audit_context(id, doc):
{"category": "Non évalué", "value": cnt_per_result[0][0]},
]

spider_data = [
{"category": "Compliance", "value": 85},
{"category": "Risk Management", "value": 70},
{"category": "Security Controls", "value": 90},
{"category": "Incident Response", "value": 75},
{"category": "Policy Adherence", "value": 80},
{"category": "Policy Adherence1", "value": 80},
{"category": "Policy Adherence2", "value": 80},
{"category": "Policy Adherence3", "value": 80},
{"category": "Policy Adherence4", "value": 80},
{"category": "Policy Adherence5", "value": 80},
]
custom_colors = ["#2196F3"]
spider_chart_buffer = plot_spider_chart(
spider_data,
colors=custom_colors,
)
horizontal_bar_data = [
{"category": "--", "value": 85},
{"category": "A faire", "value": 70},
{"category": "En cours", "value": 90},
{"category": "Bloquées", "value": 90},
{"category": "Actives", "value": 75},
{"category": "Obsolètes", "value": 80},
]

# Custom color (optional)
custom_colors = [
"#1976D2", # Deep Blue
"#388E3C", # Green
"#FFA000", # Amber
"#D32F2F", # Red
"#7B1FA2", # Purple
]

horizontal_bar_buffer = plot_horizontal_bar(
horizontal_bar_data, colors=custom_colors
)

# Create InlineImage for the document
res_donut = InlineImage(doc, plot_donut(donut_data), width=Cm(15))
chart_spider = InlineImage(doc, spider_chart_buffer, width=Cm(15))
chart_horizontal_bar = InlineImage(doc, horizontal_bar_buffer, width=Cm(15))

context = {
"audit": audit,
Expand All @@ -106,7 +259,13 @@ def gen_audit_context(id, doc):
"not_applicable": cnt_per_result[4][0],
"not_assessed": cnt_per_result[0][0],
},
"chart_progress_donut": res_donut,
"compliance_donut": res_donut,
"compliance_radar": chart_spider,
"chart_controls": chart_horizontal_bar,
"p1_controls": [
{"name": "sample", "type": "govern"},
{"name": "another", "type": "user"},
],
}

return context
Binary file modified backend/core/templates/core/audit_report_template.docx
Binary file not shown.
5 changes: 0 additions & 5 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@
import humanize


from docx import Document
from docx.shared import Inches
import matplotlib.pyplot as plt
import seaborn as sns
import io
import base64

from docxtpl import DocxTemplate
from .generators import gen_audit_context
Expand Down
2 changes: 1 addition & 1 deletion backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ huey = "^2.5.2"
seaborn = "^0.13.2"
python-docx = "^1.1.2"
docxtpl = "^0.19.0"
numpy = "^2.1.3"

[tool.poetry.group.dev.dependencies]
pytest-django = "4.8.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import { BASE_API_URL } from '$lib/utils/constants';
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ fetch, params }) => {
const URLModel = 'compliance-assessments';
const endpoint = `${BASE_API_URL}/${URLModel}/${params.id}/word_report/`;
const URLModel = 'compliance-assessments';
const endpoint = `${BASE_API_URL}/${URLModel}/${params.id}/word_report/`;

const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error fetching the Word file');
}
const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error fetching the Word file');
}

const fileName = `audit-report-${new Date().toISOString()}.docx`;
const fileName = `audit-exec-summary-${new Date().toISOString()}.docx`;

return new Response(await res.blob(), {
headers: {
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Disposition': `attachment; filename="${fileName}"`
}
});
return new Response(await res.blob(), {
headers: {
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Disposition': `attachment; filename="${fileName}"`
}
});
};

0 comments on commit 451fb8b

Please sign in to comment.