Function bodies 106 total
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-changeclearChanges 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 filopenScoringModal 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();
// PopulacloseScoringModal 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">×</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).reduresetScoringDefaults 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.fileIRepobility — 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.nnormalizeString 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 datparseCSVText 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 === '"') {