there are 2 create_pdf_report in the code, please simplify into 1 function
#mainapp.py

import sys
import re  # Import regex module for value extraction
from PySide6 import QtWidgets, QtCore  # Ensure to import QtCore for QTimer and threading
from utils import resource_path
from MainWindow import Ui_MainWindow  # Import the generated UI
from start_up import start_up  # Import the function to initialize UI components
from utils import test_print  # Import the function to handle test printing, uses test_mode = True/False
from comms import list_open_com_ports, send_string_to_ports, send_mdf_and_check  # Import functions from comms.py
from pyqtgraph import PlotWidget
import pyqtgraph as pg
from styles import stylesheet
from graphing import GraphingHandler  # Import GraphingHandler
from datetime import datetime
import os
from create_report_pdf import create_pdf_report  # Import the PDF creation function

### test_mode=True/False is set in utils.py

#only import thr library if in pyinstaller
if getattr(sys, "frozen", False):
    import pyi_splash

converted_value = 0  # global - Initialize to None or an appropriate default value
selected_time = 0  # global - Initialize to None or an appropriate default value

class SerialWorker(QtCore.QThread):
    data_received = QtCore.Signal(str)  # Signal to send data back to the main thread
       
    def __init__(self, port):
        super().__init__()
        self.port = port
        self.running = True

    def run(self):
        while self.running:
            response = send_mdf_and_check([self.port], "wei.mdf")  # Replace with actual reading logic
            if response:
                self.data_received.emit(response)  # Emit the response for processing
            self.msleep(100)  # Delay between requests to reduce load

    def stop(self):
        self.running = False
        self.wait()  # Wait for the thread to fully stop

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        
        # Initialize graphing handler
        self.graph_handler = GraphingHandler(self.graphWidget)

        # set graph bg
        self.graphWidget.setBackground('w')
        self.graphWidget.clear()
        
        # Apply the stylesheet
        self.setStyleSheet(stylesheet)
        
        self.adc_factor_value = 1  # Default value, will be updated in start_up
        
        # List to hold currently open ports
        self.open_ports = []
        
        # Variable to track the connected port
        self.connected_port = None
        
        # Variable to track the state of BtnInitialize
        self.cancel_mode = False
        
        # Initialize the timer
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.send_mdf_message)  # Connect the timer to the send_mdf_message method
        self.adc_factor_value = 0

        # Flag to track whether the test is running
        self.test_running = False  # Initialize to False

        self.log_file_path = None  # Initialize log file path
        self.data_write_counter = 0  # Initialize counter for data writing interval

        # Initialize UI components
        start_up(
            self.LblStatusVersionNo, 
            self.BtnStartTest, 
            self.ComboBoxLang, 
            self.ComboBoxMaxSwl, 
            self.FldJtfDeviceModelNo, 
            self.FldJtfDeviceSerialNo, 
            self.FldJtfDeviceManufacturer, 
            self.FldJtfDeviceMaxCapacitySwl, 
            self.FldTestDataToleranceThreshold, 
            self.FldTestDataTestCycleTime, 
            self.FldTestConductedByCompany, 
            self.FldTestConductedByPlace, 
            self.FldTestDataDateOfTest, 
            self.FldTestDataNextTestDue,
            self.FldDataFlowDisplay,
            self.LblAdcFactor,
            self.LblPaperSize,
            self.ComboBoxTestTime,
            self.LblValueDisplay,
            self.FldHydraulicJackMaxCapacitySwl,
            self.FldResultsTimeStampStart,
            self.FldResultsStartValue,
            self.FldResultsEndValue,
            self.FldResultsDeltaValue,
            self.FldResultsToleranceSwl,
            self.FldResultsTestResult,
            self.FldHydraulicJackSerialNo,
            #self.graphWidget,
            self.LblStabilizeTesting,
            #self.main_window
            self
         )
       
        # Connect the BtnInitialize to the handle button click method
        self.BtnInitialize.clicked.connect(self.on_initialize_clicked)

        # Connect status checkbox
        self.CbStatusPortOPen.clicked.connect(self.on_status_port_open_changed)

        # Connect BtnStartTest to start the countdown
        self.BtnStartTest.clicked.connect(self.start_countdown)
        
        # connect the lang combobox for changes of selection
        self.ComboBoxLang.currentTextChanged.connect(self.save_preferences)
                
        self.worker = None  # Initialize worker
            
    def on_initialize_clicked(self):
        """Event handler for BtnInitialize click."""
        # Reset FldDataFlowDisplay when initializing
        self.FldDataFlowDisplay.clear()
        self.graphWidget.clear()

        if not self.cancel_mode:
            available_ports = list_open_com_ports()
            self.open_ports = available_ports
            
            if self.open_ports:
                test_print("Available COM ports:", self.open_ports)
                responding_port, received_data = send_string_to_ports(self.open_ports, "wei.zrp")
                if responding_port is not None:
                    self.LblStatusComPort.setText(responding_port)
                    self.CbStatusPortOPen.setChecked(True)
                    
                    # Store the connected port
                    self.connected_port = responding_port

                    # Start the worker thread
                    self.worker = SerialWorker(responding_port)
                    self.worker.data_received.connect(self.handle_received_data)  # Connect signal
                    self.worker.start()  # Start the thread
                    
                    self.BtnStartTest.show()  # Display the start test button

                    # Change button text to "Cancel"
                    self.BtnInitialize.setText("Cancel")
                    self.cancel_mode = True  # Set the cancel mode to True
                else:
                    self.LblStatusComPort.setText("--")
                    self.CbStatusPortOPen.setChecked(False)
            else:
                self.LblStatusComPort.setText("No available ports.")
        else:
            # If already in Cancel mode, stop the worker
            self.on_cancel_clicked()

    def handle_received_data(self, response):
        """Handle data received from the serial worker."""
        test_print("Raw Response:", response)  # Debugging line
        
        self.LblStatusLiveData.setText(response)  # Update the status label
        self.update_data_flow_display(response)  # Update the display
        
        # Extract the specific value
        extracted_value = self.extract_value(response)
        test_print("Extracted Value:", extracted_value)  # Print for testing
        if extracted_value is not None:
            # Get the currently selected option from ComboBoxMaxSwl
            selected_unit = self.ComboBoxMaxSwl.currentText()  # Gets the selected option
            # Convert the extracted value to float
            data_value = float(extracted_value)  # Ensure it's a float for calculations

            # Apply conversion factors based on the selected unit
            global converted_value  # needs to be accessed everywhere, especially graphing
            
            if selected_unit == "kN":
                converted_value = data_value * 0.00980665
            elif selected_unit == "T":
                converted_value = data_value * 0.001
            elif selected_unit == "US tn":
                converted_value = data_value * 0.001102317
            elif selected_unit == "kg":
                converted_value = data_value  # No conversion needed
            else:
                converted_value = data_value  # Default case, if no valid unit is found

            # Set the converted value with the unit to LblValueDisplay
            # multiply by adc factor to account for older ADC units
            converted_value = converted_value * float(self.adc_factor_value)
            display_value = f"{converted_value:.2f} {selected_unit}"    # Format to 2 decimal places
            
            self.LblValueDisplay.setText(display_value)  # Set to LblValueDisplay
            test_print("Data Value:", data_value)  # Print for testing
            test_print("Converted Value:", converted_value)  # Print for testing
            test_print("Display Value:", display_value)  # Print for testing
            
            # Update FldResultsEndValue with the live data only during testing
            if self.LblStabilizeTesting.text() == "Testing":
                self.FldResultsEndValue.setText(f"{converted_value:.2f}")  # Update with live data
                
                # Write the raw value to the log file
                if self.log_file_path:
                    with open(self.log_file_path, 'a') as file:
                        file.write(f"{response}\n")
            
            # Call toggle_live_data_radio to toggle the radio button self.toggle_live_data_radio()
            self.toggle_live_data_radio()
            
            
            
    def send_mdf_message(self):
        """This method is now redundant due to threading. Keep for future logic if needed."""
        pass  # You can choose to implement this if you need timed operation or more data handling.

    def extract_value(self, data):
        """Extract the specific numerical value from the 'wei.mdf' data."""
        test_print("Data", data)  # Logging the input data for debugging
        # Updated regex to match float numbers
        match = re.search(r'GRO:\+\s*([\d.]+)', data)  
        if match:
            return match.group(1)  # Return the captured number as a string
        return None  # Return None if no match is found

    def toggle_live_data_radio(self):
        """Toggle the RbLiveData radio button state."""
        if self.RbLiveData.isChecked():
            self.RbLiveData.setChecked(False)  # Uncheck if it's currently checked
            #self.RbLiveData.setStyleSheet("background-color: none")
        else:
            self.RbLiveData.setChecked(True)   # Check it if it's currently unchecked
            #self.RbLiveData.setStyleSheet("background-color: green")
            
    def update_data_flow_display(self, data):
        """Update the FldDataFlowDisplay with new data, prepending it to the display."""
        current_display = self.FldDataFlowDisplay.toPlainText()  # Get current text in the display field
        
        # Ensure only valid data (e.g., raw data) is displayed, and it's not empty
        if data.strip():  # Check if data is not empty or just whitespace
            new_display = data + "\n" + current_display  # Prepend new raw data
            self.FldDataFlowDisplay.setPlainText(new_display)  # Update the display

        if self.LblStabilizeTesting.text() == "Testing" and self.LblTimerDisplay.text() != "00:00":
            test_print("Converted value:", converted_value)
            selected_datavalue_str = self.LblValueDisplay.text()
            selected_datavalue = float(converted_value)

            # Elapsed time should be calculated and kept aside for graphing
            elapsed_time = selected_time - self.remaining_time
            test_print(f"Elapsed Time: {elapsed_time}, Remaining Time: {self.remaining_time}")

            # Use the graphing handler to update the graph
            try:
                max_capacity_value = int(self.FldHydraulicJackMaxCapacitySwl.text())
            except ValueError:
                # Handle the case where the conversion fails
                print("Error: The value provided is not a valid integer.")
                max_capacity_value = 0  # Or any default value you want to use
            self.graph_handler.update_graph(None, converted_value, selected_time, 
                                            self.ComboBoxMaxSwl.currentText(), elapsed_time, max_capacity_value)
        else:
            # Handle case where the value could not be extracted
            test_print("Could not extract a valid value for the Y-axis.")



    def on_cancel_clicked(self):
        """Handle canceling the operation and resetting the button."""
        if self.worker:
            self.worker.data_received.disconnect(self.handle_received_data)  # Disconnect signal
            self.worker.stop()  # Stop the worker thread
            self.worker.quit()  # Request the thread to quit
            self.worker.wait()  # Wait for the thread to finish
            self.worker = None  # Ensure worker is set to None

        self.FldDataFlowDisplay.setPlainText("")  # Clear the display immediately after stopping the worker
        self.LblStatusComPort.setText("--")  # Reset com port display
        self.BtnInitialize.setText("Initialize")  # Reset button text to Initialize
        self.CbStatusPortOPen.setChecked(False)  # Reset port open status unchecked
        self.LblValueDisplay.setText("")  # Clear the value display on cancel
        self.LblStatusLiveData.setText("")  # Clear the live data display on cancel
        self.cancel_mode = False  # Reset cancel mode
        self.BtnStartTest.hide()  # Hide the start test button
        self.LblStabilizeTesting.setText("")  # Clear the timer type label
        self.FldResultsTimeStampStart.setText("")  # Clear the results start time value
        self.FldResultsStartValue.setText("")  # Clear the results start value
        self.FldResultsEndValue.setText("")  # Clear the results end value
        self.FldResultsDeltaValue.setText("")  # Clear the delta value
        self.FldResultsToleranceSwl.setText("")  # Clear the percentage value
        self.FldResultsTestResult.setText("")  # Clear the test pass/fail value
        
        # Clear the graph data and reset the graph
        self.graph_handler.clear_graph_data()
        
        # Stop and reset the timer
        if hasattr(self, 'countdown_timer'):
            self.countdown_timer.stop()
        self.LblTimerDisplay.setText("00:00")  # Stop at 00:00
        
    def on_status_port_open_changed(self):
        """Event handler for CbStatusPortOPen click."""
        if self.CbStatusPortOPen.isChecked():
            if self.connected_port:
                try:
                    test_print(f"Closing port: {self.connected_port}")
                    # Close the specific port logic should go here
                except Exception as e:
                    test_print(f"Error closing port {self.connected_port}: {e}")

            self.LblStatusComPort.setText("--")
            self.CbStatusPortOPen.setChecked(False)
        else:
            self.LblStatusComPort.setText("--")

    def start_countdown(self):
        global selected_time
        self.BtnStartTest.hide()  # hide start button when testing has started
        self.LblStabilizeTesting.setText("Stabilize")
        
        # Clear FldResultsEndValue when the test starts
        self.FldResultsEndValue.setText("")
        
        # Start the countdown timer based on the selected time in ComboBoxTestTime.
        selected_time = int(self.ComboBoxTestTime.currentText())  # Get the selected time in seconds
        self.remaining_time = selected_time  # Initialize remaining_time

        # Debugging: Print selected_time and remaining_time
        test_print(f"Selected Time: {selected_time}, Remaining Time: {self.remaining_time}")

        # Set the test_running flag to True
        self.test_running = True

        # Initialize the countdown timer
        self.countdown_timer = QtCore.QTimer(self)
        self.countdown_timer.timeout.connect(self.update_countdown)
        self.countdown_timer.start(1000)  # Update every second

    def update_countdown(self):
        """Update the countdown timer display."""
        if self.remaining_time >= 0:  # Use >= to include the final state
            # Update the display with the current remaining_time
            minutes, seconds = divmod(self.remaining_time, 60)
            self.LblTimerDisplay.setText(f"{minutes:02}:{seconds:02}")  # Update the display
            test_print(f"Countdown: {minutes:02}:{seconds:02}")

            # Calculate elapsed_time for graphing
            elapsed_time = selected_time - self.remaining_time
            test_print(f"Elapsed Time: {elapsed_time}, Remaining Time: {self.remaining_time}")

            # Call update_data_flow_display with an empty string or relevant data that should display
            self.update_data_flow_display("")  # Update without showing elapsed/remaining time
            
            # Calculate and update the delta value and percentage value
            self.calculate_swl_delta_value()  # Add this line to update the delta and percentage values

            # Calculate and update the tolerance value and percentage value
            self.calculate_swl_tolerance_value()  # Add this line to update the tolerance and percentage values
            
            # Check the tolerance value during testing
            try:
                tolerance_value = float(self.FldResultsToleranceSwl.text().replace("%", ""))
                if tolerance_value > 2:
                    self.FldResultsTestResult.setText("FAIL")
                    self.FldResultsTestResult.setStyleSheet("background-color: white; color: red; font-size: 12pt; font-weight: bold;")
                    self.countdown_timer.stop()  # Stop the timer
                    self.LblTimerDisplay.setText("00:00")  # Stop at 00:00
                    self.test_running = False  # Set the test_running flag to False
                    create_pdf_report(
                        self.FldHydraulicJackSerialNo.text().strip(),
                        self.FldResultsStartValue.text(),
                        self.FldResultsEndValue.text(),
                        self.FldResultsDeltaValue.text(),
                        self.FldResultsToleranceSwl.text(),
                        self.FldResultsTestResult.text(),
                        self.LblPaperSize.text().strip()
                    )  # Create PDF report at the end of the test
                    return  # Exit the method to stop further processing
            except ValueError:
                self.FldResultsTestResult.setText("")  # Handle invalid input
                self.FldResultsTestResult.setStyleSheet("background-color: white; color: black; font-size: 12pt; font-weight: bold;")

            # Decrement remaining_time after updating the display
            self.remaining_time -= 1
        else:
            # Stop the timer and process the final state
            self.countdown_timer.stop()
            self.LblTimerDisplay.setText("00:00")  # Stop at 00:00

            # Set the test_running flag to False
            self.test_running = False

            if self.LblStabilizeTesting.text() == "Testing":
                """Set the data start value to the Results Start Value Field"""
                start_value = "{:,.2f}".format(converted_value)
                self.FldResultsEndValue.setText(str(start_value))  # Set the data value to the label

            # Process the final state (elapsed_time = selected_time)
            elapsed_time = selected_time
            test_print(f"Final Elapsed Time: {elapsed_time}")
            self.update_data_flow_display("")  # Update display without specific content

            # Check if LblStabilizeTesting is "Stabilize"
            if self.LblStabilizeTesting.text() == "Stabilize":
                self.LblStabilizeTesting.setText("Testing")  # Change to "Testing"

                """Set the current time to the Results Start Time Field"""
                now = datetime.now()  # Get current time
                formatted_time = now.strftime("%I:%M:%S %p")  # Format time as hh:mm:ss AM/PM
                self.FldResultsTimeStampStart.setText(formatted_time)  # Set the formatted time to the label

                """Set the data start value to the Results Start Value Field"""
                start_value = "{:,.2f}".format(converted_value)
                self.FldResultsStartValue.setText(str(start_value))  # Set the data value to the label

                # Create a file in the resources/datalog folder at the start of the testing phase
                timestamp = now.strftime("%d-%m-%Y_%I-%M-%S_%p")
                serial_no = self.FldHydraulicJackSerialNo.text().strip()
                filename = f"{timestamp}_{serial_no}_dataLog.txt"
                self.log_file_path = os.path.join(resource_path('resources/datalog'), filename)
                with open(self.log_file_path, 'w') as file:
                    file.write(f"Test started at {timestamp}\n")
                test_print(f"Created log file: {self.log_file_path}")

                # Restart the countdown and update graph
                self.restart_timer()
            else:
                # Determine test result based on tolerance value
                try:
                    tolerance_value = float(self.FldResultsToleranceSwl.text().replace("%", ""))
                    if tolerance_value > 2:
                        self.FldResultsTestResult.setText("FAIL")
                        self.FldResultsTestResult.setStyleSheet("background-color: white; color: red; font-size: 12pt; font-weight: bold;")
                    else:
                        self.FldResultsTestResult.setText("PASS")
                        self.FldResultsTestResult.setStyleSheet("background-color: white; color: green; font-size: 12pt; font-weight: bold;")
                except ValueError:
                    self.FldResultsTestResult.setText("N/A")  # Handle invalid input
                    self.FldResultsTestResult.setStyleSheet("background-color: white; color: black; font-size: 12pt; font-weight: bold;")
                create_pdf_report(
                    self.FldHydraulicJackSerialNo.text().strip(),
                    self.FldResultsStartValue.text(),
                    self.FldResultsEndValue.text(),
                    self.FldResultsDeltaValue.text(),
                    self.FldResultsToleranceSwl.text(),
                    self.FldResultsTestResult.text(),
                    self.LblPaperSize.text().strip()
                )  # Create PDF report at the end of the test

    def restart_timer(self):
        """Restart the timer"""
        global selected_time
        # Reset the remaining_time based on the selected time
        selected_time = self.ComboBoxTestTime.currentText()  # Get the selected time in mm:ss format
        selected_time = int(selected_time)
        #minutes, seconds = map(int, selected_time.split(':'))  # Split and convert to integers
        self.remaining_time = selected_time  # Convert to total seconds
        # Restart the countdown timer
        self.countdown_timer.start(1000)  # Update every second
        
    # Save seleted lang option to preferences file
    def save_preferences(self, selected_text):
        selected_text = self.ComboBoxLang.currentText()  # Gets the selected option
        preferences_path = resource_path('resources/preferences.txt')
        with open(preferences_path, 'w') as file:
            file.write(selected_text)  # Write current selection to file
            
    def get_swl_value_hjack(self):
        """Extract the first word from FldHydraulicJackMaxCapacitySwl and convert it to an integer."""
        text = self.FldHydraulicJackMaxCapacitySwl.text().strip()  # Get the text and remove leading/trailing spaces
        if text:
            first_word = text.split()[0]  # Extract the first word
            try:
                return int(first_word)  # Convert to integer
            except ValueError:
                print("Error: The first word is not a valid integer.")
        return 0  # Default value if extraction fails
    
    
    
    def calculate_swl_tolerance_value(self):
        """Calculate the tolerance value and update FldResultsToleranceSwl."""
        """more work to do soon"""
        try:
            # Get the start and end values from the fields as floats
            start_value = float(self.FldResultsStartValue.text().replace(",", ""))  # Remove commas for conversion
            end_value = float(self.FldResultsEndValue.text().replace(",", ""))  # Remove commas for conversion
            
            # Get the SWL value from the hydraulic jack field
            swl_value_hjack = self.get_swl_value_hjack()
            
            # Calculate the tolerance value
            if swl_value_hjack != 0:  # Avoid division by zero
                swl_tolerance_value = ((start_value - end_value) / swl_value_hjack) * 100
                self.FldResultsToleranceSwl.setText(f"{swl_tolerance_value:.2f}%")  # Update the field with 2 decimal places
            else:
                self.FldResultsToleranceSwl.setText(f"{swl_tolerance_value:.2f}%")  # Update the field with 2 decimal places
                self.FldResultsToleranceSwl.setText("")  # Handle division by zero
        except ValueError:
            print("Error: Invalid values in start or end fields.")
            self.FldResultsToleranceSwl.setText("")  # Handle invalid input
            
            
    def calculate_swl_delta_value(self):
        """Calculate the delta value and update FldResultsDeltaValue."""
        try:
            delta_start_value = float(self.FldResultsStartValue.text().replace(",", ""))  # Remove commas for conversion
            delta_end_value = float(self.FldResultsEndValue.text().replace(",", ""))  # Remove commas for conversion
            delta_value = (delta_start_value - delta_end_value)
            self.FldResultsDeltaValue.setText(f"{delta_value:.2f}")
        except ValueError:
            print("Error: Invalid values in start or end fields.")
            self.FldResultsDeltaValue.setText("")  # Handle invalid input
                  
            
# Start the application
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    if getattr(sys, "frozen", False):
        pyi_splash.close()
    window = MainWindow()
    window.show()
    sys.exit(app.exec())