added sop pdf generator
This commit is contained in:
@@ -1,140 +1,244 @@
|
||||
import requests
|
||||
import pandas as pd
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, Image
|
||||
from reportlab.platypus import Frame, PageTemplate, NextPageTemplate
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT, TA_RIGHT
|
||||
from reportlab.lib.units import inch, cm
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
DATA_KEY = os.getenv("AI_DATA_KEY")
|
||||
# Constants for API requests
|
||||
URL = "https://erpai.mkdlabs.com//v3/api/custom/erpai/common/get-data-ai"
|
||||
HEADERS = {
|
||||
"x-project": DATA_KEY # Replace with your actual key
|
||||
}
|
||||
def header_footer(canvas, doc):
|
||||
"""Add the header and footer to each page"""
|
||||
canvas.saveState()
|
||||
|
||||
# Header
|
||||
header_text = "Standard Operating Procedures"
|
||||
canvas.setFont('Helvetica-Bold', 10)
|
||||
canvas.drawString(72, letter[1] - 40, header_text)
|
||||
|
||||
# Add a line below the header
|
||||
canvas.setStrokeColor(colors.lightgrey)
|
||||
canvas.line(72, letter[1] - 50, letter[0] - 72, letter[1] - 50)
|
||||
|
||||
# Footer with page number and date
|
||||
current_date = datetime.datetime.now().strftime("%B %d, %Y")
|
||||
page_num = f"Page {doc.page} | {current_date}"
|
||||
canvas.setFont('Helvetica', 8)
|
||||
canvas.drawString(letter[0] - 150, 40, page_num)
|
||||
|
||||
# Add a line above the footer
|
||||
canvas.line(72, 50, letter[0] - 72, 50)
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
# JSON bodies for API requests
|
||||
def create_json_body(area_type, company_id):
|
||||
return {
|
||||
"type": area_type,
|
||||
"options": {
|
||||
"company_id": company_id
|
||||
}
|
||||
}
|
||||
def convert_sop_to_pdf(sop_data, output_pdf="sop_document.pdf", company_name="Company Name"):
|
||||
"""
|
||||
Convert SOP data to a well-formatted PDF document
|
||||
|
||||
Args:
|
||||
sop_data (dict or str): SOP data in dictionary format or JSON string
|
||||
output_pdf (str): Output PDF filename
|
||||
company_name (str): Name of the company to display on the cover page
|
||||
"""
|
||||
# Parse JSON string if needed
|
||||
if isinstance(sop_data, str):
|
||||
sop_data = json.loads(sop_data)
|
||||
|
||||
# Extract SOP details
|
||||
sop_details = sop_data.get("sop_details", [])
|
||||
|
||||
# Create PDF document
|
||||
doc = SimpleDocTemplate(output_pdf, pagesize=letter,
|
||||
rightMargin=72, leftMargin=72,
|
||||
topMargin=90, bottomMargin=72)
|
||||
|
||||
# Container for PDF elements
|
||||
elements = []
|
||||
|
||||
# Get styles
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Define custom styles
|
||||
title_style = ParagraphStyle(
|
||||
'TitleStyle',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=24,
|
||||
alignment=TA_CENTER,
|
||||
spaceAfter=12,
|
||||
fontName='Helvetica-Bold',
|
||||
textColor=colors.darkblue
|
||||
)
|
||||
|
||||
subtitle_style = ParagraphStyle(
|
||||
'SubtitleStyle',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=16,
|
||||
alignment=TA_CENTER,
|
||||
spaceAfter=36,
|
||||
fontName='Helvetica-Bold',
|
||||
textColor=colors.darkblue
|
||||
)
|
||||
|
||||
heading2_style = ParagraphStyle(
|
||||
'Heading2Style',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=16,
|
||||
spaceBefore=12,
|
||||
spaceAfter=6,
|
||||
fontName='Helvetica-Bold',
|
||||
textColor=colors.darkblue,
|
||||
borderWidth=0,
|
||||
borderColor=colors.lightgrey,
|
||||
borderPadding=5,
|
||||
borderRadius=5
|
||||
)
|
||||
|
||||
heading3_style = ParagraphStyle(
|
||||
'Heading3Style',
|
||||
parent=styles['Heading3'],
|
||||
fontSize=12,
|
||||
spaceBefore=6,
|
||||
spaceAfter=3,
|
||||
fontName='Helvetica-Bold',
|
||||
textColor=colors.darkslategray
|
||||
)
|
||||
|
||||
normal_style = ParagraphStyle(
|
||||
'NormalStyle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=10,
|
||||
alignment=TA_JUSTIFY,
|
||||
leading=14,
|
||||
fontName='Helvetica'
|
||||
)
|
||||
|
||||
bullet_style = ParagraphStyle(
|
||||
'BulletStyle',
|
||||
parent=normal_style,
|
||||
leftIndent=20,
|
||||
firstLineIndent=-15,
|
||||
spaceBefore=2,
|
||||
spaceAfter=2
|
||||
)
|
||||
|
||||
# Create cover page
|
||||
elements.append(Spacer(1, 2*inch))
|
||||
elements.append(Paragraph("Standard Operating Procedures", title_style))
|
||||
elements.append(Spacer(1, 0.5*inch))
|
||||
#elements.append(Paragraph(company_name, subtitle_style))
|
||||
elements.append(Spacer(1, 2*inch))
|
||||
|
||||
# Add current date
|
||||
current_date = datetime.datetime.now().strftime("%B %d, %Y")
|
||||
date_style = ParagraphStyle(
|
||||
'DateStyle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=12,
|
||||
alignment=TA_CENTER,
|
||||
fontName='Helvetica'
|
||||
)
|
||||
elements.append(Paragraph(f"Generated on: {current_date}", date_style))
|
||||
|
||||
# Add a page break after the cover page
|
||||
elements.append(PageBreak())
|
||||
|
||||
|
||||
# Function to fetch data from the API
|
||||
def fetch_data(json_body):
|
||||
json_body["options"]["company_id"] = json_body["options"].get("company_id") # Ensure company_id is included
|
||||
response = requests.post(URL, headers=HEADERS, json=json_body)
|
||||
response.raise_for_status() # Raise an error for bad responses
|
||||
return response.json()
|
||||
|
||||
|
||||
|
||||
def convert_assessment_data_to_dataframe(assessment_data):
|
||||
df_assessment = []
|
||||
for assessment in assessment_data.get("data", []):
|
||||
assessment_id = assessment["assessment_id"]
|
||||
assessment_name = assessment["assessment_name"]
|
||||
start_date = assessment["start_date"]
|
||||
open_items = assessment["open_items"]
|
||||
completed_items = assessment["completed_items"]
|
||||
total_assigned_items = assessment["total_assigned_items"]
|
||||
red_flags = assessment["red_flags"]
|
||||
|
||||
for user in assessment.get("user_details", []):
|
||||
user_name = user["name"]
|
||||
user_total_items = user["total_assigned_items"]
|
||||
user_completed_items = user["completed_items"]
|
||||
# Process each role
|
||||
for role_data in sop_details:
|
||||
role_name = role_data.get("role", "Unnamed Role")
|
||||
sops = role_data.get("sops", {})
|
||||
narrative = role_data.get("narrative")
|
||||
|
||||
# Add role header with decorative element
|
||||
elements.append(Paragraph(role_name, heading2_style))
|
||||
|
||||
# Add horizontal rule after heading
|
||||
elements.append(Spacer(1, 0.05*inch))
|
||||
|
||||
# Add narrative if available
|
||||
if narrative and narrative != "Narrative" and narrative is not None:
|
||||
elements.append(Paragraph("Narrative:", heading3_style))
|
||||
elements.append(Paragraph(narrative, normal_style))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# Process SOPs
|
||||
for sop_type in ["must", "shall", "will"]:
|
||||
sop_items = sops.get(sop_type, [])
|
||||
|
||||
for area in user.get("area_list", []):
|
||||
df_assessment.append({
|
||||
"assessment_id": assessment_id,
|
||||
"assessment_name": assessment_name,
|
||||
"start_date": start_date,
|
||||
"open_items_overall": open_items,
|
||||
"completed_items_overall": completed_items,
|
||||
"total_assigned_items_overall": total_assigned_items,
|
||||
"user_name": user_name,
|
||||
"user_total_assigned_items": user_total_items,
|
||||
"user_completed_items": user_completed_items,
|
||||
"area": area,
|
||||
"red_flags": red_flags
|
||||
})
|
||||
return pd.DataFrame(df_assessment)
|
||||
|
||||
# Convert to DataFrame
|
||||
|
||||
|
||||
# Summary statistics for overall assessment level
|
||||
def generate_summary_statistics(df):
|
||||
total_assessments = df['assessment_id'].nunique()
|
||||
avg_open_items = df.groupby('assessment_id')['open_items_overall'].mean().mean()
|
||||
avg_completed_items = df.groupby('assessment_id')['completed_items_overall'].mean().mean()
|
||||
avg_total_assigned_items = df.groupby('assessment_id')['total_assigned_items_overall'].mean().mean()
|
||||
avg_red_flags = df['red_flags'].mean()
|
||||
|
||||
total_users = df['user_name'].nunique()
|
||||
avg_user_total_items = df.groupby('user_name')['user_total_assigned_items'].mean().mean()
|
||||
avg_user_completed_items = df.groupby('user_name')['user_completed_items'].mean().mean()
|
||||
completion_rate_per_user = (df['user_completed_items'].sum() / df['user_total_assigned_items'].sum()) * 100 if df['user_total_assigned_items'].sum() > 0 else 0
|
||||
if sop_items:
|
||||
# Capitalize the first letter of SOP type and make it bold
|
||||
sop_type_title = sop_type.capitalize()
|
||||
elements.append(Paragraph(f"{sop_type_title}:", heading3_style))
|
||||
|
||||
# Create bullet points for each SOP item with better formatting
|
||||
for item in sop_items:
|
||||
elements.append(Paragraph(f"• {item}", bullet_style))
|
||||
|
||||
elements.append(Spacer(1, 0.15*inch))
|
||||
|
||||
# Add a page break between roles for cleaner separation
|
||||
elements.append(PageBreak())
|
||||
|
||||
area_summary = df['area'].value_counts()
|
||||
# Build the PDF document with header and footer
|
||||
doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)
|
||||
|
||||
return output_pdf
|
||||
|
||||
return {
|
||||
"total_assessments": total_assessments,
|
||||
"avg_open_items_per_assessment": avg_open_items,
|
||||
"avg_completed_items_per_assessment": avg_completed_items,
|
||||
"avg_total_assigned_items_per_assessment": avg_total_assigned_items,
|
||||
"avg_red_flags": avg_red_flags,
|
||||
"total_users": total_users,
|
||||
"avg_user_total_assigned_items": avg_user_total_items,
|
||||
"avg_user_completed_items": avg_user_completed_items,
|
||||
"completion_rate_per_user": completion_rate_per_user,
|
||||
"area_summary": area_summary.to_dict()
|
||||
def main():
|
||||
# Example usage
|
||||
sop_json = """
|
||||
{
|
||||
"sop_details": [
|
||||
{
|
||||
"role": "Sales Manager",
|
||||
"role_id": 140,
|
||||
"sops": {
|
||||
"must": [],
|
||||
"shall": [
|
||||
"Shall develop and implement sales strategies to achieve company targets.",
|
||||
"Shall conduct regular performance reviews with the sales team to ensure targets are met.",
|
||||
"Shall provide training and support to sales staff to enhance their skills and performance.",
|
||||
"Shall analyze market trends and adjust sales strategies accordingly.",
|
||||
"Shall maintain accurate records of sales activities and customer interactions."
|
||||
],
|
||||
"will": [
|
||||
"Will lead the sales team to achieve monthly and quarterly sales goals.",
|
||||
"Will collaborate with marketing to align sales strategies with promotional campaigns.",
|
||||
"Will report sales performance to upper management on a regular basis.",
|
||||
"Will identify potential new markets and customer segments for growth.",
|
||||
"Will foster a positive team environment to motivate sales staff."
|
||||
]
|
||||
},
|
||||
"areas": [],
|
||||
"narrative": "The Sales Manager is responsible for leading and developing the sales team to achieve business targets and growth objectives. They will implement effective sales strategies, provide coaching to team members, and maintain strong customer relationships while ensuring all sales activities align with the company's overall goals."
|
||||
},
|
||||
{
|
||||
"role": "Campaign Manager",
|
||||
"role_id": 141,
|
||||
"sops": {
|
||||
"must": [],
|
||||
"shall": [],
|
||||
"will": [
|
||||
"Will develop and execute marketing campaigns to promote products and services.",
|
||||
"Will analyze campaign performance metrics to optimize future campaigns.",
|
||||
"Will collaborate with cross-functional teams to ensure campaign alignment with business objectives.",
|
||||
"Will manage campaign budgets and ensure effective allocation of resources.",
|
||||
"Will stay updated on industry trends to inform campaign strategies."
|
||||
]
|
||||
},
|
||||
"areas": [],
|
||||
"narrative": "The Campaign Manager oversees the planning, execution, and analysis of marketing campaigns across various channels to drive brand awareness and customer acquisition. They work closely with creative teams, external vendors, and stakeholders to ensure campaigns are effective, on-brand, and deliver measurable results."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Additional statistics for efficiency and areas
|
||||
def generate_extended_statistics(df):
|
||||
df['user_completion_rate'] = (df['user_completed_items'] / df['user_total_assigned_items']).fillna(0) * 100
|
||||
|
||||
top_5_efficient_users = df.groupby('user_name')['user_completion_rate'].mean().nlargest(5).to_dict()
|
||||
bottom_5_least_efficient_users = df.groupby('user_name')['user_completion_rate'].mean().nsmallest(5).to_dict()
|
||||
|
||||
df['uncompleted_items'] = df['user_total_assigned_items'] - df['user_completed_items']
|
||||
areas_with_most_uncompleted_items = df.groupby('area')['uncompleted_items'].sum().nlargest(5).to_dict()
|
||||
|
||||
return {
|
||||
"top_5_efficient_users": top_5_efficient_users,
|
||||
"bottom_5_least_efficient_users": bottom_5_least_efficient_users,
|
||||
"areas_with_most_uncompleted_items": areas_with_most_uncompleted_items
|
||||
}
|
||||
|
||||
# Generate statistics for problematic areas
|
||||
def generate_problematic_area_statistics(df):
|
||||
total_open_items = df.groupby('name')['open_items'].sum().sort_values(ascending=False)
|
||||
total_red_flags = df.groupby('name')['red_flags'].sum().sort_values(ascending=False)
|
||||
|
||||
return pd.DataFrame({
|
||||
"total_open_items": total_open_items,
|
||||
"total_red_flags": total_red_flags
|
||||
}).fillna(0)
|
||||
|
||||
def generate_summary_stats(assessment_data, area_data):
|
||||
assessment_df = convert_assessment_data_to_dataframe(assessment_data)
|
||||
problematic_area_df = pd.DataFrame(area_data.get("data", []))
|
||||
"""
|
||||
|
||||
summary_stats = generate_summary_statistics(assessment_df)
|
||||
extended_stats = generate_extended_statistics(assessment_df)
|
||||
summary_stats["users(Workers) based stats"] = extended_stats
|
||||
|
||||
problematic_stats = generate_problematic_area_statistics(problematic_area_df)
|
||||
summary_stats["Area based stats"] = problematic_stats.to_dict(orient='index')
|
||||
|
||||
return summary_stats
|
||||
|
||||
# You can replace the company name with your own
|
||||
output_file = convert_sop_to_pdf(sop_json, company_name="Strategic Business Solutions, Inc.")
|
||||
print(f"PDF successfully generated: {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.services.chatbot import Chatbot
|
||||
bot = Chatbot()
|
||||
res = bot.predict_next_n_assessment(companyid=12,N=3)
|
||||
main()
|
||||
|
||||
print(res)
|
||||
Reference in New Issue
Block a user