// --- FULL BROAD INFERENCE & PARTIAL DATA ENGINE --- // This block performs broad, forgiving inference from pasted text. // It fills partial data into the first unlocked row, appends to existing values, // creates color-coded notes, and stores unclassified snippets in Parser Flags. function setCellColor(cell, color) { cell.style.backgroundColor = color; } function appendIfEmpty(input, value) { if (!value) return; if (!input.value || input.value.trim() === "") input.value = value; else if (!input.value.includes(value)) input.value += " | " + value; } function appendNoteToCell(input, note, color) { appendIfEmpty(input, note); // mark the cell visually by color on the input's parent TD let td = input.parentElement; if (td) setCellColor(td, color); } function normalizeWhitespace(s) { return (s || "").replace(/\s+/g, " ").trim(); } function inferNameFromLine(line) { // Try to capture common name formats: Last, First M. or First M Last let m = line.match(/[A-Z][a-z]{2,}(?:,\s*[A-Z][a-z]{2,})?/g); if (m && m.length) return normalizeWhitespace(m[0]); // fallback: first two words let parts = line.trim().split(/\s+/); if (parts.length >= 2 && parts[0].length > 1 && parts[1].length > 1) return parts[0] + " " + parts[1]; return ""; } function inferEmail(line) { let m = line.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/i); return m ? m[0] : ""; } function inferPhone(line) { let m = line.match(/\+?\d?[\s-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/); return m ? m[0] : ""; } function inferDate(line) { // finds YYYY, or Month DD, or MM/DD or partial month-day let m = line.match(/\b(\d{4})\b/); if (m) return m[0]; m = line.match(/\b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s+\d{1,2}(?:,\s*\d{4})?\b/i); if (m) return m[0]; m = line.match(/\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/); if (m) return m[0]; return ""; } function inferAddress(line) { // simple heuristic for addresses let m = line.match(/\d{1,5}\s+[^,\n]+(?:,?\s*[A-Za-z]{2,})?\s*\d{5}?/); if (m) return m[0]; // street + city pattern m = line.match(/\d+\s+[A-Za-z0-9 .]+\s+(Street|St|Avenue|Ave|Rd|Road|Blvd|Boulevard|Lane|Ln|Court|Ct)\b/i); if (m) return m[0]; return ""; } function inferEmployerOrTitle(line) { // look for keywords often near titles/employers let m = line.match(/(Manager|Supervisor|Director|Engineer|Officer|Administrator|Representative|Counsel|Attorney|Worker|Tenant|Landlord|Clerk|HR|Human Resources)\b/i); if (m) return normalizeWhitespace(m[0]); m = line.match(/(?:Company|Inc|LLC|Corporation|Co\.|Corp\.|Association|Dept\.|Department)\b/i); if (m) return m[0]; return ""; } function inferCourt(line) { let m = line.match(/(District Court|Circuit Court|Superior Court|Magistrate|Family Court|Housing Court|Civil Court|Justice Court)/i); return m ? m[0] : ""; } function inferID(line) { let m = line.match(/\b(?:SSN|ID|DL|Driver\'s License|Passport|Employee#|Emp#|MRN)[:\s]*([A-Za-z0-9- ]{4,})\b/i); if (m) return (m[1] || "").trim(); m = line.match(/\b\d{3}-?\d{2}-?\d{4}\b/); // ssn-like if (m) return m[0]; return ""; } function addTimestamps(row) { let now = new Date().toISOString(); let cols = allColumns.slice(1); let inputs = row.querySelectorAll('input'); let createdIdx = cols.indexOf('Creation Timestamp'); let updatedIdx = cols.indexOf('Update Timestamp'); if (createdIdx >= 0 && inputs[createdIdx].value.trim() === '') inputs[createdIdx].value = now; if (updatedIdx >= 0) inputs[updatedIdx].value = now; } function broadInfer(row, text) { // Accepts multi-line text or single-line; will run several heuristics and write into cells. let cols = allColumns.slice(1); // skip LOCK let inputs = row.querySelectorAll('input'); let lines = String(text).split(/\n|;|\.|\t/).map(s => s.trim()).filter(Boolean); // Keep a running "confidence" note let confidence = 0; let flagged = false; lines.forEach(line => { if (!line) return; // try various inferences let name = inferNameFromLine(line); let email = inferEmail(line); let phone = inferPhone(line); let date = inferDate(line); let addr = inferAddress(line); let employer = inferEmployerOrTitle(line); let court = inferCourt(line); let id = inferID(line); // Map guesses to likely columns — addIfEmpty prevents overwriting existing appendIfEmpty(inputs[cols.indexOf('Full legal name')], name); appendIfEmpty(inputs[cols.indexOf('Personal email address')], email); appendIfEmpty(inputs[cols.indexOf('Primary phone number')], phone); appendIfEmpty(inputs[cols.indexOf('Date of birth')], date); appendIfEmpty(inputs[cols.indexOf('Home address')], addr); appendIfEmpty(inputs[cols.indexOf('Employer/organization name')], employer); appendIfEmpty(inputs[cols.indexOf('Assigned courthouse')], court); appendIfEmpty(inputs[cols.indexOf('Government ID number')], id); // Add to unclassified bucket let bucketIdx = cols.indexOf('Parser Flags'); if (bucketIdx >= 0) { let bucketInput = inputs[bucketIdx]; if (bucketInput) appendIfEmpty(bucketInput, line.substring(0, 200)); } // Notes and color coding behavior let notesIdx = cols.indexOf('Notes/remarks field'); let notesInput = notesIdx >= 0 ? inputs[notesIdx] : null; // heuristics for confidence if (email) confidence += 2; if (phone) confidence += 2; if (name) confidence += 1; if (addr) confidence += 1; if (id) confidence += 1; // if line contains 'missing', 'tbd', etc mark RED if (/missing|unknown|tbd|not provided|n\/a|no phone|no email/i.test(line)) { if (notesInput) appendNoteToCell(notesInput, '[RED: incomplete data] ' + line, '#ffd6d6'); flagged = true; } else if (/confirmed|verified|identified|on file|witnessed|signed/i.test(line)) { if (notesInput) appendNoteToCell(notesInput, '[GREEN: verified] ' + line, '#e6ffea'); } else { if (notesInput) appendNoteToCell(notesInput, '[BLUE: inferred] ' + line, '#e6f0ff'); } }); // After processing lines, add timestamps addTimestamps(row); // Write a confidence summary into Confidence Score col let confIdx = cols.indexOf('Confidence Score'); if (confIdx >= 0) { let out = inputs[confIdx]; if (out) out.value = Math.min(100, 10 * confidence) + '%'; } // If flagged (some red items), mark the row visually if (flagged) { row.style.border = '2px solid #ff8a8a'; } } // Hook into existing loadData logic — will attempt to fill rows iteratively. (function attachBroadLoad() { const prevLoad = window.loadData; window.loadData = function() { try { let text = document.getElementById('inputArea').value; if (!text) return prevLoad(); // If large input, treat entire text as one record first; then split lines to additional rows // Strategy: If text contains multiple 'person separators' (like blank lines) create multiple rows let blocks = text.split(/\n\s*\n/).map(b => b.trim()).filter(Boolean); if (blocks.length === 0) blocks = [text]; // For each block, find first unlocked row and infer blocks.forEach(block => { let row = getFirstUnlockedRow(); if (!row) { // no unlocked rows left: add new one addEmptyRow(); row = getFirstUnlockedRow(); } broadInfer(row, block); }); } catch (e) { console.error('Broad load error', e); // fallback to original behavior return prevLoad(); } }; })(); // Small UI helper: allow clicking a row to edit notes quickly and cycle note colors (function enableRowClickNotes() { tableBody.addEventListener('click', function(e) { let td = e.target.closest('td'); if (!td) return; let tr = td.parentElement; // double-click to cycle note color if cell is Notes/remarks if (td.cellIndex === allColumns.indexOf('Notes/remarks field')) { let colors = ['#ffffff', '#e6f0ff', '#ffd6d6', '#e6ffea']; let cur = td.style.backgroundColor || '#ffffff'; let idx = colors.indexOf(cur); idx = (idx + 1) % colors.length; td.style.backgroundColor = colors[idx]; } }); })(); // End of inference engine