feat: Add logo recommendation scoring to candidate gallery
Some checks are pending
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
Some checks are pending
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
Score candidates based on format (SVG > raster), source reliability (img_scan > apple-touch-icon > og:image > favicon), aspect ratio (square-ish preferred), and size. Recommended candidate gets amber border with "rekomendowane" badge in gallery UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4119e44a58
commit
788ac12d30
@ -112,6 +112,12 @@ class LogoFetchService:
|
|||||||
if not saved:
|
if not saved:
|
||||||
return {'success': False, 'message': 'Nie udało się pobrać żadnego kandydata', 'candidates': [], 'steps': steps}
|
return {'success': False, 'message': 'Nie udało się pobrać żadnego kandydata', 'candidates': [], 'steps': steps}
|
||||||
|
|
||||||
|
# Score candidates and find recommendation
|
||||||
|
recommended_index = None
|
||||||
|
if saved:
|
||||||
|
best = max(saved, key=lambda c: self._score_candidate(c))
|
||||||
|
recommended_index = best['index']
|
||||||
|
|
||||||
steps.append({
|
steps.append({
|
||||||
'step': 'save',
|
'step': 'save',
|
||||||
'status': 'complete',
|
'status': 'complete',
|
||||||
@ -122,6 +128,7 @@ class LogoFetchService:
|
|||||||
'success': True,
|
'success': True,
|
||||||
'message': f'Znaleziono {len(saved)} kandydatów na logo',
|
'message': f'Znaleziono {len(saved)} kandydatów na logo',
|
||||||
'candidates': saved,
|
'candidates': saved,
|
||||||
|
'recommended_index': recommended_index,
|
||||||
'steps': steps
|
'steps': steps
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,6 +415,53 @@ class LogoFetchService:
|
|||||||
else:
|
else:
|
||||||
steps.append({'step': 'scan_images', 'status': 'missing', 'message': 'Brak elementów img z "logo" w atrybutach'})
|
steps.append({'step': 'scan_images', 'status': 'missing', 'message': 'Brak elementów img z "logo" w atrybutach'})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _score_candidate(candidate):
|
||||||
|
"""Score a candidate for recommendation. Higher = better logo choice."""
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
# SVG is ideal for logos (vector, scalable)
|
||||||
|
if candidate['ext'] == 'svg':
|
||||||
|
score += 30
|
||||||
|
|
||||||
|
# Source reliability — how likely this is the actual logo
|
||||||
|
source_scores = {
|
||||||
|
'img_scan': 35,
|
||||||
|
'css_bg': 25,
|
||||||
|
'apple-touch-icon': 15,
|
||||||
|
'og:image': 0,
|
||||||
|
'twitter:image': 0,
|
||||||
|
'favicon': -10,
|
||||||
|
'google_favicon': -20,
|
||||||
|
}
|
||||||
|
score += source_scores.get(candidate['source'], 0)
|
||||||
|
|
||||||
|
# Aspect ratio — square-ish logos are most versatile
|
||||||
|
w, h = candidate.get('width', 0), candidate.get('height', 0)
|
||||||
|
if w > 0 and h > 0:
|
||||||
|
ratio = max(w, h) / min(w, h)
|
||||||
|
if ratio <= 2.0:
|
||||||
|
score += 20
|
||||||
|
elif ratio <= 3.0:
|
||||||
|
score += 5
|
||||||
|
else:
|
||||||
|
score -= 15
|
||||||
|
elif candidate['ext'] == 'svg':
|
||||||
|
score += 15
|
||||||
|
|
||||||
|
# Size — prefer medium-sized, not favicon-tiny or banner-huge
|
||||||
|
max_dim = max(w, h) if w > 0 else 0
|
||||||
|
if max_dim == 0 and candidate['ext'] == 'svg':
|
||||||
|
score += 10
|
||||||
|
elif 64 <= max_dim <= 512:
|
||||||
|
score += 15
|
||||||
|
elif max_dim > 512:
|
||||||
|
score += 5
|
||||||
|
elif max_dim > 0:
|
||||||
|
score -= 10
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_size(sizes_str):
|
def _parse_size(sizes_str):
|
||||||
"""Parse '180x180' to max dimension int."""
|
"""Parse '180x180' to max dimension int."""
|
||||||
|
|||||||
@ -446,6 +446,11 @@
|
|||||||
background: #d1fae5;
|
background: #d1fae5;
|
||||||
color: #065f46;
|
color: #065f46;
|
||||||
}
|
}
|
||||||
|
.logo-gallery-item.recommended { border-color: #f59e0b; }
|
||||||
|
.logo-gallery-item.recommended .gallery-badge.badge-recommended {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
.logo-gallery-item .gallery-size {
|
.logo-gallery-item .gallery-size {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
@ -4707,7 +4712,7 @@ function updateLogoStep(stepId, status, message) {
|
|||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGallery(candidates, existingLogoExt) {
|
function buildGallery(candidates, existingLogoExt, recommendedIndex) {
|
||||||
const grid = document.getElementById('logoGalleryGrid');
|
const grid = document.getElementById('logoGalleryGrid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
selectedIndex = null;
|
selectedIndex = null;
|
||||||
@ -4731,15 +4736,18 @@ function updateLogoStep(stepId, status, message) {
|
|||||||
// Show candidates
|
// Show candidates
|
||||||
candidates.forEach(c => {
|
candidates.forEach(c => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'logo-gallery-item';
|
const isRecommended = (recommendedIndex !== null && c.index === recommendedIndex);
|
||||||
|
item.className = 'logo-gallery-item' + (isRecommended ? ' recommended' : '');
|
||||||
item.dataset.index = c.index;
|
item.dataset.index = c.index;
|
||||||
const dims = c.width ? `${c.width}x${c.height}` : 'SVG';
|
const dims = c.width ? `${c.width}x${c.height}` : 'SVG';
|
||||||
|
const recBadge = isRecommended ? '<span class="gallery-badge badge-recommended">rekomendowane</span>' : '';
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div class="gallery-img">
|
<div class="gallery-img">
|
||||||
<img src="/static/img/companies/${c.filename}${cacheBust}" alt="${c.label}">
|
<img src="/static/img/companies/${c.filename}${cacheBust}" alt="${c.label}">
|
||||||
</div>
|
</div>
|
||||||
<div class="gallery-label">${c.label}</div>
|
<div class="gallery-label">${c.label}</div>
|
||||||
<div class="gallery-size">${dims}</div>
|
<div class="gallery-size">${dims}</div>
|
||||||
|
${recBadge}
|
||||||
`;
|
`;
|
||||||
item.addEventListener('click', () => selectCandidate(item, c.index));
|
item.addEventListener('click', () => selectCandidate(item, c.index));
|
||||||
grid.appendChild(item);
|
grid.appendChild(item);
|
||||||
@ -4787,7 +4795,7 @@ function updateLogoStep(stepId, status, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show gallery with all candidates
|
// Show gallery with all candidates
|
||||||
buildGallery(data.candidates, data.existing_logo_ext);
|
buildGallery(data.candidates, data.existing_logo_ext, data.recommended_index);
|
||||||
document.getElementById('logoGalleryOverlay').classList.add('active');
|
document.getElementById('logoGalleryOverlay').classList.add('active');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user