1. Right now i am getting 4 files. I do not want the first file being written. Omit that. I need the _formatted.xlsx, routekey xlsx and the sales by bin xlsx only.
2. Show Info on console everytime with timestamp so that I know what is happening right now.
3. After Drawing all Vertical borders in the inv_count_and_value_summary_formatted file. I want to erase all VERTICAL lines from the row having Summation till eof.
Give me complete code.

I have this code:
```import pandas as pd
import numpy as np
from pathlib import Path
import sys
import re
from functools import reduce
from openpyxl import Workbook, load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font, Border, Side
import copy
import logging

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Constants (unchanged)
COL_TRANSACTION_TYPE = 'Transaction Type'
COL_LOCATION = 'Location'
COL_CUSTOMER_TYPE = 'Customer Type'
COL_CUSTOMER_CODE = 'Customer Code'
COL_INVOICE_NUMBER = 'Invoice Number'
COL_BAD_RETURN_QTY = 'Bad Return Qty Value'
COL_DEFECTIVE_RETURN_QTY = 'Defective Return Qty Value'
COL_SALES_QTY = 'Sales Qty Value'
COL_GOOD_RETURN_QTY = 'Good Return Qty Value'
COL_DISCOUNT_VALUE = 'Discount Value'
COL_INVOICE_VALUE = 'Invoice Value'
COL_TOTAL_INV_COUNT = 'Total Invoice Count'
COL_TOTAL_VALUE_INV = 'Total VALUE of Invoices'
COL_NET_VALUE = 'Net Value'
COL_ROUTE_CODE = 'Route Code'

VAL_RETAIL_EXPIRY = 'Retail Expiry'
VAL_RETAIL_SALES = 'Retail Sales'
CUSTOMER_TYPE_CASH = 'Cash Customer'
CUSTOMER_TYPE_CREDIT = 'Credit Customer'

EXPECTED_VALUE_COLS = [COL_DISCOUNT_VALUE, COL_SALES_QTY, COL_GOOD_RETURN_QTY, COL_BAD_RETURN_QTY, COL_DEFECTIVE_RETURN_QTY]
COLS_FOR_RIGHT_BORDER = [COL_LOCATION, 'COUNT of Credit Customers', 'COUNT of Credit Invoices', COL_TOTAL_INV_COUNT, 'VALUE by Credit Invoices', COL_TOTAL_VALUE_INV]

class DataLoader
    def __init__(self, directory Path = Path('.'))
        self.directory = directory
        self.files = self._find_xlsb_files()
        self.dataframes = self._load_dataframes()
        self.aggregated_df = self._aggregate_dataframes()

    def _find_xlsb_files(self) - list[Path]
        files = list(self.directory.glob('.xlsb'))
        if not files
            logger.info(fNo .xlsb files found in {self.directory})
        return files

    def _load_dataframes(self) - list[pd.DataFrame]
        dfs = []
        for file in self.files
            try
                df = pd.read_excel(file, engine='pyxlsb')
                df.columns = df.columns.str.strip()
                dfs.append(df)
                logger.info(fLoaded {file.name})
            except Exception as e
                logger.error(fError loading {file.name} {e})
        return dfs

    def _aggregate_dataframes(self) - pd.DataFrame
        if not self.dataframes
            return pd.DataFrame()
        combined = pd.concat(self.dataframes, ignore_index=True)
        combined[COL_NET_VALUE] = pd.to_numeric(combined.get(COL_NET_VALUE, 0), errors='coerce')
        combined[COL_DISCOUNT_VALUE] = pd.to_numeric(combined.get(COL_DISCOUNT_VALUE, 0), errors='coerce')
        return combined

class SummaryProcessor
    def __init__(self, dataframes list[pd.DataFrame], files list[Path])
        self.dataframes = dataframes
        self.files = files
        self.summary_data = {}  # To store summary data in memory

    def process(self)
        if not self.dataframes
            logger.info(No data to process for summaries.)
            return

        # Process dataframes and store results in memory
        processed_dfs = [self._process_single_df(df) for df in self.dataframes]
        all_counts = [df[[COL_TRANSACTION_TYPE, COL_LOCATION, COL_TOTAL_INV_COUNT]].rename(columns={COL_TOTAL_INV_COUNT Path(file).stem}) for df, file in zip(processed_dfs, self.files)]
        all_values = [df[[COL_TRANSACTION_TYPE, COL_LOCATION, COL_TOTAL_VALUE_INV]].rename(columns={COL_TOTAL_VALUE_INV Path(file).stem}) for df, file in zip(processed_dfs, self.files)]

        # Build master summaries
        summary_counts = self._build_master_summary(all_counts)
        summary_values = self._build_master_summary(all_values)

        # Store in memory instead of writing to file
        self.summary_data['Invoice Count Summary'] = summary_counts
        self.summary_data['Invoice Value Summary'] = summary_values
        for file, df in zip(self.files, processed_dfs)
            sheet_name = self._sanitize_sheet_name(Path(file).stem)
            self.summary_data[sheet_name] = df
        logger.info(Summaries processed and stored in memory.)

    def _process_single_df(self, df pd.DataFrame) - pd.DataFrame
        # (Unchanged, same as original)
        df = df.copy()
        for col in [COL_BAD_RETURN_QTY, COL_DEFECTIVE_RETURN_QTY, COL_CUSTOMER_CODE, COL_INVOICE_NUMBER]
            df[col] = df.get(col, np.nan)

        conditions = [df[COL_BAD_RETURN_QTY].notna()  df[COL_DEFECTIVE_RETURN_QTY].notna()]
        choices = [VAL_RETAIL_EXPIRY]
        df[COL_TRANSACTION_TYPE] = np.select(conditions, choices, default=VAL_RETAIL_SALES)
        mask_outlet = df[COL_LOCATION].astype(str).str.strip().str.casefold() == 'outlet'
        df.loc[mask_outlet, COL_TRANSACTION_TYPE] = VAL_RETAIL_SALES
        df[COL_TRANSACTION_TYPE] = pd.Categorical(df[COL_TRANSACTION_TYPE], categories=[VAL_RETAIL_SALES, VAL_RETAIL_EXPIRY], ordered=True)

        for col in EXPECTED_VALUE_COLS
            df[col] = df.get(col, 0).fillna(0)
        is_sales = df[COL_TRANSACTION_TYPE] == VAL_RETAIL_SALES
        sales_val = df[COL_SALES_QTY] + df[COL_GOOD_RETURN_QTY] - df[COL_DISCOUNT_VALUE]
        expiry_val = df[COL_BAD_RETURN_QTY] + df[COL_DEFECTIVE_RETURN_QTY] - df[COL_DISCOUNT_VALUE]
        df[COL_INVOICE_VALUE] = np.where(is_sales, sales_val, expiry_val)

        counts = self._create_count_summary(df)
        value_agg = df.groupby([COL_TRANSACTION_TYPE, COL_LOCATION, COL_CUSTOMER_TYPE], observed=True).agg(Total_Invoice_Value_Sum=(COL_INVOICE_VALUE, 'sum')).reset_index()
        values = self._create_value_pivot(value_agg, 'Total_Invoice_Value_Sum', {CUSTOMER_TYPE_CASH 'VALUE by Cash Invoices', CUSTOMER_TYPE_CREDIT 'VALUE by Credit Invoices'})

        processed = counts.merge(values, on=[COL_TRANSACTION_TYPE, COL_LOCATION], how='left')
        non_categorical_cols = processed.columns.difference([COL_TRANSACTION_TYPE, COL_LOCATION])
        processed[non_categorical_cols] = processed[non_categorical_cols].fillna(0)
        processed = processed.dropna(subset=[COL_TRANSACTION_TYPE])
        processed[COL_TOTAL_INV_COUNT] = processed['COUNT of Cash Invoices'] + processed['COUNT of Credit Invoices']
        processed[COL_TOTAL_VALUE_INV] = processed['VALUE by Cash Invoices'] + processed['VALUE by Credit Invoices']
        metric_cols = processed.columns.difference([COL_TRANSACTION_TYPE, COL_LOCATION])
        processed = processed.loc[~(processed[metric_cols] == 0).all(axis=1)].reset_index(drop=True)
        return processed

    def _create_count_summary(self, df pd.DataFrame) - pd.DataFrame
        # (Unchanged, same as original)
        summary = df.groupby([COL_TRANSACTION_TYPE, COL_LOCATION, COL_CUSTOMER_TYPE], observed=True).agg(
            No_of_Customers=(COL_CUSTOMER_CODE, pd.Series.nunique),
            Inv_Count=(COL_INVOICE_NUMBER, pd.Series.nunique)
        ).reset_index()
        pivot = summary.pivot_table(index=[COL_TRANSACTION_TYPE, COL_LOCATION], columns=COL_CUSTOMER_TYPE, values=['No_of_Customers', 'Inv_Count'], fill_value=0, observed=True).reset_index()
        pivot.columns = [' '.join(col).strip() if isinstance(col, tuple) else col for col in pivot.columns]
        pivot = pivot.rename(columns={
            f'No_of_Customers {CUSTOMER_TYPE_CASH}' 'COUNT of Cash Customers',
            f'No_of_Customers {CUSTOMER_TYPE_CREDIT}' 'COUNT of Credit Customers',
            f'Inv_Count {CUSTOMER_TYPE_CASH}' 'COUNT of Cash Invoices',
            f'Inv_Count {CUSTOMER_TYPE_CREDIT}' 'COUNT of Credit Invoices'
        })
        required = [COL_TRANSACTION_TYPE, COL_LOCATION, 'COUNT of Cash Customers', 'COUNT of Credit Customers', 'COUNT of Cash Invoices', 'COUNT of Credit Invoices']
        for col in required
            pivot[col] = pivot.get(col, 0)
        pivot.sort_values([COL_TRANSACTION_TYPE, COL_LOCATION], inplace=True)
        return pivot[required].reset_index(drop=True)

    def _create_value_pivot(self, df pd.DataFrame, value_col str, rename_map dict) - pd.DataFrame
        # (Unchanged, same as original)
        pivot = df.pivot_table(index=[COL_TRANSACTION_TYPE, COL_LOCATION], columns=COL_CUSTOMER_TYPE, values=value_col, fill_value=0, observed=True).reset_index()
        pivot.columns = [' '.join(col).strip() if isinstance(col, tuple) else col for col in pivot.columns]
        pivot = pivot.rename(columns=rename_map)
        pivot[COL_TRANSACTION_TYPE] = pd.Categorical(pivot[COL_TRANSACTION_TYPE], categories=[VAL_RETAIL_SALES, VAL_RETAIL_EXPIRY], ordered=True)
        for col in rename_map.values()
            pivot[col] = pivot.get(col, 0)
        return pivot

    def _build_master_summary(self, frames list[pd.DataFrame]) - pd.DataFrame
        # (Unchanged, same as original)
        if not frames
            return pd.DataFrame()
        base = pd.DataFrame({COL_TRANSACTION_TYPE pd.Categorical([], categories=[VAL_RETAIL_SALES, VAL_RETAIL_EXPIRY], ordered=True), COL_LOCATION []})
        master = reduce(lambda left, right pd.merge(left, right, on=[COL_TRANSACTION_TYPE, COL_LOCATION], how='outer'), frames, base)
        value_cols = [c for c in master.columns if c not in [COL_TRANSACTION_TYPE, COL_LOCATION]]
        master[value_cols] = master[value_cols].fillna(0)
        master.dropna(subset=[COL_TRANSACTION_TYPE, COL_LOCATION], inplace=True)
        master.sort_values([COL_TRANSACTION_TYPE, COL_LOCATION], inplace=True)
        return master

    def _sanitize_sheet_name(self, name str) - str
        # (Unchanged, same as original)
        name = re.sub(r'[[]]', '_', name)
        return name[31]

class Formatter
    def __init__(self, summary_data dict, output_file str)
        self.summary_data = summary_data
        self.output_file = Path(output_file)
        self.wb = Workbook()  # Create a new workbook
        self.config = {
            'target_start_col' 11,
            'target_end_col' 20,
            'summation_row' 16,
            'location_col' 21,
            'expiry_unique_customer_col' 22,
            'expiry_count_col' 23,
            'expiry_value_col' 24,
            'border_columns' [1, 6, 9, 11, 19],
            'skip_first_n_sheets' 2
        }
        self.styles = {
            'thin_border' Side(border_style=thin, color=000000),
            'bold_font' Font(bold=True),
            'percentage_format' '0.00%',
            'number_format' '#,##0'
        }

    def process(self)
        if not self.summary_data
            logger.error(No summary data to process.)
            return
        for sheet_name, df in self.summary_data.items()
            self._process_sheet(sheet_name, df)
        self._format_numbers()
        self._autofit_columns()
        self.wb.save(self.output_file)
        logger.info(fFormatted file saved to {self.output_file})

    def _process_sheet(self, sheet_name str, df pd.DataFrame)
        ws = self.wb.create_sheet(sheet_name)
        if df.empty
            pd.DataFrame([{Message fNo data for {sheet_name}}]).to_excel(ws, index=False)
            return

        if sheet_name in ['Invoice Count Summary', 'Invoice Value Summary']
            sales_df = df[df[COL_TRANSACTION_TYPE] == VAL_RETAIL_SALES].drop(columns=[COL_TRANSACTION_TYPE], errors='ignore')
            expiry_df = df[df[COL_TRANSACTION_TYPE] == VAL_RETAIL_EXPIRY].drop(columns=[COL_TRANSACTION_TYPE], errors='ignore')
            current_row = 0
            if not sales_df.empty
                sales_df.to_excel(ws, startrow=current_row, index=False, engine='openpyxl')
                current_row += len(sales_df) + 2
            if not expiry_df.empty
                expiry_df.to_excel(ws, startrow=current_row, index=False, engine='openpyxl')
            self._auto_adjust_columns(ws, pd.concat([sales_df, expiry_df], ignore_index=True))
        else
            ordered_cols = [COL_LOCATION, 'COUNT of Cash Customers', 'COUNT of Credit Customers', 'COUNT of Cash Invoices', 'COUNT of Credit Invoices', COL_TOTAL_INV_COUNT, 'VALUE by Cash Invoices', 'VALUE by Credit Invoices', COL_TOTAL_VALUE_INV]
            df_sheet = df.drop(columns=[COL_TRANSACTION_TYPE], errors='ignore')[ordered_cols].fillna(0)
            df_sheet.to_excel(ws, index=False, engine='openpyxl')
            self._auto_adjust_columns(ws, df_sheet)
            right_border_fmt = self.wb.add_format({'right' 1})
            for col_name in COLS_FOR_RIGHT_BORDER
                if col_name in df_sheet.columns
                    col_idx = df_sheet.columns.get_loc(col_name) + 1  # +1 for openpyxl 1-based indexing
                    ws.set_column(col_idx, col_idx, None, right_border_fmt)
        
        self._copy_headers_to_right(ws)
        self._move_second_table(ws)
        self._apply_borders(ws)
        self._add_summation_row(ws)
        self._add_expiry_columns(ws)

    def _copy_headers_to_right(self, ws)
        max_col = ws.max_column
        for i in range(1, max_col + 1)
            val = ws.cell(1, i).value
            if val
                cell = ws.cell(1, self.config['target_start_col'] + i - 1)
                cell.value = val
                cell.font = self.styles['bold_font']
            ws.cell(1, i).font = self.styles['bold_font']

    def _move_second_table(self, ws)
        max_row = ws.max_row
        ref_value = ws['A2'].value
        if not ref_value
            return
        cut_row = next((row for row in range(3, max_row + 1) if ws.cell(row, 1).value == ref_value), None)
        if not cut_row
            return
        rows_to_move = max_row - cut_row + 1
        for r in range(rows_to_move)
            for c in range(1, ws.max_column + 1)
                src = ws.cell(cut_row + r, c)
                dest = ws.cell(2 + r, self.config['target_start_col'] + c - 1)
                dest.value = src.value
                if src.has_style
                    dest._style = copy.copy(src._style)
        ws.delete_rows(cut_row, rows_to_move)

    def _apply_borders(self, ws)
        max_row, max_col = ws.max_row, ws.max_column
        thin = self.styles['thin_border']
        for row in ws.iter_rows(1, max_row, 1, max_col)
            for cell in row
                cell.border = Border()

        for col in self.config['border_columns']
            for row in range(1, 18)
                ws.cell(row, col).border = Border(left=thin, right=thin)

        for col in range(1, max_col + 1)
            cell = ws.cell(1, col)
            current = cell.border
            cell.border = Border(left=current.left, right=current.right, bottom=thin)

    def _add_summation_row(self, ws)
        row = self.config['summation_row']
        ws.insert_rows(row)
        ws.cell(row, 1).value = Summation
        ws.cell(row, 1).font = self.styles['bold_font']
        ws.cell(row, 1).border = Border(top=self.styles['thin_border'])
        for col in range(2, ws.max_column + 1)
            if any(isinstance(ws.cell(r, col).value, (int, float)) for r in range(2, row))
                col_letter = get_column_letter(col)
                cell = ws.cell(row, col)
                cell.value = f=SUM({col_letter}2{col_letter}{row-1})
                cell.font = self.styles['bold_font']
                cell.border = Border(top=self.styles['thin_border'])
                cell.number_format = self.styles['number_format']

    def _add_expiry_columns(self, ws)
        row = self.config['summation_row']
        cols = {
            'location' self.config['location_col'],
            'unique_customer' self.config['expiry_unique_customer_col'],
            'count' self.config['expiry_count_col'],
            'value' self.config['expiry_value_col']
        }
        ws.cell(1, cols['location']).value = Location
        ws.cell(1, cols['unique_customer']).value = Expiry by Unique Customer
        ws.cell(1, cols['count']).value = Expiry by Invoice Count
        ws.cell(1, cols['value']).value = Expiry by Value
        for c in cols.values()
            ws.cell(1, c).font = self.styles['bold_font']

        second_map = {str(ws.cell(r, 11).value).strip().lower() r for r in range(2, row) if ws.cell(r, 11).value}
        for r in range(2, row)
            loc_val = str(ws.cell(r, 1).value).strip()
            loc = loc_val.lower()
            match_r = second_map.get(loc)
            ws.cell(r, cols['location']).value = loc_val

            l_val = ws.cell(match_r, 12).value if match_r else 0
            m_val = ws.cell(match_r, 13).value if match_r else 0
            b_val = ws.cell(r, 2).value or 0
            c_val = ws.cell(r, 3).value or 0
            p_val = ws.cell(match_r, 16).value if match_r else 0
            f_val = ws.cell(r, 6).value or 0
            s_val = ws.cell(match_r, 19).value if match_r else 0
            i_val = ws.cell(r, 9).value or 0

            self._set_percentage(ws, r, cols['unique_customer'], l_val + m_val, b_val + c_val)
            self._set_percentage(ws, r, cols['count'], p_val, f_val)
            self._set_percentage(ws, r, cols['value'], s_val, i_val)

        for c in cols.values()
            ws.cell(row, c).value = None

    def _set_percentage(self, ws, row, col, num, den)
        cell = ws.cell(row, col)
        try
            num, den = float(num), float(den)
            cell.value = round(num  den, 4) if den != 0 else 0
        except (ValueError, ZeroDivisionError)
            cell.value = 0
        cell.number_format = self.styles['percentage_format']

    def _format_numbers(self)
        for ws in self.wb.worksheets
            for row in ws.iter_rows()
                for cell in row
                    if isinstance(cell.value, (int, float)) and cell.number_format != self.styles['percentage_format']
                        cell.number_format = self.styles['number_format']

    def _autofit_columns(self)
        for ws in self.wb.worksheets
            for col in ws.columns
                max_len = 0
                letter = get_column_letter(col[0].column)
                for cell in col
                    if cell.value is not None
                        val = f{cell.value,.0f} if isinstance(cell.value, (int, float)) else str(cell.value)
                        max_len = max(max_len, len(val))
                ws.column_dimensions[letter].width = min(max(max_len + 2, 8), 50)

    def _auto_adjust_columns(self, worksheet, df pd.DataFrame, padding int = 2)
        if df.empty
            worksheet.set_column(0, 0, 50)
            return
        for idx, col_name in enumerate(df.columns)
            header_len = len(str(col_name))
            max_len = max(header_len, df[col_name].astype(str).str.len().max()) + padding if not df[col_name].empty else header_len + padding
            worksheet.set_column(idx + 1, idx + 1, min(max_len, 255))  # +1 for openpyxl 1-based indexing

class BinningProcessor
    def __init__(self, aggregated_df pd.DataFrame)
        self.df = aggregated_df
        self.bins_output = 'sales_by_bins.xlsx'
        self.key_output = 'route_to_location_key.xlsx'

    def process(self)
        if self.df.empty
            logger.info(No data for binning.)
            return

        route_summary = self.df.groupby(COL_ROUTE_CODE, observed=True)[COL_NET_VALUE].sum().reset_index()
        bins = [0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, 200000, float('inf')]
        labels = ['0-25k', '25k-50k', '50k-75k', '75k-100k', '100k-125k', '125k-150k', '150k-175k', '175k-200k', '200k+']
        route_summary['Bin Range'] = pd.cut(route_summary[COL_NET_VALUE], bins=bins, labels=labels, right=False)
        route_summary.sort_values(by=COL_NET_VALUE, ascending=False, inplace=True)
        route_summary.to_excel(self.bins_output, index=False)

        route_location = self.df[[COL_ROUTE_CODE, COL_LOCATION]].drop_duplicates().sort_values(by=[COL_ROUTE_CODE, COL_LOCATION])
        route_location.to_excel(self.key_output, index=False)
        logger.info(fBinning files saved {self.bins_output} and {self.key_output})

class MainProcessor
    def __init__(self)
        self.loader = DataLoader()
        self.summary_processor = SummaryProcessor(self.loader.dataframes, self.loader.files)
        self.formatter = Formatter(self.summary_processor.summary_data, 'inv_count_and_value_summary_formatted.xlsx')
        self.binning_processor = BinningProcessor(self.loader.aggregated_df)

    def run(self)
        if not self.loader.files
            logger.info(No files found. Exiting.)
            sys.exit(0)
        self.summary_processor.process()
        self.formatter.process()
        self.binning_processor.process()
        logger.info(All processing complete.)

if __name__ == '__main__'
    MainProcessor().run()```