77from datetime import datetime
88from pathlib import Path
99
10+ # Context definitions for widgets
11+ WIDGET_CONTEXT = {
12+ "DynamoDB" : "Shows the consumed write capacity units for the DynamoDB table. High usage may indicate need for scaling." ,
13+ "Lambda" : "Number of times the Lambda function was invoked. Spikes may indicate increased traffic or retries." ,
14+ "5xx" : "Server-side errors. Should be zero ideally." ,
15+ "4xx" : "Client-side errors. Frequent 4xx errors might indicate issues with client requests." ,
16+ "Latency" : "Response time of the service. Lower is better." ,
17+ "CPU" : "CPU utilization percentage. Consistently high CPU might require instance upsizing." ,
18+ "Memory" : "Memory usage. Ensure there is sufficient headroom." ,
19+ "Errors" : "Count of error events." ,
20+ "Throttles" : "Number of throttled requests. Indicates capacity limits are being hit."
21+ }
22+
23+ def get_widget_description (title ):
24+ """
25+ Get a description for a widget based on keywords in its title.
26+ """
27+ title_lower = title .lower ()
28+ description_parts = []
29+
30+ for key , desc in WIDGET_CONTEXT .items ():
31+ if key .lower () in title_lower :
32+ description_parts .append (desc )
33+
34+ if not description_parts :
35+ return "Performance metric visualization."
36+
37+ return " " .join (description_parts )
38+
1039def generate_html_report (images_dir = 'dashboard_exports' , output_file = None ):
1140 """
1241 Generate an HTML report with all dashboard widget images.
@@ -35,127 +64,156 @@ def generate_html_report(images_dir='dashboard_exports', output_file=None):
3564
3665 # Get dashboard name and timestamp from definition file
3766 dashboard_name = "Monthly Demand And Capacity Report - EliD"
38- report_date = datetime .now ().strftime ('%Y-%m-%d %H:%M:%S ' )
67+ report_date = datetime .now ().strftime ('%d %B %Y at %H:%M' )
3968
4069 # Build HTML
4170 html_content = f"""<!DOCTYPE html>
4271<html lang="en">
4372<head>
4473 <meta charset="UTF-8">
4574 <meta name="viewport" content="width=device-width, initial-scale=1.0">
46- <title>Dashboard Report - { dashboard_name } </title>
75+ <title>NHS Dashboard Report - { dashboard_name } </title>
4776 <style>
77+ :root {{
78+ --nhs-blue: #005EB8;
79+ --nhs-white: #FFFFFF;
80+ --nhs-black: #231f20;
81+ --nhs-dark-grey: #425563;
82+ --nhs-mid-grey: #768692;
83+ --nhs-pale-grey: #E8EDEE;
84+ --nhs-warm-yellow: #FFB81C;
85+ }}
86+
4887 * {{
4988 margin: 0;
5089 padding: 0;
5190 box-sizing: border-box;
5291 }}
5392
5493 body {{
55- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
56- background: #f5f5f5;
57- padding: 20px;
58- color: #333;
94+ font-family: "Frutiger W01", Arial, sans-serif;
95+ background: var(--nhs-pale-grey);
96+ color: var(--nhs-black);
97+ line-height: 1.5;
98+ }}
99+
100+ .nhs-header {{
101+ background-color: var(--nhs-blue);
102+ color: var(--nhs-white);
103+ padding: 24px 0;
104+ margin-bottom: 32px;
59105 }}
60106
61- .container {{
62- max-width: 1400px ;
107+ .nhs- container {{
108+ max-width: 960px ;
63109 margin: 0 auto;
64- background: white;
65- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
110+ padding: 0 16px;
66111 }}
67112
68- .header {{
69- background: linear-gradient(135deg, #232F3E 0%, #FF9900 100%);
70- color: white;
71- padding: 30px 40px;
72- border-bottom: 4px solid #FF9900;
113+ .nhs-logo {{
114+ font-weight: 700;
115+ font-size: 24px;
116+ letter-spacing: -0.5px;
117+ display: inline-block;
118+ margin-right: 16px;
119+ padding-right: 16px;
120+ border-right: 1px solid rgba(255, 255, 255, 0.3);
73121 }}
74122
75- .header h1 {{
76- font-size: 32px ;
123+ .report-title {{
124+ font-size: 24px ;
77125 font-weight: 600;
78- margin-bottom: 10px ;
126+ display: inline-block ;
79127 }}
80128
81- .header .subtitle {{
82- font-size: 16px;
129+ .report-meta {{
130+ margin-top: 8px;
131+ font-size: 14px;
83132 opacity: 0.9;
84133 }}
85134
86135 .content {{
87- padding: 40px ;
136+ padding-bottom: 48px ;
88137 }}
89138
90- .widget {{
91- margin-bottom: 40px;
139+ .widget-card {{
140+ background: var(--nhs-white);
141+ border: 1px solid #d8dde0;
142+ border-bottom: 4px solid var(--nhs-blue);
143+ margin-bottom: 32px;
144+ padding: 24px;
92145 page-break-inside: avoid;
93146 }}
94147
148+ .widget-header {{
149+ margin-bottom: 16px;
150+ border-bottom: 1px solid var(--nhs-pale-grey);
151+ padding-bottom: 16px;
152+ }}
153+
95154 .widget-title {{
96- font-size: 20px ;
155+ font-size: 19px ;
97156 font-weight: 600;
98- color: #232F3E;
99- margin-bottom: 15px;
100- padding-bottom: 10px;
101- border-bottom: 2px solid #FF9900;
157+ color: var(--nhs-black);
158+ margin-bottom: 8px;
159+ }}
160+
161+ .widget-description {{
162+ font-size: 16px;
163+ color: var(--nhs-dark-grey);
164+ background: #f0f4f5;
165+ padding: 12px;
166+ border-left: 4px solid var(--nhs-mid-grey);
167+ }}
168+
169+ .widget-image-container {{
170+ margin-top: 20px;
171+ text-align: center;
102172 }}
103173
104174 .widget-image {{
105- width: 100%;
175+ max- width: 100%;
106176 height: auto;
107- border: 1px solid #ddd;
108- border-radius: 4px;
109- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
177+ border: 1px solid var(--nhs-pale-grey);
110178 }}
111179
112180 .footer {{
113- background: #f8f8f8;
114- padding: 20px 40px;
115181 text-align: center;
116- color: #666;
182+ padding: 32px 0;
183+ color: var(--nhs-mid-grey);
117184 font-size: 14px;
118- border-top: 1px solid #ddd;
185+ border-top: 1px solid #d8dde0;
186+ margin-top: 48px;
119187 }}
120188
121189 @media print {{
122190 body {{
123191 background: white;
124- padding: 0;
125- }}
126-
127- .container {{
128- box-shadow: none;
129- }}
130-
131- .widget {{
132- page-break-inside: avoid;
133192 }}
134- }}
135-
136- @media (max-width: 768px) {{
137- body {{
138- padding: 10px;
139- }}
140-
141- .content {{
142- padding: 20px;
193+ .nhs-header {{
194+ background: white;
195+ color: black;
196+ border-bottom: 2px solid var(--nhs-blue);
143197 }}
144-
145- .header {{
146- padding: 20px ;
198+ .widget-card {{
199+ border: none;
200+ border-bottom: 1px solid #ccc ;
147201 }}
148202 }}
149203 </style>
150204</head>
151205<body>
152- <div class="container">
153- <div class="header">
154- <h1>📊 { dashboard_name } </h1>
155- <div class="subtitle">Last 8 Weeks Report • Generated: { report_date } </div>
206+ <header class="nhs-header">
207+ <div class="nhs-container">
208+ <div class="header-content">
209+ <span class="nhs-logo">NHS</span>
210+ <h1 class="report-title">{ dashboard_name } </h1>
211+ <div class="report-meta">Generated on { report_date } </div>
212+ </div>
156213 </div>
214+ </header>
157215
158- <div class="content">
216+ <div class="nhs-container content">
159217"""
160218
161219 # Add each widget image
@@ -167,27 +225,35 @@ def generate_html_report(images_dir='dashboard_exports', output_file=None):
167225 # Replace underscores with spaces
168226 title = title .replace ('_' , ' ' )
169227
228+ description = get_widget_description (title )
229+
170230 # Read and encode image
171231 with open (image_file , 'rb' ) as f :
172232 image_data = base64 .b64encode (f .read ()).decode ('utf-8' )
173233
174234 html_content += f"""
175- <div class="widget">
235+ <div class="widget-card">
236+ <div class="widget-header">
176237 <div class="widget-title">{ idx } . { title } </div>
238+ <div class="widget-description">{ description } </div>
239+ </div>
240+ <div class="widget-image-container">
177241 <img class="widget-image"
178242 src="data:image/png;base64,{ image_data } "
179243 alt="{ title } ">
180244 </div>
245+ </div>
181246"""
182247
183248 # Close HTML
184249 html_content += """
185- </div>
250+ </div>
186251
187- <div class="footer">
188- Generated from AWS CloudWatch Dashboard
252+ <footer class="footer">
253+ <div class="nhs-container">
254+ <p>Eligibility Data Product • Generated from AWS CloudWatch</p>
189255 </div>
190- </div >
256+ </footer >
191257</body>
192258</html>
193259"""
@@ -196,11 +262,10 @@ def generate_html_report(images_dir='dashboard_exports', output_file=None):
196262 with open (output_file , 'w' , encoding = 'utf-8' ) as f :
197263 f .write (html_content )
198264
199- print (f"\n ✓ Report generated: { output_file } " )
265+ print (f"\n ✓ Capacity and Demand Report generated: { output_file } " )
200266 print (f"\n To view:" )
201267 print (f" - Open in browser: file://{ Path (output_file ).absolute ()} " )
202268 print (f" - Or run: xdg-open { output_file } " )
203- print (f"\n To save as PDF: Open in browser → Print → Save as PDF" )
204269
205270 return output_file
206271
0 commit comments