7070WITNESS_TEST_CLASSES : list [str ] = ["DER-A" , "DER-G" , "DER-L" ] # Classes from section 14 of sa-ts-5573-2025
7171
7272CHART_MARGINS = dict (l = 80 , r = 20 , t = 40 , b = 80 )
73+ WARNING_BANNER_COLOR = HexColor (0xFFF3E0 ) # Light orange background
7374
7475
7576class ConditionalSpacer (Spacer ):
@@ -455,6 +456,29 @@ def generate_criteria_failure_table(check_results: dict[str, CheckResult], style
455456 return table
456457
457458
459+ def generate_set_max_w_warning_banner (stylesheet : StyleSheet ) -> list [Flowable ]:
460+ """Generate a warning banner indicating that setMaxW was varied during the test."""
461+ warning_style = TableStyle (
462+ [
463+ ("BACKGROUND" , (0 , 0 ), (- 1 , - 1 ), WARNING_BANNER_COLOR ),
464+ ("TEXTCOLOR" , (0 , 0 ), (- 1 , - 1 ), TEXT_COLOR ),
465+ ("ALIGN" , (0 , 0 ), (- 1 , - 1 ), "CENTER" ),
466+ ("TOPPADDING" , (0 , 0 ), (- 1 , - 1 ), 10 ),
467+ ("BOTTOMPADDING" , (0 , 0 ), (- 1 , - 1 ), 10 ),
468+ ("LEFTPADDING" , (0 , 0 ), (- 1 , - 1 ), 12 ),
469+ ("RIGHTPADDING" , (0 , 0 ), (- 1 , - 1 ), 12 ),
470+ ("BOX" , (0 , 0 ), (- 1 , - 1 ), 1 , HexColor (0xE65100 )),
471+ ]
472+ )
473+ warning_text = Paragraph (
474+ "<font color='#E65100'><b>! Warning:</b></font> setMaxW was varied during this test. "
475+ "This unexpected behaviour may affect test assumptions and DERControls."
476+ )
477+ table = Table ([[warning_text ]], colWidths = [stylesheet .table_width ])
478+ table .setStyle (warning_style )
479+ return [table , stylesheet .spacer ]
480+
481+
458482def generate_criteria_section (
459483 check_results : dict [str , CheckResult ], requires_witness_testing : bool , stylesheet : StyleSheet
460484) -> list [Flowable ]:
@@ -649,7 +673,7 @@ def generate_site_der_rating_table(site_der_rating: SiteDERRating, stylesheet: S
649673 non_null_attributes = get_non_null_attributes (site_der_rating , attributes_to_include )
650674 null_attributes_paragraph = make_null_attributes_paragraph (attributes_to_include , non_null_attributes )
651675 table_data = generate_der_table_data (site_der_rating , non_null_attributes )
652- table_data .insert (0 , ["DER Rating " , "Value" ])
676+ table_data .insert (0 , ["DER Capability " , "Value" ])
653677 column_widths = [int (fraction * stylesheet .table_width ) for fraction in [0.5 , 0.5 ]]
654678 table = Table (table_data , colWidths = column_widths )
655679 table .setStyle (stylesheet .table )
@@ -1112,11 +1136,68 @@ def generate_timeline_checklist(timeline: Timeline, runner_state: RunnerState) -
11121136 return fig_to_image (fig = fig , content_width = MAX_CONTENT_WIDTH )
11131137
11141138
1139+ def generate_step_completion_table (runner_state : RunnerState , stylesheet : StyleSheet ) -> list [Flowable ]:
1140+ """Generate a table summarising the completion status and timing of each test step."""
1141+ if not (runner_state .active_test_procedure and runner_state .active_test_procedure .step_status ):
1142+ return []
1143+
1144+ base_timestamp = runner_state .interaction_timestamp (interaction_type = ClientInteractionType .TEST_PROCEDURE_START )
1145+
1146+ table_style = TableStyle (
1147+ [
1148+ ("ALIGN" , (0 , 0 ), (- 1 , - 1 ), "LEFT" ),
1149+ ("TOPPADDING" , (0 , 0 ), (- 1 , - 1 ), 8 ),
1150+ ("BOTTOMPADDING" , (0 , 0 ), (- 1 , - 1 ), 8 ),
1151+ ("ROWBACKGROUNDS" , (0 , 0 ), (- 1 , - 1 ), [TABLE_ROW_COLOR , TABLE_ALT_ROW_COLOR ]),
1152+ ("TEXTCOLOR" , (0 , 0 ), (- 1 , 0 ), TABLE_HEADER_TEXT_COLOR ),
1153+ ("FONTNAME" , (0 , 0 ), (- 1 , 0 ), "Helvetica-Bold" ),
1154+ ("LINEBELOW" , (0 , 0 ), (- 1 , 0 ), 1 , TABLE_LINE_COLOR ),
1155+ ("LINEBELOW" , (0 , - 1 ), (- 1 , - 1 ), 1 , TABLE_LINE_COLOR ),
1156+ ("FONTNAME" , (3 , 0 ), (3 , - 1 ), "Helvetica-Bold" ),
1157+ ]
1158+ )
1159+
1160+ table_data : list [list ] = [["Step Name" , "Relative Time" , "UTC Time" , "Status" ]]
1161+
1162+ for row_index , (step_name , step_info ) in enumerate (runner_state .active_test_procedure .step_status .items ()):
1163+ status = step_info .get_step_status ()
1164+
1165+ if status == StepStatus .RESOLVED :
1166+ timestamp = step_info .completed_at
1167+ status_label = "Resolved"
1168+ status_color = PASS_COLOR
1169+ elif status == StepStatus .ACTIVE :
1170+ timestamp = step_info .started_at
1171+ status_label = "Active"
1172+ status_color = GENTLE_WARNING_COLOR
1173+ else :
1174+ timestamp = None
1175+ status_label = "Pending"
1176+ status_color = FAIL_COLOR
1177+
1178+ if timestamp is not None and base_timestamp is not None :
1179+ relative_seconds = (timestamp - base_timestamp ).total_seconds ()
1180+ relative_time = duration_to_label (int (relative_seconds ))
1181+ utc_time = timestamp .strftime (stylesheet .date_format )
1182+ else :
1183+ relative_time = "-"
1184+ utc_time = "-"
1185+
1186+ table_data .append ([step_name , relative_time , utc_time , status_label ])
1187+ table_style .add ("TEXTCOLOR" , (3 , row_index + 1 ), (3 , row_index + 1 ), status_color )
1188+
1189+ column_widths = [int (fraction * stylesheet .table_width ) for fraction in [0.35 , 0.2 , 0.25 , 0.2 ]]
1190+ table = Table (table_data , colWidths = column_widths )
1191+ table .setStyle (table_style )
1192+ return [table ]
1193+
1194+
11151195def generate_timeline_section (
11161196 timeline : Timeline | None , runner_state : RunnerState , sites : Sequence [Site ], stylesheet : StyleSheet
11171197) -> list [Flowable ]:
11181198 elements : list [Flowable ] = []
11191199 elements .append (Paragraph ("Timeline" , stylesheet .heading ))
1200+
11201201 if timeline is not None :
11211202 elements .append (
11221203 Paragraph (
@@ -1127,6 +1208,7 @@ def generate_timeline_section(
11271208 elements .append (generate_timeline_checklist (timeline = timeline , runner_state = runner_state ))
11281209 else :
11291210 elements .append (Paragraph ("Timeline chart is unavailable due to a lack of data." ))
1211+ elements .extend (generate_step_completion_table (runner_state = runner_state , stylesheet = stylesheet ))
11301212 elements .append (stylesheet .spacer )
11311213 return elements
11321214
@@ -1489,6 +1571,7 @@ def generate_page_elements(
14891571 sites : Sequence [Site ],
14901572 timeline : Timeline | None ,
14911573 stylesheet : StyleSheet ,
1574+ set_max_w_varied : bool = False ,
14921575) -> list [Flowable ]:
14931576 active_test_procedure = runner_state .active_test_procedure
14941577 if active_test_procedure is None :
@@ -1545,6 +1628,10 @@ def generate_page_elements(
15451628 # the appropriate client interactions SHOULD be defined in the runner state.
15461629 logger .error (f"Unable to add 'test procedure overview' to PDF report. Reason={ repr (e )} " )
15471630
1631+ # setMaxW Warning Banner
1632+ if set_max_w_varied :
1633+ page_elements .extend (generate_set_max_w_warning_banner (stylesheet = stylesheet ))
1634+
15481635 # Criteria Section
15491636 page_elements .extend (
15501637 generate_criteria_section (
@@ -1557,16 +1644,16 @@ def generate_page_elements(
15571644 generate_timeline_section (timeline = timeline , runner_state = runner_state , sites = sites , stylesheet = stylesheet )
15581645 )
15591646
1560- # Devices Section
1561- page_elements .extend (generate_devices_section (sites = sites , stylesheet = stylesheet ))
1562-
15631647 # Readings Section
15641648 page_elements .extend (
15651649 generate_readings_section (
15661650 runner_state = runner_state , readings = readings , reading_counts = reading_counts , stylesheet = stylesheet
15671651 )
15681652 )
15691653
1654+ # Devices Section
1655+ page_elements .extend (generate_devices_section (sites = sites , stylesheet = stylesheet ))
1656+
15701657 return page_elements
15711658
15721659
@@ -1578,6 +1665,7 @@ def pdf_report_as_bytes(
15781665 sites : Sequence [Site ],
15791666 timeline : Timeline | None ,
15801667 no_spacers : bool = False ,
1668+ set_max_w_varied : bool = False ,
15811669) -> bytes :
15821670 stylesheet = get_stylesheet ()
15831671 if no_spacers :
@@ -1601,6 +1689,7 @@ def pdf_report_as_bytes(
16011689 sites = sites ,
16021690 timeline = timeline ,
16031691 stylesheet = stylesheet ,
1692+ set_max_w_varied = set_max_w_varied ,
16041693 )
16051694
16061695 test_procedure_name = runner_state .active_test_procedure .name
0 commit comments