import io import re from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Flowable from reportlab.lib.units import mm, inch from reportlab.graphics.shapes import Drawing, Rect, String, Line class SliderFlowable(Flowable): def __init__(self, name, value, min_val, max_val, is_percentage=False): Flowable.__init__(self) self.name = name self.value = value self.min_val = min_val self.max_val = max_val self.is_percentage = is_percentage self.width = 400 self.height = 80 def draw(self): drawing = Drawing(self.width, self.height) # Draw slider bar bar = Rect(50, 30, 300, 20, fillColor=colors.HexColor("#f7fbfd"), strokeColor=colors.HexColor("#9999ff")) drawing.add(bar) # Draw slider value if self.max_val == self.min_val: value_width = 50 # or some default width else: value_width = 50 + ((self.value - self.min_val) / (self.max_val - self.min_val) * 300) value_bar = Rect(50, 30, value_width - 50, 20, fillColor=colors.HexColor("#9999ff"), strokeColor=None) drawing.add(value_bar) # Add slider name drawing.add(String(0, 60, self.name, fontSize=12, fillColor=colors.HexColor("#26004d"))) # Add range labels min_str = f"{self.min_val:.1f}%" if self.is_percentage else f"{self.min_val:.1f}" max_str = f"{self.max_val:.1f}%" if self.is_percentage else f"{self.max_val:.1f}" drawing.add(String(40, 10, min_str, fontSize=10, fillColor=colors.HexColor("#26004d"))) drawing.add(String(340, 10, max_str, fontSize=10, fillColor=colors.HexColor("#26004d"))) # Add value label value_str = f"{self.value:.1f}%" if self.is_percentage else f"{self.value:.1f}" drawing.add(String(value_width - 20, 55, value_str, fontSize=10, fillColor=colors.HexColor("#26004d"))) # Add value marker drawing.add(Line(value_width, 25, value_width, 55, strokeColor=colors.HexColor("#26004d"), strokeWidth=2)) drawing.drawOn(self.canv, 0, 0) def create_styles(): styles = getSampleStyleSheet() styles['Title'].fontName = 'Helvetica-Bold' styles['Title'].fontSize = 18 styles['Title'].spaceAfter = 16 styles['Title'].textColor = colors.HexColor("#26004d") styles['Heading1'].fontName = 'Helvetica-Bold' styles['Heading1'].fontSize = 16 styles['Heading1'].spaceAfter = 10 styles['Heading1'].textColor = colors.HexColor("#3b0b75") styles['Heading2'].fontName = 'Helvetica' styles['Heading2'].fontSize = 14 styles['Heading2'].spaceAfter = 12 styles['Heading2'].textColor = colors.HexColor("#52176a") styles['BodyText'].fontName = 'Helvetica' styles['BodyText'].fontSize = 12 styles['BodyText'].spaceAfter = 12 styles['BodyText'].textColor = colors.HexColor("#26004d") styles.add(ParagraphStyle( name='Answer', parent=styles['BodyText'], backColor=colors.HexColor("#f0f2fd"), borderColor=colors.HexColor("#9999ff"), borderWidth=0.5, borderPadding=(5, 5, 5, 5), spaceAfter=10 )) return styles def create_page_template(canvas, doc): canvas.saveState() canvas.setFillColor(colors.HexColor("#e6ebfb")) canvas.rect(0, 0, doc.pagesize[0], doc.pagesize[1], fill=1) canvas.setFillColor(colors.HexColor("#26004d")) canvas.setFont('Helvetica', 9) canvas.drawString(30, 20, f"Page {doc.page}") canvas.restoreState() def generate_pdf(session_state): buffer = io.BytesIO() doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=20*mm, leftMargin=20*mm, topMargin=20*mm, bottomMargin=20*mm) styles = create_styles() story = [Paragraph("Pilot Drafting", styles['Title'])] for page in session_state.pages[:-1]: # Skip the last page if 'input_key' in page and page['input_key'] is not None: story.append(Paragraph(page['title'], styles['Heading1'])) if page['input_key'] == 'workload_scope': story.extend(process_workload_scope(session_state, styles)) elif page['input_key'] == 'useful_assets': story.extend(process_useful_assets(session_state, styles)) elif page['input_key'] == 'pilot_evaluation': story.extend(process_pilot_evaluation(session_state, styles)) story.append(Spacer(1, 12)) doc.build(story, onFirstPage=create_page_template, onLaterPages=create_page_template) buffer.seek(0) return buffer def process_workload_scope(session_state, styles): story = [] answers = session_state.answers['workload_scope'] story.append(Paragraph("Feature Prioritization", styles['Heading2'])) for i, feature in enumerate(answers['feature_prioritization']): lines = feature.split('\n') for line in lines: story.append(Paragraph(f"Feature {i+1}: {line}", styles['Answer'])) # story.append(Paragraph("Preferred Start Period", styles['Heading2'])) # story.append(Paragraph(str(answers['preferred_start_period']), styles['Answer'])) story.append(Paragraph("Preferred Start Period", styles['Heading2'])) story.append(Paragraph(f"{answers['preferred_start_period'][0]} - {answers['preferred_start_period'][1]}", styles['Answer'])) story.append(Paragraph("Team Composition", styles['Heading2'])) for team_member in ['partner', 'ibm']: lines = answers['team_composition'][team_member].split('\n') for line in lines: story.append(Paragraph(f"{team_member.capitalize()}: {line}", styles['Answer'])) return story def process_useful_assets(session_state, styles): story = [] answers = session_state.answers['useful_assets'] story.append(Paragraph("Solution Elements", styles['Heading2'])) for element in answers['solution_elements']: story.append(Paragraph(element, styles['Answer'])) story.append(Paragraph("IBM Software", styles['Heading2'])) for software in answers['ibm_software']: story.append(Paragraph(software, styles['Answer'])) story.append(Paragraph("Open Source Supports", styles['Heading2'])) for support in answers['open_source_supports']: story.append(Paragraph(support, styles['Answer'])) return story def process_pilot_evaluation(session_state, styles): story = [] answers = session_state.answers['useful_assets'] story.extend(process_validation_criteria(answers, styles)) return story def process_validation_criteria(session_state, styles): story = [] # Qualitative Criteria story.append(Paragraph("Qualitative Criteria:", styles['Heading2'])) for i, criterion in enumerate(session_state.get('qualitative', [])): description = session_state.get(f'qual_desc_{i}', '') story.append(Paragraph(f"{criterion}: {description}", styles['Answer'])) # Quantitative Criteria story.append(Paragraph("Quantitative Criteria:", styles['Heading2'])) for i, criterion in enumerate(session_state.get('quantitative', [])): parsed = parse_quantitative_criteria(criterion) if parsed: name, min_val, max_val, is_percentage, is_integer = parsed value = session_state.get(f'quant_value_{i}', min_val) if is_percentage: slider = SliderFlowable(name, value*100, min_val*100, max_val*100, is_percentage=True) else: slider = SliderFlowable(name, value, min_val, max_val, is_percentage=False) story.append(slider) story.append(Paragraph(f"{name}: {value:.2f}", styles['Answer'])) return story def parse_quantitative_criteria(input_string): match = re.match(r'(.+)\[([-+]?(?:\d*\.*\d+)(?:%)?)\s*-\s*([-+]?(?:\d*\.*\d+)(?:%)?)?\]', input_string) if match: name, min_val, max_val = match.groups() name = name.strip() # Handle percentage inputs is_percentage = '%' in min_val or '%' in max_val min_val = float(min_val.rstrip('%')) max_val = float(max_val.rstrip('%')) if is_percentage: min_val /= 100 max_val /= 100 is_integer = '.' not in input_string or (min_val.is_integer() and max_val.is_integer()) return name, min_val, max_val, is_percentage, is_integer return None