← back to elcasillas__DealUpdates

Function bodies 106 total

All specs Real LLM only Function bodies
populateFilterDropdowns function · javascript · L1139-L1159 (21 LOC)
js/app.js
    function populateFilterDropdowns(deals) {
        // Populate owners
        const owners = [...new Set(deals.map(d => d.dealOwner).filter(Boolean))].sort();
        elements.filterOwner.innerHTML = '<option value="">All Owners</option>';
        owners.forEach(owner => {
            const option = document.createElement('option');
            option.value = owner;
            option.textContent = owner;
            elements.filterOwner.appendChild(option);
        });

        // Populate stages
        const stages = [...new Set(deals.map(d => d.stage).filter(Boolean))].sort();
        elements.filterStage.innerHTML = '<option value="">All Stages</option>';
        stages.forEach(stage => {
            const option = document.createElement('option');
            option.value = stage;
            option.textContent = stage;
            elements.filterStage.appendChild(option);
        });
    }
escapeCSVField function · javascript · L1162-L1168 (7 LOC)
js/app.js
    function escapeCSVField(value) {
        const str = String(value == null ? '' : value);
        if (str.includes(',') || str.includes('"') || str.includes('\n')) {
            return '"' + str.replace(/"/g, '""') + '"';
        }
        return str;
    }
exportToCSV function · javascript · L1170-L1195 (26 LOC)
js/app.js
    function exportToCSV() {
        const headers = ['Deal Owner', 'Deal Name', 'Stage', 'ACV (CAD)', 'Closing Date', 'Modified Date', 'Days Since', 'Health Score', 'Notes Summary', 'Notes'];
        const rows = filteredDeals.map(deal => [
            escapeCSVField(deal.dealOwner),
            escapeCSVField(deal.dealName),
            escapeCSVField(deal.stage),
            escapeCSVField(deal.acv),
            escapeCSVField(deal.closingDate ? formatDate(deal.closingDate) : ''),
            escapeCSVField(deal.modifiedDate ? formatDate(deal.modifiedDate) : ''),
            escapeCSVField(deal.daysSince),
            escapeCSVField(deal.healthScore != null ? deal.healthScore : ''),
            escapeCSVField(deal.notesSummary || ''),
            escapeCSVField(deal.noteContent)
        ].join(','));

        const csv = [headers.join(','), ...rows].join('\r\n');
        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const url = URL.createObjectURL(blob);
updateRowCount function · javascript · L1197-L1212 (16 LOC)
js/app.js
    function updateRowCount() {
        const total = allDeals.length;
        const shown = filteredDeals.length;
        const hasFilters = elements.searchInput.value ||
            elements.filterOwner.value ||
            elements.filterStage.value ||
            elements.filterUrgency.value ||
            elements.filterHealth.value ||
            elements.filterChanges.value;

        if (hasFilters) {
            elements.rowCount.textContent = `Showing ${shown} of ${total} deals`;
        } else {
            elements.rowCount.textContent = `Showing all ${total} deals`;
        }
    }
renderChangesSummary function · javascript · L1214-L1237 (24 LOC)
js/app.js
    function renderChangesSummary() {
        if (!changesSummary) {
            elements.changesSummaryEl.classList.add('hidden');
            elements.filterChangesGroup.classList.add('hidden');
            return;
        }

        const { newCount, updatedCount, removedCount, unchangedCount } = changesSummary;
        elements.changesSummaryEl.innerHTML = `
            <span class="font-semibold text-slate-700">Changes detected:</span>
            <span class="flex items-center gap-3 flex-wrap">
                <span class="font-semibold text-green-700">${newCount} new</span>
                <span class="font-semibold text-blue-600">${updatedCount} updated</span>
                <span class="font-semibold text-red-600">${removedCount} removed</span>
                <span class="text-slate-500">${unchangedCount} unchanged</span>
            </span>
            <button class="ml-auto text-xs text-slate-500 hover:text-slate-800 px-2 py-1 rounded hover:bg-slate-100" id="dismiss-change
clearChanges function · javascript · L1239-L1248 (10 LOC)
js/app.js
    function clearChanges() {
        changesSummary = null;
        for (const deal of allDeals) {
            deal.changeType = null;
            deal.changes = [];
        }
        elements.filterChanges.value = '';
        renderChangesSummary();
        applyFilters();
    }
showDashboard function · javascript · L1250-L1258 (9 LOC)
js/app.js
    function showDashboard() {
        elements.uploadZone.classList.add('hidden');
        elements.statsSection.classList.remove('hidden');
        elements.datePickerSection.classList.remove('hidden');
        elements.filtersSection.classList.remove('hidden');
        elements.reuploadZone.classList.remove('hidden');
        elements.rowCount.classList.remove('hidden');
        elements.tableSection.classList.remove('hidden');
    }
Source: Repobility analyzer · https://repobility.com
showUploadZone function · javascript · L1260-L1268 (9 LOC)
js/app.js
    function showUploadZone() {
        elements.uploadZone.classList.remove('hidden');
        elements.statsSection.classList.add('hidden');
        elements.datePickerSection.classList.add('hidden');
        elements.filtersSection.classList.add('hidden');
        elements.reuploadZone.classList.add('hidden');
        elements.rowCount.classList.add('hidden');
        elements.tableSection.classList.add('hidden');
    }
resetFilters function · javascript · L1271-L1279 (9 LOC)
js/app.js
    function resetFilters() {
        elements.searchInput.value = '';
        elements.filterOwner.value = '';
        elements.filterStage.value = '';
        elements.filterUrgency.value = '';
        elements.filterHealth.value = '';
        elements.filterChanges.value = '';
        applyFilters();
    }
applyFilters function · javascript · L1281-L1336 (56 LOC)
js/app.js
    function applyFilters() {
        const searchTerm = elements.searchInput.value.toLowerCase();
        const ownerFilter = elements.filterOwner.value;
        const stageFilter = elements.filterStage.value;
        const urgencyFilter = elements.filterUrgency.value;
        const healthFilter = elements.filterHealth.value;
        const changesFilter = elements.filterChanges.value;

        filteredDeals = allDeals.filter(deal => {
            // Search across all fields
            if (searchTerm) {
                const searchableText = [
                    deal.dealOwner,
                    deal.dealName,
                    deal.stage,
                    deal.noteContent
                ].join(' ').toLowerCase();

                if (!searchableText.includes(searchTerm)) {
                    return false;
                }
            }

            // Owner filter
            if (ownerFilter && deal.dealOwner !== ownerFilter) {
                return false;
            }

applySorting function · javascript · L1338-L1363 (26 LOC)
js/app.js
    function applySorting() {
        const { column, direction } = currentSort;

        filteredDeals.sort((a, b) => {
            let valA = a[column];
            let valB = b[column];

            // Handle dates
            if (valA instanceof Date) valA = valA ? valA.getTime() : 0;
            if (valB instanceof Date) valB = valB ? valB.getTime() : 0;

            // Handle strings
            if (typeof valA === 'string') valA = valA.toLowerCase();
            if (typeof valB === 'string') valB = valB.toLowerCase();

            // Handle nulls
            if (valA == null) valA = '';
            if (valB == null) valB = '';

            let comparison = 0;
            if (valA < valB) comparison = -1;
            if (valA > valB) comparison = 1;

            return direction === 'asc' ? comparison : -comparison;
        });
    }
handleSort function · javascript · L1365-L1375 (11 LOC)
js/app.js
    function handleSort(column) {
        if (currentSort.column === column) {
            currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
        } else {
            currentSort.column = column;
            currentSort.direction = 'asc';
        }

        updateSortIndicators();
        applyFilters();
    }
updateSortIndicators function · javascript · L1377-L1387 (11 LOC)
js/app.js
    function updateSortIndicators() {
        document.querySelectorAll('th[data-sort]').forEach(th => {
            th.classList.remove('sort-asc', 'sort-desc');
            if (th.dataset.sort === currentSort.column) {
                th.classList.add(`sort-${currentSort.direction}`);
                th.setAttribute('aria-sort', currentSort.direction === 'asc' ? 'ascending' : 'descending');
            } else {
                th.setAttribute('aria-sort', 'none');
            }
        });
    }
handleFile function · javascript · L1390-L1401 (12 LOC)
js/app.js
    function handleFile(file) {
        if (!file.name.endsWith('.csv')) {
            alert('Please upload a CSV file.');
            return;
        }

        const reader = new FileReader();
        reader.onload = function(e) {
            processCSVData(e.target.result, file.name);
        };
        reader.readAsText(file);
    }
runIngestWorker function · javascript · L1406-L1434 (29 LOC)
js/app.js
    function runIngestWorker(csvText, existingDeals) {
        return new Promise((resolve, reject) => {
            const worker = new Worker('js/ingest-worker.js');
            activeWorker = worker;

            worker.onmessage = function(e) {
                const msg = e.data;
                if (msg.type === 'progress') {
                    showLoading(msg.phase);
                } else if (msg.type === 'complete') {
                    activeWorker = null;
                    worker.terminate();
                    resolve({ deals: msg.deals, generatedDate: msg.generatedDate });
                } else if (msg.type === 'error') {
                    activeWorker = null;
                    worker.terminate();
                    reject(new Error(msg.message));
                }
            };

            worker.onerror = function(err) {
                activeWorker = null;
                worker.terminate();
                reject(new Error(err.message || 'Worker error'));
   
Repobility analyzer · published findings · https://repobility.com
processCSVData function · javascript · L1436-L1517 (82 LOC)
js/app.js
    async function processCSVData(csvText, filename) {
        showLoading('Processing...');
        try {
            // Run parse + process + validate + dedup in Web Worker
            const { deals: workerDeals, generatedDate: genDateISO } = await runIngestWorker(csvText, allDeals);

            // Determine upload date
            const uploadDate = genDateISO
                ? genDateISO.slice(0, 10)
                : new Date().toISOString().slice(0, 10);

            console.log('Upload date:', uploadDate);

            // Apply AI summaries on main thread (needs supabaseClient)
            showLoading('Generating summaries...');
            let processed = await applyAISummaries(workerDeals, allDeals, generateAISummaries);
            console.log(`After worker + AI: ${processed.length} deals`);

            // Compute health scores
            attachHealthScores(processed);

            if (processed.length === 0) {
                alert('No valid CAD deals found in the CSV fil
openScoringModal function · javascript · L1529-L1565 (37 LOC)
js/app.js
    function openScoringModal() {
        const config = loadScoringConfig() || {};
        const weights = config.weights || defaultWeights();
        const stageMap = config.stageScoreMap || { ...DEFAULT_STAGE_SCORES };
        const posKw = config.positiveKeywords || [...POSITIVE_KEYWORDS];
        const negKw = config.negativeKeywords || [...NEGATIVE_KEYWORDS];

        // Populate weights grid
        const grid = document.getElementById('scoring-weights-grid');
        grid.innerHTML = '';
        const weightKeys = Object.keys(defaultWeights());
        for (const key of weightKeys) {
            const item = document.createElement('div');
            item.className = 'scoring-weight-item';
            item.innerHTML = '<label>' + WEIGHT_LABELS[key] + '</label>' +
                '<input type="number" min="0" max="100" data-weight-key="' + key + '" value="' + (weights[key] || 0) + '">';
            grid.appendChild(item);
        }
        updateWeightTotal();

        // Popula
closeScoringModal function · javascript · L1567-L1569 (3 LOC)
js/app.js
    function closeScoringModal() {
        document.getElementById('scoring-modal').classList.add('hidden');
    }
addStageRow function · javascript · L1571-L1579 (9 LOC)
js/app.js
    function addStageRow(container, stageName, stageScore) {
        const row = document.createElement('div');
        row.className = 'scoring-stage-row';
        row.innerHTML = '<input type="text" placeholder="Stage name" value="' + (stageName || '') + '">' +
            '<input type="number" min="0" max="100" placeholder="0" value="' + (stageScore != null ? stageScore : '') + '">' +
            '<button class="scoring-stage-remove" title="Remove">&times;</button>';
        row.querySelector('.scoring-stage-remove').addEventListener('click', () => row.remove());
        container.appendChild(row);
    }
updateWeightTotal function · javascript · L1581-L1588 (8 LOC)
js/app.js
    function updateWeightTotal() {
        const inputs = document.querySelectorAll('#scoring-weights-grid input[data-weight-key]');
        let total = 0;
        inputs.forEach(inp => { total += parseFloat(inp.value) || 0; });
        const el = document.getElementById('scoring-weight-total');
        el.textContent = '(' + total + ')';
        el.classList.toggle('scoring-weight-total--invalid', total !== 100);
    }
saveScoringSettings function · javascript · L1590-L1655 (66 LOC)
js/app.js
    function saveScoringSettings() {
        const validation = document.getElementById('scoring-validation');
        validation.textContent = '';

        // Read weights
        const inputs = document.querySelectorAll('#scoring-weights-grid input[data-weight-key]');
        const weights = {};
        let total = 0;
        inputs.forEach(inp => {
            const val = parseFloat(inp.value) || 0;
            weights[inp.dataset.weightKey] = val;
            total += val;
        });

        // Auto-normalize if not summing to 100
        if (total > 0 && total !== 100) {
            const factor = 100 / total;
            for (const key of Object.keys(weights)) {
                weights[key] = Math.round(weights[key] * factor * 100) / 100;
            }
            // Fix rounding: adjust largest weight
            let roundedTotal = Object.values(weights).reduce((a, b) => a + b, 0);
            if (roundedTotal !== 100) {
                const maxKey = Object.keys(weights).redu
resetScoringDefaults function · javascript · L1657-L1667 (11 LOC)
js/app.js
    function resetScoringDefaults() {
        localStorage.removeItem(SCORING_CONFIG_KEY);
        // Recompute with defaults if deals are loaded
        if (allDeals.length > 0) {
            attachHealthScores(allDeals);
            filteredDeals = [...allDeals];
            applyFilters();
            renderStats(allDeals);
        }
        closeScoringModal();
    }
setupEventListeners function · javascript · L1670-L1842 (173 LOC)
js/app.js
    function setupEventListeners() {
        // File input
        elements.fileInput.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                handleFile(e.target.files[0]);
            }
        });

        // Drag and drop
        elements.uploadZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            elements.uploadZone.classList.add('drag-over');
        });

        elements.uploadZone.addEventListener('dragleave', () => {
            elements.uploadZone.classList.remove('drag-over');
        });

        elements.uploadZone.addEventListener('drop', (e) => {
            e.preventDefault();
            elements.uploadZone.classList.remove('drag-over');
            if (e.dataTransfer.files.length > 0) {
                handleFile(e.dataTransfer.files[0]);
            }
        });

        // Click to upload
        elements.uploadZone.addEventListener('click', (e) => {
            if (e.target !== elements.fileI
Repobility — same analyzer, your code, free for public repos · /scan/
init function · javascript · L1845-L1883 (39 LOC)
js/app.js
    async function init() {
        checkSchemaVersion();
        setupEventListeners();
        initSupabase();

        if (window.UI_ONLY) {
            if (elements.datePickerSection) elements.datePickerSection.classList.add('hidden');
            var badge = document.getElementById('ui-only-badge');
            if (badge) badge.classList.remove('hidden');
        }

        // Show Manage Contacts button only when online
        document.getElementById('manage-contacts-btn').classList.toggle('hidden', !isOnline);

        if (isOnline) {
            try {
                showLoading();
                await loadOwnerContacts();
                await populateDatePicker();

                // Auto-select the most recent upload
                const primarySelect = elements.dateSelectPrimary;
                if (primarySelect.options.length > 1) {
                    primarySelect.selectedIndex = 1;
                    await handleDateSelection();
                } else {
           
loadFromLocalStorageFallback function · javascript · L1885-L1900 (16 LOC)
js/app.js
    function loadFromLocalStorageFallback() {
        populateDatePicker(); // Shows disabled state when offline
        const savedDeals = loadFromStorage();
        if (savedDeals && savedDeals.length > 0) {
            attachHealthScores(savedDeals);
            allDeals = savedDeals;
            filteredDeals = [...allDeals];
            populateFilterDropdowns(allDeals);
            renderStats(allDeals);
            applySorting();
            renderTable(filteredDeals);
            updateSortIndicators();
            updateRowCount();
            showDashboard();
        }
    }
defaultWeights function · javascript · L10-L19 (10 LOC)
js/dealHealthScore.js
    function defaultWeights() {
        return {
            stageProbability: 25,
            velocity: 20,
            activityRecency: 15,
            closeDateIntegrity: 10,
            acv: 15,
            notesSignal: 15
        };
    }
scoreStageProbability function · javascript · L63-L68 (6 LOC)
js/dealHealthScore.js
    function scoreStageProbability(stage, stageScoreMap) {
        if (!stage) return 35;
        const map = stageScoreMap || DEFAULT_STAGE_SCORES;
        const key = stage.trim().toLowerCase();
        return (key in map) ? map[key] : 35;
    }
scoreVelocity function · javascript · L70-L77 (8 LOC)
js/dealHealthScore.js
    function scoreVelocity(daysInStage, benchmarkDays) {
        if (daysInStage == null || benchmarkDays == null || benchmarkDays <= 0) return 70;
        var ratio = daysInStage / benchmarkDays;
        if (ratio <= 0.8) return 100;
        if (ratio <= 1.2) return 70;
        if (ratio <= 1.5) return 40;
        return 10;
    }
scoreActivityRecency function · javascript · L79-L85 (7 LOC)
js/dealHealthScore.js
    function scoreActivityRecency(lastActivityDaysSince) {
        if (lastActivityDaysSince == null || isNaN(lastActivityDaysSince) || lastActivityDaysSince >= 999) return 40;
        if (lastActivityDaysSince <= 7) return 100;
        if (lastActivityDaysSince <= 14) return 70;
        if (lastActivityDaysSince <= 30) return 40;
        return 10;
    }
scoreCloseDateIntegrity function · javascript · L87-L120 (34 LOC)
js/dealHealthScore.js
    function scoreCloseDateIntegrity(deal) {
        if (!deal) return 60;

        var base;
        var daysUntil = deal.daysUntilClosing;

        if (daysUntil == null) {
            base = 60;
        } else if (daysUntil < 0) {
            // Past due and not closed won
            var stageLower = (deal.stage || '').trim().toLowerCase();
            if (stageLower === 'closed won') {
                base = 100;
            } else {
                base = 10;
            }
        } else if (daysUntil <= 30) {
            base = 70;
        } else {
            base = 100;
        }

        // Scan notes for push signals
        var text = ((deal.notesCanonical || '') + ' ' + (deal.noteContent || '')).toLowerCase();
        var pushCount = 0;
        for (var i = 0; i < PUSH_SIGNALS.length; i++) {
            if (text.indexOf(PUSH_SIGNALS[i]) !== -1) {
                pushCount++;
            }
        }

        base -= pushCount * 20;
        return Math.max(10, Math.min(100, 
scoreAcv function · javascript · L122-L137 (16 LOC)
js/dealHealthScore.js
    function scoreAcv(acv, acvDistribution) {
        if (acv == null || isNaN(acv) || acv === 0) return 40;
        if (!acvDistribution || acvDistribution.length === 0) return 40;

        // Compute percentile rank
        var below = 0;
        for (var i = 0; i < acvDistribution.length; i++) {
            if (acvDistribution[i] < acv) below++;
        }
        var percentile = below / acvDistribution.length;

        // Top 20% => 100, 20-60% => 70, bottom 40% => 40
        if (percentile >= 0.8) return 100;
        if (percentile >= 0.4) return 70;
        return 40;
    }
Powered by Repobility — scan your code at https://repobility.com
scoreNotesSignal function · javascript · L139-L165 (27 LOC)
js/dealHealthScore.js
    function scoreNotesSignal(noteContent, notesCanonical, posKw, negKw) {
        var text = ((noteContent || '') + ' ' + (notesCanonical || '')).toLowerCase();
        var score = 50;
        var positiveMatched = [];
        var negativeMatched = [];
        var posWords = posKw || POSITIVE_KEYWORDS;
        var negWords = negKw || NEGATIVE_KEYWORDS;

        for (var i = 0; i < posWords.length; i++) {
            if (text.indexOf(posWords[i].toLowerCase()) !== -1) {
                positiveMatched.push(posWords[i]);
                score += 10;
            }
        }
        for (var i = 0; i < negWords.length; i++) {
            if (text.indexOf(negWords[i].toLowerCase()) !== -1) {
                negativeMatched.push(negWords[i]);
                score -= 10;
            }
        }

        return {
            score: Math.max(0, Math.min(100, score)),
            positive: positiveMatched,
            negative: negativeMatched
        };
    }
buildContext function · javascript · L169-L220 (52 LOC)
js/dealHealthScore.js
    function buildContext(deals, options) {
        var now = (options && options.now) ? options.now : Date.now();

        if (!deals || deals.length === 0) {
            return { now: now, acvDistribution: [], stageBenchmarks: STAGE_BENCHMARKS };
        }

        // ACV distribution: sorted array of all non-zero ACV values
        var acvValues = [];
        for (var i = 0; i < deals.length; i++) {
            var acv = deals[i].acv;
            if (acv != null && !isNaN(acv) && acv > 0) {
                acvValues.push(acv);
            }
        }
        acvValues.sort(function(a, b) { return a - b; });

        // Dataset median days-in-stage by stage (approximated from daysSince)
        var stageGroups = {};
        for (var i = 0; i < deals.length; i++) {
            var stage = (deals[i].stage || '').trim().toLowerCase();
            if (!stage) continue;
            var ds = deals[i].daysSince;
            if (ds == null || isNaN(ds) || ds >= 999) continue;
            if 
deriveDealMetrics function · javascript · L224-L264 (41 LOC)
js/dealHealthScore.js
    function deriveDealMetrics(deal, ctx) {
        var now = ctx.now || Date.now();

        // Last activity date: max of modifiedDate (which is "Modified Time (Notes)")
        // In the current data model these are the same field; this picks the
        // most recent date available per deal to be forward-compatible.
        var lastActivityDate = null;
        if (deal.modifiedDate) {
            lastActivityDate = deal.modifiedDate instanceof Date
                ? deal.modifiedDate : new Date(deal.modifiedDate);
        }

        var lastActivityDaysSince;
        if (lastActivityDate && !isNaN(lastActivityDate.getTime())) {
            var todayMidnight = new Date(now);
            todayMidnight.setHours(0, 0, 0, 0);
            lastActivityDaysSince = Math.max(0, Math.floor((todayMidnight - lastActivityDate) / MS_PER_DAY));
        } else {
            lastActivityDaysSince = 999;
        }

        // Days in stage: approximate from earliest record at current stage.
        
computeDealHealthScore function · javascript · L268-L338 (71 LOC)
js/dealHealthScore.js
    function computeDealHealthScore(deal, context, config) {
        var weights = (config && config.weights) || defaultWeights();
        var stageScoreMap = (config && config.stageScoreMap) || DEFAULT_STAGE_SCORES;
        var ctx = context || { now: Date.now(), acvDistribution: [], stageBenchmarks: STAGE_BENCHMARKS };

        // Derive per-deal metrics
        var metrics = deriveDealMetrics(deal, ctx);

        // Compute each component
        var stageProbability = scoreStageProbability(deal.stage, stageScoreMap);
        var velocity = scoreVelocity(metrics.daysInStage, metrics.stageBenchmark);
        var activityRecency = scoreActivityRecency(metrics.lastActivityDaysSince);
        var closeDateIntegrity = scoreCloseDateIntegrity(deal);
        var acvScore = scoreAcv(deal.acv, ctx.acvDistribution);
        var posKw = (config && config.positiveKeywords) || null;
        var negKw = (config && config.negativeKeywords) || null;
        var notesResult = scoreNotesSignal(deal.n
normalizeString function · javascript · L16-L18 (3 LOC)
js/domain.js
    function normalizeString(s) {
        return (s || '').trim().toLowerCase().replace(/\s+/g, ' ');
    }
makeDealKey function · javascript · L20-L22 (3 LOC)
js/domain.js
    function makeDealKey(dealName, dealOwner) {
        return normalizeString(dealName) + '||' + normalizeString(dealOwner);
    }
sha256Hex function · javascript · L25-L29 (5 LOC)
js/domain.js
    async function sha256Hex(str) {
        const data = new TextEncoder().encode(str);
        const buf = await crypto.subtle.digest('SHA-256', data);
        return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
    }
buildNotesCanonical function · javascript · L32-L35 (4 LOC)
js/domain.js
    function buildNotesCanonical(rawNotes) {
        const unique = [...new Set(rawNotes.map(n => n.trim()).filter(Boolean))].sort();
        return { canonical: unique.join('\n---\n'), count: unique.length };
    }
Source: Repobility analyzer · https://repobility.com
parseACV function · javascript · L50-L102 (53 LOC)
js/domain.js
    function parseACV(value) {
        if (!value || typeof value !== 'string') {
            return { value: 0, currency: 'CAD', isCAD: true, raw: value ?? '' };
        }

        const raw = value;
        const upper = value.trim().toUpperCase();

        // Detect currency
        let currency;
        if (upper.includes('USD') || /^US\$/.test(upper)) {
            currency = 'USD';
        } else if (upper.includes('EUR') || /^€/.test(upper.replace(/\s/g, ''))) {
            currency = 'EUR';
        } else if (upper.includes('CAD') || /CA\$|C\$/.test(upper)) {
            currency = 'CAD';
        } else {
            // Bare "$" or plain number — treat as CAD (our CRM default)
            currency = 'CAD';
        }

        const isCAD = currency === 'CAD';

        // Detect parenthesised negative: ($1,234.56) or (1234)
        const isNegative = /\(.*\)/.test(upper);

        // Strip everything except digits, dots, and minus signs
        let numeric = upper.replace(/[^0-9.
parseDate function · javascript · L105-L114 (10 LOC)
js/domain.js
    function parseDate(dateStr) {
        if (!dateStr) return null;

        // Try parsing the date string
        const date = new Date(dateStr);
        if (isNaN(date.getTime())) return null;

        // Return date only (strip time)
        return new Date(date.getFullYear(), date.getMonth(), date.getDate());
    }
calculateDaysSince function · javascript · L116-L126 (11 LOC)
js/domain.js
    function calculateDaysSince(date, referenceDate) {
        if (!date) return 999; // High number for unknown dates

        const today = referenceDate ? new Date(referenceDate) : new Date();
        today.setHours(0, 0, 0, 0);

        const diffTime = today - date;
        const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));

        return Math.max(0, diffDays);
    }
calculateDaysUntilClosing function · javascript · L128-L134 (7 LOC)
js/domain.js
    function calculateDaysUntilClosing(date, referenceDate) {
        if (!date) return null;
        const today = referenceDate ? new Date(referenceDate) : new Date();
        today.setHours(0, 0, 0, 0);
        const diffTime = date - today;
        return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    }
getUrgencyLevel function · javascript · L137-L142 (6 LOC)
js/domain.js
    function getUrgencyLevel(days) {
        if (days <= URGENCY_THRESHOLDS.fresh) return 'fresh';
        if (days <= URGENCY_THRESHOLDS.warning) return 'warning';
        if (days <= URGENCY_THRESHOLDS.stale) return 'stale';
        return 'critical';
    }
getClosingStatus function · javascript · L144-L149 (6 LOC)
js/domain.js
    function getClosingStatus(daysUntil) {
        if (daysUntil === null) return null;
        if (daysUntil < 0) return 'overdue';
        if (daysUntil <= CLOSING_SOON_DAYS) return 'soon';
        return 'normal';
    }
getHealthLevel function · javascript · L151-L156 (6 LOC)
js/domain.js
    function getHealthLevel(score) {
        if (score >= 80) return 'good';
        if (score >= 60) return 'watch';
        if (score >= 40) return 'risk';
        return 'dead';
    }
stripHTML function · javascript · L29-L36 (8 LOC)
js/ingest.js
    function stripHTML(html) {
        if (!html) return '';
        if (typeof DOMParser !== 'undefined') {
            const doc = new DOMParser().parseFromString(html, 'text/html');
            return doc.body.textContent || '';
        }
        return html.replace(/<[^>]*>/g, '');
    }
Repobility analyzer · published findings · https://repobility.com
formatCurrency function · javascript · L38-L45 (8 LOC)
js/ingest.js
    function formatCurrency(value) {
        return new Intl.NumberFormat('en-CA', {
            style: 'currency',
            currency: 'CAD',
            minimumFractionDigits: 0,
            maximumFractionDigits: 0
        }).format(value);
    }
parseCSV function · javascript · L48-L139 (92 LOC)
js/ingest.js
    function parseCSV(text) {
        // Remove BOM if present
        if (text.charCodeAt(0) === 0xFEFF) {
            text = text.slice(1);
        }

        // Parse all rows handling multiline quoted fields
        const allRows = parseCSVText(text);

        console.log('Total rows parsed:', allRows.length);
        console.log('First 3 rows:', allRows.slice(0, 3));

        if (allRows.length === 0) {
            throw new Error('No data found in CSV file.');
        }

        // Find header row and extract "Generated by" date
        let headerRowIndex = -1;
        let headers = [];
        let generatedDate = null;

        for (let i = 0; i < Math.min(allRows.length, 20); i++) {
            const row = allRows[i];

            // Check for "Generated by" row before finding header
            if (headerRowIndex === -1) {
                const joinedRow = row.join(' ').trim();
                if (/generated\s+by/i.test(joinedRow)) {
                    // Try to extract a dat
parseCSVText function · javascript · L142-L206 (65 LOC)
js/ingest.js
    function parseCSVText(text) {
        const rows = [];
        let currentRow = [];
        let currentField = '';
        let inQuotes = false;

        for (let i = 0; i < text.length; i++) {
            const char = text[i];
            const nextChar = text[i + 1];

            if (inQuotes) {
                // Inside a quoted field
                if (char === '"') {
                    if (nextChar === '"') {
                        // Escaped quote ("") - add single quote and skip next
                        currentField += '"';
                        i++;
                    } else {
                        // End of quoted field
                        inQuotes = false;
                    }
                } else {
                    // Any character inside quotes (including newlines) is part of the field
                    currentField += char;
                }
            } else {
                // Outside quotes
                if (char === '"') {
              
‹ prevpage 2 / 3next ›