Add logs for this code to show progress at all times:

import * as fs from 'fs';
import csv from 'csv-parser';

interface Mapping {
    sentralColumn: string;
    proserveColumn: string;
}

interface ParityReportEntry {
    proserveId: string;
    sentralId: string;
    proserveColumn: string;
    sentralColumn: string;
    proserveValue: string;
    sentralValue: string;
}

async function parseCSV(filePath: string): Promise<any[]> {
    return new Promise((resolve, reject) => {
        const results: any[] = [];
        fs.createReadStream(filePath)
            .pipe(csv())
            .on('data', (data: any) => results.push(data))
            .on('end', () => resolve(results))
            .on('error', (error: any) => reject(error));
    });
}

function generateParityReport(
    sentralData: any[],
    proserveData: any[],
    mappings: Mapping[],
    recordIdMapping: [string, string][],
    sentralIdColumn: string,
    proserveIdColumn: string
): { report: ParityReportEntry[], summary: any } {
    const report: ParityReportEntry[] = [];
    let totalRecords = 0;
    let failedCount = 0;
    let passedCount = 0;
    let totalMismatches = 0;

    const sentralMap = new Map(sentralData.map((row) => [row[sentralIdColumn], row]));
    const proserveMap = new Map(proserveData.map((row) => [row[proserveIdColumn], row]));
    let skipped = 0;

    recordIdMapping.forEach(([proserveId, sentralId]) => {
        const sentralRow = sentralMap.get(sentralId);
        const proserveRow = proserveMap.get(proserveId);

        if (!sentralRow || !proserveRow) {
            skipped++;
            return; // Skip if there's no corresponding row in either dataset
        }

        totalRecords++;

        let recordHasMismatch = false;
        mappings.forEach((mapping) => {
            let sentralValue = sentralRow[mapping.sentralColumn];
            let proserveValue = proserveRow[mapping.proserveColumn];

            if (typeof sentralValue === 'string') {
                sentralValue = sentralValue.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
            }
            if (typeof proserveValue === 'string') {
                proserveValue = proserveValue.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
            }

            if ((sentralValue === '' || sentralValue == null) && (proserveValue === '' || proserveValue == null)) {
                return;
            }

            if (sentralValue !== proserveValue) {
                report.push({
                    proserveId,
                    sentralId,
                    proserveColumn: mapping.proserveColumn,
                    sentralColumn: mapping.sentralColumn,
                    proserveValue: proserveValue || '',
                    sentralValue: sentralValue || ''
                });
                recordHasMismatch = true;
                totalMismatches++;
            }
        });

        if (recordHasMismatch) {
            failedCount++;
        } else {
            passedCount++;
        }
    });

    const summary = {
        totalRecords,
        passedCount,
        failedCount,
        totalMismatches
    };

    return { report, summary };
}

function saveReportToHTML(report: ParityReportEntry[], summary: any, filePath: string): void {
    const fs = require('fs');

    // Group report entries by record ID
    const groupedReports: { [key: string]: ParityReportEntry[] } = {};
    report.forEach(entry => {
        const recordId = entry.proserveId || entry.sentralId;
        if (!groupedReports[recordId]) {
            groupedReports[recordId] = [];
        }
        groupedReports[recordId].push(entry);
    });

    // Divide grouped reports into chunks of 100 entries
    const chunks = Object.entries(groupedReports).reduce((result, [recordId, entries], index) => {
        const chunkIndex = Math.floor(index / 1000);
        if (!result[chunkIndex]) result[chunkIndex] = [];
        result[chunkIndex].push([recordId, entries]);
        return result;
    }, [] as [string, ParityReportEntry[]][][]);

    let htmlContent = `
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Opportunity Object Migration Parity Report</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1, h2, h3 { color: #333; }
        .summary { margin-bottom: 30px; }
        .summary p { font-size: 16px; }
        .added, .removed, .modified { margin-bottom: 20px; }
        .added-item { color: green; }
        .removed-item { color: red; }
        table { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
        th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .old-value { background-color: #d4fcdc; }
        .new-value { background-color: #fcdcdc; }
        .toggle-button {
            background-color: #007BFF;
            color: white;
            padding: 5px 10px;
            border: none;
            cursor: pointer;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        .toggle-button:hover {
            background-color: #0056b3;
        }
        .object-summary, .batch-summary {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background-color: #eef;
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        .batch-details {
            display: none;
            margin-top: 10px;
            padding-left: 20px;
        }
    </style>
    <script>
        function toggleBatch(id) {
            const details = document.getElementById('batch-details-' + id);
            const button = document.getElementById('batch-button-' + id);
            if (details.style.display === 'none' || details.style.display === '') {
                details.style.display = 'block';
                button.innerText = 'Collapse Batch';
            } else {
                details.style.display = 'none';
                button.innerText = 'Expand Batch';
            }
        }
    </script>
</head>
<body>
    <h1>Opportunity Object Migration Parity Report</h1>
    
    <!-- Overall Summary -->
    <div class="summary">
        <h2>Overall Summary</h2>
        <p><strong>Total Records:</strong> ${summary.totalRecords}</p>
        <p><strong>Records with No Mismatches:</strong> ${summary.passedCount}</p>
        <p><strong>Records with Mismatches:</strong> ${summary.failedCount}</p>
        <p><strong>Total Mismatches:</strong> ${summary.totalMismatches}</p>
        <p><strong>Fields Compared per Record:</strong> ${71}</p>
    </div>`;

    // Generate HTML for each chunk of 100 records
    chunks.forEach((chunk, chunkIndex) => {
        htmlContent += `
    <div class="modified">
        <div class="batch-summary">
            <span><strong>Migrated Records - Batch ${chunkIndex + 1}</strong></span>
            <button class="toggle-button" id="batch-button-${chunkIndex + 1}" onclick="toggleBatch('${chunkIndex + 1}')">Expand Batch</button>
        </div>
        <div class="batch-details" id="batch-details-${chunkIndex + 1}">`;

        chunk.forEach(([recordId, entries]) => {
            const mismatchCount = entries.length;
            const proserveId = entries[0].proserveId;
            const sentralId = entries[0].sentralId;

            htmlContent += `
            <div class="object">
                <div class="object-summary">
                    <span><strong>Proserve ID:</strong> ${proserveId} | <strong>Sentral ID:</strong> ${sentralId}</span>
                    <span><strong>Fields with Mismatches:</strong> ${mismatchCount}</span>
                </div>
                <div class="object-details">
                    <h3>Record Level Metrics</h3>
                    <p>Total Fields with Differences: ${mismatchCount}</p>
                    <table>
                        <thead>
                            <tr>
                                <th>Proserve Column</th>
                                <th>Proserve Value</th>
                                <th>Sentral Column</th>
                                <th>Sentral Value</th>
                            </tr>
                        </thead>
                        <tbody>`;

            entries.forEach(entry => {
                htmlContent += `
                            <tr>
                                <td>${entry.proserveColumn}</td>
                                <td class="old-value">${entry.proserveValue || "null"}</td>
                                <td>${entry.sentralColumn}</td>
                                <td class="new-value">${entry.sentralValue || "null"}</td>
                            </tr>`;
            });

            htmlContent += `
                        </tbody>
                    </table>
                </div>
            </div>`;
        });

        htmlContent += `
        </div>
    </div>`;
    });

    htmlContent += `
</body>
</html>`;

    fs.writeFileSync(filePath, htmlContent);
    console.log(`HTML report saved to ${filePath}`);
}







async function checkParity() {
    try {
        const sentralFilePath = '/Users/svvemur/workspaces/AWSCRMTestAutomation/src/AWSCRMTestAutomation/SentralMerged.csv';
        const proserveFilePath = '/Users/svvemur/Downloads/ProserveQueriesAndData/ProserveMerged.csv';
        const sentralIdColumn = 'Id';
        const proserveIdColumn = 'Id';

        const recordIdMapping = await readFirstTwoColumns('/Users/svvemur/workspaces/AWSCRMTestAutomation/src/AWSCRMTestAutomation/output.csv');
        const sentralData = await parseCSV(sentralFilePath);
        const proserveData = await parseCSV(proserveFilePath);
        const mappings: Mapping[] = await generateFieldMappings("/Users/svvemur/workspaces/AWSCRMTestAutomation/src/AWSCRMTestAutomation/TO-BE Data model - Opportunity.csv");

        const { report, summary } = generateParityReport(sentralData, proserveData, mappings, recordIdMapping, sentralIdColumn, proserveIdColumn);

        if (report.length === 0) {
            console.log("Data is in parity, no mismatches found.");
        } else {
            console.log("Data Parity Report generated with mismatches found.");
            const reportFilePath = './parity_report.html';
            saveReportToHTML(report, summary, reportFilePath);
        }

    } catch (error) {
        console.error("Error processing files:", error);
    }
}

function generateFieldMappings(filePath: string): Promise<Mapping[]> {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf8', (err, data) => {
            if (err) {
                return reject(err);
            }

            const mappings: Mapping[] = [];
            const lines = data.split('\n');

            const headers = lines[0].split(',');
            const fieldNameIndex = headers.indexOf('Field Name');
            const fieldReplacedIndex = headers.indexOf('Field Replaced from ProServe instance');

            if (fieldNameIndex === -1 || fieldReplacedIndex === -1) {
                return reject(new Error('Required columns not found in CSV file.'));
            }

            for (let i = 1; i < lines.length; i++) {
                const columns = lines[i].split(',');

                if (columns[fieldReplacedIndex]) {
                    mappings.push({
                        sentralColumn: columns[fieldNameIndex].trim(),
                        proserveColumn: columns[fieldReplacedIndex].trim()
                    });
                }
            }

            resolve(mappings);
        });
    });
}

async function readFirstTwoColumns(filePath: string): Promise<[string, string][]> {
    return new Promise<[string, string][]>((resolve, reject) => {
        const results: [string, string][] = [];

        const readStream = fs.createReadStream(filePath, { encoding: 'utf8' });

        readStream.on('error', (err) => {
            reject(`Error reading the file: ${err.message}`);
        });

        const parser = csv()
            .on('headers', (headers: string[]) => {
                if (headers.length < 2) {
                    parser.destroy(new Error('CSV does not contain at least two columns.'));
                }
            })
            .on('data', (row: any) => {
                try {
                    const keys = Object.keys(row);
                    if (keys.length < 2) {
                        return;
                    }

                    const col1 = row[keys[0]].trim();
                    const col2 = row[keys[1]].trim();

                    results.push([col1, col2]);
                } catch (err) {
                }
            })
            .on('end', () => {
                resolve(results);
            })
            .on('error', (err: Error) => {
                reject(`Error processing the CSV: ${err.message}`);
            });

        readStream.pipe(parser);
    });
}

export default checkParity;