added sop pdf generator
This commit is contained in:
@@ -4,3 +4,4 @@ langchain-openai
|
|||||||
pydantic
|
pydantic
|
||||||
flask
|
flask
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
reportlab
|
||||||
+63
-4
@@ -7,7 +7,11 @@ from src.utils.auth import auth_check
|
|||||||
|
|
||||||
from src.utils.utils import delete_all_files_in_directory
|
from src.utils.utils import delete_all_files_in_directory
|
||||||
from src.utils.document_loader import load_document
|
from src.utils.document_loader import load_document
|
||||||
from flask import Blueprint, jsonify, request, make_response
|
import os
|
||||||
|
import tempfile
|
||||||
|
import datetime
|
||||||
|
from flask import send_file
|
||||||
|
from flask import Blueprint, jsonify, request, make_response,after_this_request
|
||||||
import json
|
import json
|
||||||
# Initialize the Blueprint
|
# Initialize the Blueprint
|
||||||
sops_bp = Blueprint('sops', __name__)
|
sops_bp = Blueprint('sops', __name__)
|
||||||
@@ -71,6 +75,7 @@ def get_roles():
|
|||||||
def get_roles_questionnaire():
|
def get_roles_questionnaire():
|
||||||
# Check if the post request has the file part
|
# Check if the post request has the file part
|
||||||
questionnaire_data = request.json
|
questionnaire_data = request.json
|
||||||
|
role_slug = questionnaire_data.get("role_slug")
|
||||||
|
|
||||||
# Validate the required fields in the questionnaire data
|
# Validate the required fields in the questionnaire data
|
||||||
if not questionnaire_data.get('questionnaire_response'):
|
if not questionnaire_data.get('questionnaire_response'):
|
||||||
@@ -81,7 +86,7 @@ def get_roles_questionnaire():
|
|||||||
|
|
||||||
generator = SopPersonalAssessment()
|
generator = SopPersonalAssessment()
|
||||||
|
|
||||||
roles = generator.generate_roles_from_questionnaire(questionnaire_data)
|
roles = generator.generate_roles_from_questionnaire(questionnaire_data,role_slug)
|
||||||
|
|
||||||
if not roles:
|
if not roles:
|
||||||
return jsonify({"error": "No roles found", "message": "No roles were extracted from the questionnaire."}), 404
|
return jsonify({"error": "No roles found", "message": "No roles were extracted from the questionnaire."}), 404
|
||||||
@@ -89,8 +94,6 @@ def get_roles_questionnaire():
|
|||||||
return jsonify({"roles": roles, "message": "Roles successfully extracted from the questionnaire."}), 200
|
return jsonify({"roles": roles, "message": "Roles successfully extracted from the questionnaire."}), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@sops_bp.route('/personal_assessment/generate_sops_from_doc', methods=['POST'])
|
@sops_bp.route('/personal_assessment/generate_sops_from_doc', methods=['POST'])
|
||||||
@auth_check
|
@auth_check
|
||||||
def generate_sops():
|
def generate_sops():
|
||||||
@@ -226,6 +229,62 @@ def generate_sops_by_roles_and_areas():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@sops_bp.route('/general/generate_sops_pdf', methods=['POST'])
|
||||||
|
@auth_check
|
||||||
|
def generate_sops_pdf():
|
||||||
|
"""
|
||||||
|
Generate a PDF file of SOPs based on the SOP JSON data provided in the request body.
|
||||||
|
Returns the PDF as a downloadable file and then deletes the temporary file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get the SOP JSON data from the request body
|
||||||
|
sop_data = request.json
|
||||||
|
|
||||||
|
# Validate the presence of SOP data
|
||||||
|
if not sop_data or not isinstance(sop_data, dict) or 'sop_details' not in sop_data:
|
||||||
|
return make_response(jsonify({
|
||||||
|
"error": "Invalid input",
|
||||||
|
"message": "The request body should contain valid SOP data with a 'sop_details' field."
|
||||||
|
}), 400)
|
||||||
|
|
||||||
|
# Create a unique temporary filename for the PDF
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
pdf_filename = f"sop_document_{timestamp}.pdf"
|
||||||
|
pdf_path = os.path.join(temp_dir, pdf_filename)
|
||||||
|
|
||||||
|
# Import the PDF conversion function
|
||||||
|
from ...utils.sop_pdf_creator import convert_sop_to_pdf
|
||||||
|
|
||||||
|
# Generate the PDF file
|
||||||
|
convert_sop_to_pdf(sop_data, pdf_path, "")
|
||||||
|
|
||||||
|
# Send the file as a download attachment
|
||||||
|
@after_this_request
|
||||||
|
def remove_file(response):
|
||||||
|
try:
|
||||||
|
# Delete the temporary file after the response is sent
|
||||||
|
if os.path.exists(pdf_path):
|
||||||
|
os.remove(pdf_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error removing temporary PDF file: {str(e)}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Return the file as a downloadable attachment
|
||||||
|
return send_file(
|
||||||
|
pdf_path,
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=f"SOP_Document_{timestamp}.pdf",
|
||||||
|
mimetype='application/pdf'
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating PDF: {str(e)}")
|
||||||
|
return make_response(jsonify({
|
||||||
|
"error": "Processing error",
|
||||||
|
"message": f"An error occurred while generating the SOP PDF: {str(e)}"
|
||||||
|
}), 500)
|
||||||
|
|
||||||
|
|
||||||
@sops_bp.route('/executive/generate_sop_mission_from_vision', methods=['POST'])
|
@sops_bp.route('/executive/generate_sop_mission_from_vision', methods=['POST'])
|
||||||
@auth_check
|
@auth_check
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class Categories(BaseModel):
|
|||||||
|
|
||||||
class RoleSops(BaseModel):
|
class RoleSops(BaseModel):
|
||||||
role:str
|
role:str
|
||||||
|
narrative:str
|
||||||
sops:Categories
|
sops:Categories
|
||||||
|
|
||||||
class RoleSopssLists(BaseModel):
|
class RoleSopssLists(BaseModel):
|
||||||
|
|||||||
+28
-11
@@ -15,19 +15,35 @@ def get_sop_extraction_from_doc():
|
|||||||
|
|
||||||
|
|
||||||
def get_roles_extraction_from_questionnaire():
|
def get_roles_extraction_from_questionnaire():
|
||||||
return '''Your task is to extract the "Roles" from the provided questionnaire responses.
|
return '''
|
||||||
You must identify and categorize the roles based on the information provided.
|
|
||||||
|
You are a specialized role extractor for company documents. Your task is to identify and extract job roles/positions mentioned in the provided Questionairre data.
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
1. Extract ALL job roles/positions mentioned in the in questionairre response data as a list.
|
||||||
|
2. Filter the extracted roles based on the provided role_slug that will be provided.
|
||||||
|
3. Return the filtered roles as a JSON list.
|
||||||
|
|
||||||
|
RULES:
|
||||||
|
- Return an empty list if no matching roles are found.
|
||||||
|
- The role_slug is a keyword or category used to filter relevant roles.
|
||||||
|
- Only include roles that semantically relate to the role_slug.
|
||||||
|
- Be precise in extracting official job titles rather than general descriptions.
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
|
||||||
|
Example 1:
|
||||||
|
Text: "Our company is looking to hire a Senior Data Scientist, Junior Data Analyst, and Database Administrator for the Analytics department. We also have openings for Financial Manager and Customer Support Manager."
|
||||||
|
Role_slug: "data"
|
||||||
|
Expected output: ["Senior Data Scientist", "Junior Data Analyst", "Database Administrator"]
|
||||||
|
|
||||||
|
Example 2:
|
||||||
|
Text: "The restructuring process will affect several departments including the Financial Analysis team, Customer Relations department, and Sales Management. We are currently seeking a Regional Sales Manager, Sales Team Supervisor, and Customer Support Manager."
|
||||||
|
Role_slug: "manager"
|
||||||
|
Expected output: ["Financial Manager", "Regional Sales Manager", "Customer Support Manager"]
|
||||||
|
|
||||||
|
|
||||||
Instructions:
|
'''
|
||||||
1. **Roles**: Extract the roles mentioned in the questionnaire.
|
|
||||||
2. **Vision**: If applicable, extract the vision of the company or organization as it relates to the roles.
|
|
||||||
3. **Mission**: If applicable, extract the mission of the company or organization as it relates to the roles.
|
|
||||||
4. **Role-specific SOPs**:
|
|
||||||
- Identify any role-specific Standard Operating Procedures (SOPs) mentioned in the questionnaire.
|
|
||||||
- If SOPs for the role are not explicitly stated, infer them from the context, but only if there is clear evidence within the questionnaire. Do not generate or assume SOPs that are not directly supported by the information provided.
|
|
||||||
- If no roles or SOPs are found, return an empty list for each category.
|
|
||||||
Provide the extracted roles and any relevant sections exactly as they appear in the questionnaire.'''
|
|
||||||
|
|
||||||
|
|
||||||
def get_sop_personalassessment_from_questionnaire():
|
def get_sop_personalassessment_from_questionnaire():
|
||||||
@@ -89,6 +105,7 @@ def get_sop_personalassessment_from_area_rolev2():
|
|||||||
NOTE: IF AREAS ARE NOT PROVIDED (AREA IS "NOT PROVIDED"), INTUITIVELY PROVIDE THE SOP BASED ON THE ROLE NAME.
|
NOTE: IF AREAS ARE NOT PROVIDED (AREA IS "NOT PROVIDED"), INTUITIVELY PROVIDE THE SOP BASED ON THE ROLE NAME.
|
||||||
NOTE: MAKE SURE SOPS ARE NOT MISSING FOR THE PROVIDED TYPES.
|
NOTE: MAKE SURE SOPS ARE NOT MISSING FOR THE PROVIDED TYPES.
|
||||||
NOTE: FOR SOP TYPES NOT SELECTED RETURN AN EMPTY LIST and not "null" E.G IF "SHALL" AND "WILL" ARE SELECTED BUT "MUST" IS NOT AMONG, MUST WILL BE AN EMPTY LIST
|
NOTE: FOR SOP TYPES NOT SELECTED RETURN AN EMPTY LIST and not "null" E.G IF "SHALL" AND "WILL" ARE SELECTED BUT "MUST" IS NOT AMONG, MUST WILL BE AN EMPTY LIST
|
||||||
|
also for each role , add a "narrative" which is short description of the role in question
|
||||||
|
|
||||||
NOTE !!!: IF A ROLE POINTS TO A SPECIFIC SOP TYPE (E.G., "SHALL" AND "MUST"), THESE TWO MUST NEVER BE EMPTY FOR THAT ROLE.
|
NOTE !!!: IF A ROLE POINTS TO A SPECIFIC SOP TYPE (E.G., "SHALL" AND "MUST"), THESE TWO MUST NEVER BE EMPTY FOR THAT ROLE.
|
||||||
: FORMAT: SOPS SHOULD BE CLEAR, DIRECT, AND CONCISE. EACH ROLE SHOULD HAVE 5-7 BULLET POINTS PER SOP TYPE ("WILL," "SHALL," OR "MUST"). FOR COMPLEX ROLES, EACH SOP TYPE MAY HAVE A MAXIMUM OF 7-10 BULLET POINTS, NOT TOTAL ACROSS ALL TYPES, BUT PER SOP TYPE.
|
: FORMAT: SOPS SHOULD BE CLEAR, DIRECT, AND CONCISE. EACH ROLE SHOULD HAVE 5-7 BULLET POINTS PER SOP TYPE ("WILL," "SHALL," OR "MUST"). FOR COMPLEX ROLES, EACH SOP TYPE MAY HAVE A MAXIMUM OF 7-10 BULLET POINTS, NOT TOTAL ACROSS ALL TYPES, BUT PER SOP TYPE.
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class SopPersonalAssessment:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def generate_roles_from_questionnaire(self, questionnaire_data: List[dict]) -> Roles_response:
|
def generate_roles_from_questionnaire(self, questionnaire_data: List[dict],role_slug:str) -> Roles_response:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# List of areas: ["communication", "development", etc.]
|
# List of areas: ["communication", "development", etc.]
|
||||||
@@ -139,10 +139,14 @@ class SopPersonalAssessment:
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": f'''Questionairre data : {questionnaire_data}''',
|
"content": f'''Questionairre data : {questionnaire_data}''',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": f'''Role slug to consider : {role_slug}''',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
response_format=Roles_response,
|
response_format=Roles_response,
|
||||||
max_tokens=16000,
|
max_tokens=4096,
|
||||||
temperature=0.1
|
temperature=0.1
|
||||||
)
|
)
|
||||||
extracted_text = json.loads(response.choices[0].message.content)
|
extracted_text = json.loads(response.choices[0].message.content)
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
|
# 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, [])
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
# Build the PDF document with header and footer
|
||||||
|
doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)
|
||||||
|
|
||||||
|
return output_pdf
|
||||||
|
|
||||||
|
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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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__":
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -1,140 +1,244 @@
|
|||||||
import requests
|
import json
|
||||||
import pandas as pd
|
import datetime
|
||||||
from dotenv import load_dotenv
|
from reportlab.lib.pagesizes import letter
|
||||||
load_dotenv()
|
from reportlab.lib import colors
|
||||||
import os
|
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")
|
def header_footer(canvas, doc):
|
||||||
# Constants for API requests
|
"""Add the header and footer to each page"""
|
||||||
URL = "https://erpai.mkdlabs.com//v3/api/custom/erpai/common/get-data-ai"
|
canvas.saveState()
|
||||||
HEADERS = {
|
|
||||||
"x-project": DATA_KEY # Replace with your actual key
|
# Header
|
||||||
}
|
header_text = "Standard Operating Procedures"
|
||||||
|
canvas.setFont('Helvetica-Bold', 10)
|
||||||
# JSON bodies for API requests
|
canvas.drawString(72, letter[1] - 40, header_text)
|
||||||
def create_json_body(area_type, company_id):
|
|
||||||
return {
|
# Add a line below the header
|
||||||
"type": area_type,
|
canvas.setStrokeColor(colors.lightgrey)
|
||||||
"options": {
|
canvas.line(72, letter[1] - 50, letter[0] - 72, letter[1] - 50)
|
||||||
"company_id": company_id
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
|
# 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, [])
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
# Build the PDF document with header and footer
|
||||||
|
doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)
|
||||||
|
|
||||||
|
return output_pdf
|
||||||
|
|
||||||
|
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."
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
# Function to fetch data from the API
|
# You can replace the company name with your own
|
||||||
def fetch_data(json_body):
|
output_file = convert_sop_to_pdf(sop_json, company_name="Strategic Business Solutions, Inc.")
|
||||||
json_body["options"]["company_id"] = json_body["options"].get("company_id") # Ensure company_id is included
|
print(f"PDF successfully generated: {output_file}")
|
||||||
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"]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
area_summary = df['area'].value_counts()
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from src.services.chatbot import Chatbot
|
main()
|
||||||
bot = Chatbot()
|
|
||||||
res = bot.predict_next_n_assessment(companyid=12,N=3)
|
|
||||||
|
|
||||||
print(res)
|
|
||||||
Reference in New Issue
Block a user