MilanM commited on
Commit
ffcff41
·
verified ·
1 Parent(s): 7fcc82d

Upload 2 files

Browse files
Files changed (2) hide show
  1. pdf_generator.py +218 -0
  2. requirements.txt +7 -0
pdf_generator.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import re
3
+ from reportlab.lib import colors
4
+ from reportlab.lib.pagesizes import A4
5
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
6
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Flowable
7
+ from reportlab.lib.units import mm, inch
8
+ from reportlab.graphics.shapes import Drawing, Rect, String, Line
9
+
10
+ class SliderFlowable(Flowable):
11
+ def __init__(self, name, value, min_val, max_val, is_percentage=False):
12
+ Flowable.__init__(self)
13
+ self.name = name
14
+ self.value = value
15
+ self.min_val = min_val
16
+ self.max_val = max_val
17
+ self.is_percentage = is_percentage
18
+ self.width = 400
19
+ self.height = 80
20
+
21
+ def draw(self):
22
+ drawing = Drawing(self.width, self.height)
23
+
24
+ # Draw slider bar
25
+ bar = Rect(50, 30, 300, 20, fillColor=colors.HexColor("#f7fbfd"), strokeColor=colors.HexColor("#9999ff"))
26
+ drawing.add(bar)
27
+
28
+ # Draw slider value
29
+ if self.max_val == self.min_val:
30
+ value_width = 50 # or some default width
31
+ else:
32
+ value_width = 50 + ((self.value - self.min_val) / (self.max_val - self.min_val) * 300)
33
+ value_bar = Rect(50, 30, value_width - 50, 20, fillColor=colors.HexColor("#9999ff"), strokeColor=None)
34
+ drawing.add(value_bar)
35
+
36
+ # Add slider name
37
+ drawing.add(String(0, 60, self.name, fontSize=12, fillColor=colors.HexColor("#26004d")))
38
+
39
+ # Add range labels
40
+ min_str = f"{self.min_val:.1f}%" if self.is_percentage else f"{self.min_val:.1f}"
41
+ max_str = f"{self.max_val:.1f}%" if self.is_percentage else f"{self.max_val:.1f}"
42
+ drawing.add(String(40, 10, min_str, fontSize=10, fillColor=colors.HexColor("#26004d")))
43
+ drawing.add(String(340, 10, max_str, fontSize=10, fillColor=colors.HexColor("#26004d")))
44
+
45
+ # Add value label
46
+ value_str = f"{self.value:.1f}%" if self.is_percentage else f"{self.value:.1f}"
47
+ drawing.add(String(value_width - 20, 55, value_str, fontSize=10, fillColor=colors.HexColor("#26004d")))
48
+
49
+ # Add value marker
50
+ drawing.add(Line(value_width, 25, value_width, 55, strokeColor=colors.HexColor("#26004d"), strokeWidth=2))
51
+
52
+ drawing.drawOn(self.canv, 0, 0)
53
+
54
+ def create_styles():
55
+ styles = getSampleStyleSheet()
56
+ styles['Title'].fontName = 'Helvetica-Bold'
57
+ styles['Title'].fontSize = 18
58
+ styles['Title'].spaceAfter = 16
59
+ styles['Title'].textColor = colors.HexColor("#26004d")
60
+
61
+ styles['Heading1'].fontName = 'Helvetica-Bold'
62
+ styles['Heading1'].fontSize = 16
63
+ styles['Heading1'].spaceAfter = 10
64
+ styles['Heading1'].textColor = colors.HexColor("#3b0b75")
65
+
66
+ styles['Heading2'].fontName = 'Helvetica'
67
+ styles['Heading2'].fontSize = 14
68
+ styles['Heading2'].spaceAfter = 12
69
+ styles['Heading2'].textColor = colors.HexColor("#52176a")
70
+
71
+ styles['BodyText'].fontName = 'Helvetica'
72
+ styles['BodyText'].fontSize = 12
73
+ styles['BodyText'].spaceAfter = 12
74
+ styles['BodyText'].textColor = colors.HexColor("#26004d")
75
+
76
+ styles.add(ParagraphStyle(
77
+ name='Answer',
78
+ parent=styles['BodyText'],
79
+ backColor=colors.HexColor("#f0f2fd"),
80
+ borderColor=colors.HexColor("#9999ff"),
81
+ borderWidth=0.5,
82
+ borderPadding=(5, 5, 5, 5),
83
+ spaceAfter=10
84
+ ))
85
+
86
+ return styles
87
+
88
+ def create_page_template(canvas, doc):
89
+ canvas.saveState()
90
+ canvas.setFillColor(colors.HexColor("#e6ebfb"))
91
+ canvas.rect(0, 0, doc.pagesize[0], doc.pagesize[1], fill=1)
92
+ canvas.setFillColor(colors.HexColor("#26004d"))
93
+ canvas.setFont('Helvetica', 9)
94
+ canvas.drawString(30, 20, f"Page {doc.page}")
95
+ canvas.restoreState()
96
+
97
+ def generate_pdf(session_state):
98
+ buffer = io.BytesIO()
99
+ doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=20*mm, leftMargin=20*mm, topMargin=20*mm, bottomMargin=20*mm)
100
+ styles = create_styles()
101
+ story = [Paragraph("Experimental Validation Method Plan", styles['Title'])]
102
+
103
+ for page in session_state.pages[:-1]: # Skip the last page
104
+ if 'input_key' in page and page['input_key'] is not None:
105
+ story.append(Paragraph(page['title'], styles['Heading1']))
106
+
107
+ if page['input_key'] == 'idea_testing':
108
+ story.extend(process_idea_testing(session_state, styles))
109
+ elif page['input_key'] == 'capability_testing':
110
+ story.extend(process_capability_testing(session_state, styles))
111
+ elif page['input_key'] == 'approach_comparison':
112
+ story.extend(process_approach_comparison(session_state, styles))
113
+ elif page['input_key'] == 'mock_application':
114
+ story.extend(process_mock_application(session_state, styles))
115
+
116
+ story.append(Spacer(1, 12))
117
+
118
+ doc.build(story, onFirstPage=create_page_template, onLaterPages=create_page_template)
119
+ buffer.seek(0)
120
+ return buffer
121
+
122
+ def process_idea_testing(session_state, styles):
123
+ story = []
124
+ answers = session_state.answers['idea_testing']
125
+
126
+ story.append(Paragraph("Rapid Prototyping", styles['Heading2']))
127
+ for field in ['input', 'process', 'output']:
128
+ story.append(Paragraph(f"{field.capitalize()}:", styles['Heading2']))
129
+ story.append(Paragraph(answers['rapid_prototyping'].get(field, ""), styles['Answer']))
130
+
131
+ story.append(Paragraph("Idea Testing", styles['Heading2']))
132
+ story.append(Paragraph("Framework for idea validation:", styles['Heading2']))
133
+ story.append(Paragraph(answers.get('framework', ""), styles['Answer']))
134
+ story.append(Paragraph("Useful libraries, tools, or assets:", styles['Heading2']))
135
+ story.append(Paragraph(answers.get('tools', ""), styles['Answer']))
136
+
137
+ return story
138
+
139
+ def process_capability_testing(session_state, styles):
140
+ story = []
141
+ answers = session_state.answers['capability_testing']
142
+
143
+ story.append(Paragraph("Capability Testing", styles['Heading2']))
144
+ for field in ['capability', 'assessment_method', 'success_definition']:
145
+ story.append(Paragraph(f"{field.replace('_', ' ').capitalize()}:", styles['Heading2']))
146
+ story.append(Paragraph(answers.get(field, ""), styles['Answer']))
147
+
148
+ story.append(Paragraph("Validation Criteria", styles['Heading2']))
149
+ story.extend(process_validation_criteria(answers['validation_criteria'], styles))
150
+
151
+ return story
152
+
153
+ def process_validation_criteria(session_state, styles):
154
+ story = []
155
+
156
+ # Qualitative Criteria
157
+ story.append(Paragraph("Qualitative Criteria:", styles['Heading2']))
158
+ for i, criterion in enumerate(session_state.get('qualitative', [])):
159
+ description = session_state.get(f'qual_desc_{i}', '')
160
+ story.append(Paragraph(f"<b>{criterion}</b>: <font face='Helvetica' size=11 color='#350863'>{description}</font>", styles['Answer']))
161
+
162
+ # Quantitative Criteria
163
+ story.append(Paragraph("Quantitative Criteria:", styles['Heading2']))
164
+ for i, criterion in enumerate(session_state.get('quantitative', [])):
165
+ parsed = parse_quantitative_criteria(criterion)
166
+ if parsed:
167
+ name, min_val, max_val, is_percentage, is_integer = parsed
168
+ value = session_state.get(f'quant_value_{i}', min_val)
169
+
170
+ if is_percentage:
171
+ slider = SliderFlowable(name, value*100, min_val*100, max_val*100, is_percentage=True)
172
+ else:
173
+ slider = SliderFlowable(name, value, min_val, max_val, is_percentage=False)
174
+
175
+ story.append(slider)
176
+ story.append(Paragraph(f"{name}: {value:.2f}", styles['Answer']))
177
+
178
+ return story
179
+
180
+ def process_approach_comparison(session_state, styles):
181
+ story = []
182
+ answers = session_state.answers['approach_comparison']
183
+
184
+ for field in ['standardization', 'experiment_overview']:
185
+ story.append(Paragraph(f"{field.replace('_', ' ').capitalize()}:", styles['Heading2']))
186
+ story.append(Paragraph(answers.get(field, ""), styles['Answer']))
187
+
188
+ return story
189
+
190
+ def process_mock_application(session_state, styles):
191
+ story = []
192
+ answers = session_state.answers['mock_application']
193
+
194
+ for field in ['user_testing', 'insights', 'presentation']:
195
+ story.append(Paragraph(f"{field.replace('_', ' ').capitalize()}:", styles['Heading2']))
196
+ story.append(Paragraph(answers.get(field, ""), styles['Answer']))
197
+
198
+ return story
199
+
200
+ def parse_quantitative_criteria(input_string):
201
+ match = re.match(r'(.+)\[([-+]?(?:\d*\.*\d+)(?:%)?)\s*-\s*([-+]?(?:\d*\.*\d+)(?:%)?)?\]', input_string)
202
+ if match:
203
+ name, min_val, max_val = match.groups()
204
+ name = name.strip()
205
+
206
+ # Handle percentage inputs
207
+ is_percentage = '%' in min_val or '%' in max_val
208
+ min_val = float(min_val.rstrip('%'))
209
+ max_val = float(max_val.rstrip('%'))
210
+
211
+ if is_percentage:
212
+ min_val /= 100
213
+ max_val /= 100
214
+
215
+ is_integer = '.' not in input_string or (min_val.is_integer() and max_val.is_integer())
216
+
217
+ return name, min_val, max_val, is_percentage, is_integer
218
+ return None
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ matplotlib
4
+ base64
5
+ streamlit-tags
6
+ streamlit-vertical-slider
7
+ reportlab