{% extends "base.html" %} {% block title %}Jakość danych - Admin{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

Jakość danych firm

Przegląd kompletności i jakości danych {{ total }} firm w katalogu

Stan na {{ now|local_time('%d.%m.%Y, %H:%M') }}
{{ total }}
Firm w katalogu
{{ avg_score }}%
Średnia kompletność
{{ quality_dist.get('complete', 0) }}
Kompletnych (67%+)
{{ quality_dist.get('basic', 0) }}
Podstawowych (<34%)
Pokrycie danych per pole
{% for field_name, stats in field_stats.items() %}
{{ field_name }}
{{ stats.pct }}%
{{ stats.count }}/{{ total }}
{% endfor %}
Rozkład jakości danych
{{ quality_dist.get('basic', 0) }}
Podstawowe (<34%)
{{ quality_dist.get('enhanced', 0) }}
Rozszerzone (34-66%)
{{ quality_dist.get('complete', 0) }}
Kompletne (67%+)
{{ score_dist.get('0-25', 0) }}
0-25%
{{ score_dist.get('26-50', 0) }}
26-50%
{{ score_dist.get('51-75', 0) }}
51-75%
{{ score_dist.get('76-100', 0) }}
76-100%
{% if available_data %}
Dane gotowe do uzupełnienia ({{ available_data|length }})

Poniższe dane zostały znalezione w Google Business Profile, ale nie są jeszcze w profilu firmy.

{% for item in available_data %} {% endfor %}
Firma Pole Źródło Wartość Akcja
{{ item.company_name }} {{ item.field }} {{ item.source }} {% if item.google_name and item.google_name.lower() != item.company_name.lower() %}
Profil: {{ item.google_name[:40] }} {% endif %}
{{ item.value[:50] }}
{% endif %}
Odkryte strony WWW {% if discovery_data %} ({{ discovery_data|length }} kandydatów) {% endif %}
{% if companies_without_website > 0 %} {{ companies_without_website }} firm bez WWW {% endif %}
{% if discovery_data %} {% for d in discovery_data %} {% if d.snippet %} {% endif %} {% endfor %}
Firma Strona Dopasowania Akcja
{{ d.company_name }} {% if d.brave_description %}
{{ d.brave_description }} {% endif %}
{{ d.domain }} {% if d.title %}
{{ d.title[:80] }} {% endif %}
{% if d.has_nip %} NIP {% endif %} {% if d.has_regon %} REGON {% endif %} {% if d.has_krs %} KRS {% endif %} {% if d.has_phone %} Tel {% endif %} {% if d.has_email %} Email {% endif %} {% if d.has_city %} Miasto {% endif %} {% if d.has_owner %} Właściciel {% endif %} Domena {% if d.match_geo == 'wejherowo' %} Wejherowo {% elif d.match_geo == 'powiat' %} Powiat {% elif d.match_geo == 'pomorskie' %} Pomorskie {% else %} Lokalizacja {% endif %}
{{ d.snippet }}
{% else %}

Brak kandydatów. Kliknij "Szukaj WWW" aby uruchomić wyszukiwanie.

{% endif %} {% if rejected_companies %}
Odrzucone przez admina ({{ rejected_companies|length }} firm):
{% for rc in rejected_companies %}
{{ rc.company_name }} {% if rc.domains %} {{ rc.domains|join(', ') }} {% endif %}
{% endfor %}
Odrzucone domeny nie pojawią się ponownie w propozycjach.
{% endif %}
Firmy wg kompletności danych
0 zaznaczonych
Filtr pola: — firmy bez tego pola
Pokazano {{ companies_table|length }} z {{ total }} firm
{% for c in companies_table %} {% endfor %}
Firma Score Pola Kompletność Jakość Akcje
{{ c.name }} {{ c.score }}% {% if c.registry_stale %} Dane stare {% endif %} {{ c.filled }}/{{ c.total }}
{% for fname, fval in c.fields.items() %} {% endfor %}
{% if c.label == 'basic' %}Podstawowe{% elif c.label == 'enhanced' %}Rozszerzone{% else %}Kompletne{% endif %}
{# 1. Rejestr #} {% if not c.fields['Dane urzędowe'] and c.nip %} {% else %} {% endif %} {# 2. Szukaj WWW #} {% if not c.website %} {% else %} {% endif %} {# 3. SEO #} {% if not c.fields['Audyt SEO'] and c.website %} {% else %} {% endif %} {# 4. Social #} {% if not c.fields['Audyt Social'] %} {% else %} {% endif %} {# 5. GBP #} {% if not c.fields['Audyt GBP'] %} {% else %} {% endif %} {# 6. Logo #} {% if not c.fields['Logo'] and c.website %} {% else %} {% endif %}
{% endblock %} {% block extra_js %} // Data Quality Dashboard JS function filterTable() { applyFilters(); } function sortTable(colIdx) { var table = document.getElementById('companiesTable'); var tbody = table.querySelector('tbody'); var rows = Array.from(tbody.querySelectorAll('tr')); var asc = table.dataset.sortCol == colIdx && table.dataset.sortDir !== 'asc'; table.dataset.sortCol = colIdx; table.dataset.sortDir = asc ? 'asc' : 'desc'; rows.sort(function(a, b) { var aVal = a.cells[colIdx].textContent.trim().replace('%', ''); var bVal = b.cells[colIdx].textContent.trim().replace('%', ''); var aNum = parseFloat(aVal); var bNum = parseFloat(bVal); if (!isNaN(aNum) && !isNaN(bNum)) { return asc ? aNum - bNum : bNum - aNum; } return asc ? aVal.localeCompare(bVal, 'pl') : bVal.localeCompare(aVal, 'pl'); }); rows.forEach(function(row) { tbody.appendChild(row); }); } // Checkbox selection function toggleSelectAll() { var checked = document.getElementById('selectAll').checked; document.querySelectorAll('.company-cb').forEach(function(cb) { var row = cb.closest('tr'); if (row.style.display !== 'none') { cb.checked = checked; } }); updateBulkBar(); } document.addEventListener('change', function(e) { if (e.target.classList.contains('company-cb')) { updateBulkBar(); } }); function updateBulkBar() { var selected = document.querySelectorAll('.company-cb:checked').length; var bar = document.getElementById('bulkBar'); document.getElementById('selectedCount').textContent = selected; if (selected > 0) { bar.classList.add('active'); } else { bar.classList.remove('active'); } } function clearSelection() { document.querySelectorAll('.company-cb').forEach(function(cb) { cb.checked = false; }); document.getElementById('selectAll').checked = false; updateBulkBar(); } // Bulk enrich modal function openBulkEnrich() { var selected = document.querySelectorAll('.company-cb:checked').length; document.getElementById('modalCount').textContent = selected; document.getElementById('bulkModal').style.display = 'flex'; document.getElementById('bulkProgress').style.display = 'none'; } function closeBulkModal() { document.getElementById('bulkModal').style.display = 'none'; } function startBulkEnrich() { var companyIds = []; document.querySelectorAll('.company-cb:checked').forEach(function(cb) { companyIds.push(parseInt(cb.value)); }); var steps = []; if (document.getElementById('step-registry').checked) steps.push('registry'); if (document.getElementById('step-seo').checked) steps.push('seo'); if (document.getElementById('step-social').checked) steps.push('social'); if (document.getElementById('step-gbp').checked) steps.push('gbp'); if (document.getElementById('step-logo').checked) steps.push('logo'); if (companyIds.length === 0 || steps.length === 0) return; document.getElementById('bulkProgress').style.display = 'block'; document.getElementById('progressText').textContent = '0/' + companyIds.length; document.getElementById('progressLog').innerHTML = ''; fetch('/admin/data-quality/bulk-enrich', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('meta[name=csrf-token]')?.content || ''}, body: JSON.stringify({company_ids: companyIds, steps: steps}) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.job_id) { pollProgress(data.job_id, companyIds.length); } }) .catch(function(err) { document.getElementById('progressLog').innerHTML += '
Błąd: ' + err.message + '
'; }); } // --- A1: Filter by field --- var activeFieldFilter = null; function filterByField(fieldName) { // Toggle: if same field clicked again, reset if (activeFieldFilter === fieldName) { resetFieldFilter(); return; } activeFieldFilter = fieldName; // Highlight active bar document.querySelectorAll('.dq-bar-row').forEach(function(row) { row.classList.toggle('dq-bar-active', row.dataset.field === fieldName); }); // Show filter info document.getElementById('fieldFilterName').textContent = fieldName; document.getElementById('fieldFilterInfo').classList.add('active'); applyFilters(); } function resetFieldFilter() { activeFieldFilter = null; document.querySelectorAll('.dq-bar-row').forEach(function(row) { row.classList.remove('dq-bar-active'); }); document.getElementById('fieldFilterInfo').classList.remove('active'); applyFilters(); } function applyFilters() { var qualityFilter = document.getElementById('qualityFilter').value; var rows = document.querySelectorAll('#companiesTable tbody tr'); var shown = 0; rows.forEach(function(row) { var qualityMatch = (qualityFilter === 'all' || row.dataset.quality === qualityFilter); var fieldMatch = true; if (activeFieldFilter) { try { var fields = JSON.parse(row.dataset.fields); // Show only companies MISSING this field fieldMatch = !fields[activeFieldFilter]; } catch(e) { fieldMatch = true; } } if (qualityMatch && fieldMatch) { row.style.display = ''; shown++; } else { row.style.display = 'none'; } }); document.getElementById('shownCount').textContent = shown; } // --- Custom tooltip for action buttons --- (function() { var tip = document.createElement('div'); tip.className = 'dq-tooltip'; document.body.appendChild(tip); var timer = null; document.addEventListener('mouseover', function(e) { var btn = e.target.closest('[data-tip]'); if (!btn) return; clearTimeout(timer); timer = setTimeout(function() { tip.textContent = btn.getAttribute('data-tip'); var rect = btn.getBoundingClientRect(); tip.style.left = (rect.left + rect.width / 2 - tip.offsetWidth / 2) + 'px'; tip.style.top = (rect.top - tip.offsetHeight - 8) + 'px'; tip.classList.add('visible'); }, 500); }); document.addEventListener('mouseout', function(e) { var btn = e.target.closest('[data-tip]'); if (!btn) return; clearTimeout(timer); tip.classList.remove('visible'); }); })(); // --- A2: Quick action buttons --- var ACTION_CONFIG = { registry: { url: function(id) { return '/api/company/' + id + '/enrich-registry'; }, body: null, field: 'Dane urzędowe', steps: ['Szukam w rejestrze...', 'Zapisuję dane...'] }, www: { url: function(id) { return '/admin/discover-website/' + id; }, body: null, field: 'Strona WWW', steps: ['Przeszukuję internet...', 'Weryfikuję stronę...'] }, seo: { url: function() { return '/api/seo/audit'; }, bodyFn: function(id) { return JSON.stringify({company_id: id}); }, field: 'Audyt SEO', steps: ['Pobieram stronę...', 'Analizuję PageSpeed...'] }, social: { url: function() { return '/api/social/audit'; }, bodyFn: function(id) { return JSON.stringify({company_id: id}); }, field: 'Audyt Social', steps: ['Skanuję social media...', 'Zapisuję profile...'] }, gbp: { url: function() { return '/api/gbp/audit'; }, bodyFn: function(id) { return JSON.stringify({company_id: id}); }, field: 'Audyt GBP', steps: ['Szukam w Google Maps...', 'Analizuję profil...'] }, logo: { url: function(id) { return '/api/company/' + id + '/fetch-logo'; }, body: null, field: 'Logo', steps: ['Szukam logo na stronie...', 'Pobieram grafikę...'] } }; function quickAction(btn, type, companyId) { if (btn.disabled || btn.classList.contains('loading')) return; var cfg = ACTION_CONFIG[type]; if (!cfg) return; var cell = document.getElementById('actions-' + companyId); if (!cell) return; // Show progress overlay var overlay = document.createElement('div'); overlay.className = 'dq-progress-overlay'; overlay.innerHTML = '
' + cfg.steps[0] + ''; cell.appendChild(overlay); // Animate steps var stepIdx = 0; var stepTimer = setInterval(function() { stepIdx++; if (stepIdx < cfg.steps.length) { var pct = Math.round(((stepIdx + 1) / (cfg.steps.length + 1)) * 100); overlay.querySelector('.dq-progress-bar-fill').style.width = pct + '%'; overlay.querySelector('.dq-progress-text').textContent = cfg.steps[stepIdx]; } }, 1500); var csrf = document.querySelector('meta[name=csrf-token]')?.content || ''; var url = cfg.url(companyId); var body = cfg.bodyFn ? cfg.bodyFn(companyId) : cfg.body; var opts = { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf} }; if (body) opts.body = body; fetch(url, opts) .then(function(r) { return r.json().then(function(d) { return {ok: r.ok, data: d}; }); }) .then(function(result) { clearInterval(stepTimer); if (result.ok) { // Success — show checkmark briefly then restore buttons overlay.querySelector('.dq-progress-bar-fill').style.width = '100%'; overlay.querySelector('.dq-progress-text').innerHTML = ' Gotowe'; setTimeout(function() { overlay.remove(); // Disable the clicked button btn.disabled = true; btn.setAttribute('data-tip', 'Wykonano'); btn.onclick = null; // Update field dot var row = btn.closest('tr'); var dot = row.querySelector('.dq-field-dot[data-field="' + cfg.field + '"]'); if (dot) { dot.classList.remove('empty'); dot.classList.add('filled'); } // WWW: reload page since candidate goes to Discovery section if (type === 'www') { location.reload(); } }, 1200); } else { overlay.querySelector('.dq-progress-text').textContent = 'Błąd: ' + (result.data.error || 'nieznany'); overlay.querySelector('.dq-progress-bar-fill').style.background = '#ef4444'; setTimeout(function() { overlay.remove(); }, 2500); } }) .catch(function(err) { clearInterval(stepTimer); overlay.querySelector('.dq-progress-text').textContent = 'Błąd: ' + err.message; overlay.querySelector('.dq-progress-bar-fill').style.background = '#ef4444'; setTimeout(function() { overlay.remove(); }, 2500); }); } function pollProgress(jobId, total) { fetch('/admin/data-quality/bulk-enrich/status?job_id=' + jobId) .then(function(r) { return r.json(); }) .then(function(data) { var processed = data.processed || 0; var pct = Math.round(processed / total * 100); document.getElementById('progressBar').style.width = pct + '%'; document.getElementById('progressText').textContent = processed + '/' + total; if (data.latest_result) { var log = document.getElementById('progressLog'); log.innerHTML += '
' + data.latest_result + '
'; log.scrollTop = log.scrollHeight; } if (data.status === 'running') { setTimeout(function() { pollProgress(jobId, total); }, 2000); } else { document.getElementById('progressLog').innerHTML += '
Zakończono!
'; } }); } function applyAvailableHint(companyId, field, value, rowId) { var btn = event.target; btn.disabled = true; btn.textContent = '...'; fetch('/api/company/' + companyId + '/apply-hint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('meta[name=csrf-token]')?.content || '' }, body: JSON.stringify({field: field, value: value}) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { var row = document.getElementById(rowId); if (row) row.style.opacity = '0.3'; btn.textContent = 'OK'; btn.style.background = '#22c55e'; } else { btn.textContent = 'Błąd'; btn.style.background = '#ef4444'; } }) .catch(function() { btn.textContent = 'Błąd'; btn.style.background = '#ef4444'; }); } function applyAllAvailableHints() { if (!confirm('Uzupełnić wszystkie dane z Google Business?')) return; var rows = document.querySelectorAll('#availableDataTable tbody tr'); rows.forEach(function(row) { var btn = row.querySelector('.hint-apply-btn'); if (btn && !btn.disabled) btn.click(); }); } // --- Website Discovery --- function discoverWebsite(companyId, btn) { if (btn.disabled) return; var originalHTML = btn.innerHTML; btn.disabled = true; btn.innerHTML = '...'; var csrf = document.querySelector('meta[name=csrf-token]')?.content || ''; fetch('/admin/discover-website/' + companyId, { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf} }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success && data.status === 'found') { btn.innerHTML = ''; btn.setAttribute('data-tip', 'Znaleziono: ' + (data.url || '')); } else { btn.innerHTML = originalHTML; btn.disabled = false; btn.setAttribute('data-tip', data.error || 'Brak wyników'); } }) .catch(function(err) { btn.innerHTML = originalHTML; btn.disabled = false; btn.setAttribute('data-tip', 'Błąd: ' + err.message); }); } function discoverWebsitesBulk() { document.getElementById('discoveryModal').style.display = 'flex'; document.getElementById('discCloseBtn').style.display = 'none'; document.getElementById('discProgressBar').style.width = '0%'; document.getElementById('discProgressText').textContent = 'Uruchamiam...'; document.getElementById('discProgressLog').innerHTML = ''; var csrf = document.querySelector('meta[name=csrf-token]')?.content || ''; fetch('/admin/discover-websites-bulk', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf} }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.job_id) { // Wait 3s before first poll to let the thread start setTimeout(function() { pollDiscoveryProgress(data.job_id); }, 3000); } }) .catch(function(err) { document.getElementById('discProgressLog').innerHTML = '
Błąd: ' + err.message + '
'; document.getElementById('discCloseBtn').style.display = 'inline-block'; }); } var _discLogOffset = 0; function pollDiscoveryProgress(jobId) { fetch('/admin/discover-websites-status?job_id=' + jobId + '&log_offset=' + _discLogOffset) .then(function(r) { return r.json(); }) .then(function(data) { var total = data.total || 0; var processed = data.processed || 0; var pct = total > 0 ? Math.round(processed / total * 100) : 0; document.getElementById('discProgressBar').style.width = pct + '%'; document.getElementById('discProgressText').textContent = processed + '/' + total; if (data.log_entries && data.log_entries.length > 0) { var log = document.getElementById('discProgressLog'); data.log_entries.forEach(function(entry) { log.innerHTML += '
' + entry + '
'; }); log.scrollTop = log.scrollHeight; _discLogOffset = data.log_offset; } if (data.status === 'running') { setTimeout(function() { pollDiscoveryProgress(jobId); }, 3000); } else { var msg = processed > 0 ? 'Zakończono (' + processed + '/' + total + '). Odśwież stronę aby zobaczyć wyniki.' : (total === 0 ? 'Brak nowych firm do wyszukania (wszystkie mają już kandydatów).' : 'Zakończono. Odśwież stronę aby zobaczyć wyniki.'); document.getElementById('discProgressLog').innerHTML += '
' + msg + '
'; document.getElementById('discCloseBtn').style.display = 'inline-block'; } }); } function closeDiscoveryModal() { document.getElementById('discoveryModal').style.display = 'none'; location.reload(); } // Toggle snippet expansion document.querySelectorAll('.disc-snippet').forEach(function(el) { el.addEventListener('click', function() { this.classList.toggle('expanded'); }); }); function acceptDiscovery(candidateId, rowId) { var csrf = document.querySelector('meta[name=csrf-token]')?.content || ''; fetch('/admin/discovery/' + candidateId + '/accept', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf} }) .then(function(r) { return r.json(); }) .then(function(data) { var row = document.getElementById(rowId); if (data.success) { if (row) row.style.opacity = '0.3'; } else { alert('Błąd: ' + (data.error || 'nieznany')); } }) .catch(function(err) { alert('Błąd: ' + err.message); }); } function rejectDiscovery(candidateId, rowId) { var csrf = document.querySelector('meta[name=csrf-token]')?.content || ''; fetch('/admin/discovery/' + candidateId + '/reject', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf} }) .then(function(r) { return r.json(); }) .then(function(data) { var row = document.getElementById(rowId); if (data.success) { if (row) row.remove(); } else { alert('Błąd: ' + (data.error || 'nieznany')); } }) .catch(function(err) { alert('Błąd: ' + err.message); }); } {% endblock %}