feat: add performance metrics, category benchmarks, and WHOIS domain info
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
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

- Speed Index, Time to Interactive, Total Blocking Time from PageSpeed
- Benchmark table: company vs category avg vs all-members avg with arrows
- WHOIS via RDAP: domain registration, expiry, last change, registrar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-11 06:09:22 +01:00
parent 04bbf5db18
commit 3690b4d77e
2 changed files with 203 additions and 2 deletions

View File

@ -327,11 +327,109 @@ def admin_seo_detail(company_id):
except Exception:
pass
# Category benchmarks — average scores in same category
benchmarks = {}
if company.category_id and analysis:
try:
from sqlalchemy import func as sqlfunc
cat_stats = db.query(
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_seo_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_performance_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_accessibility_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_best_practices_score),
sqlfunc.count(CompanyWebsiteAnalysis.id),
).join(Company, Company.id == CompanyWebsiteAnalysis.company_id
).filter(
Company.category_id == company.category_id,
Company.status == 'active',
CompanyWebsiteAnalysis.pagespeed_seo_score.isnot(None),
).first()
if cat_stats and cat_stats[4] > 1:
from database import Category
cat_name = db.query(Category.name).filter_by(id=company.category_id).scalar()
benchmarks = {
'category_name': cat_name or '',
'count': cat_stats[4],
'avg_seo': round(float(cat_stats[0] or 0)),
'avg_performance': round(float(cat_stats[1] or 0)),
'avg_accessibility': round(float(cat_stats[2] or 0)),
'avg_best_practices': round(float(cat_stats[3] or 0)),
}
except Exception:
pass
# All-members benchmark
all_benchmarks = {}
if analysis:
try:
from sqlalchemy import func as sqlfunc
all_stats = db.query(
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_seo_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_performance_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_accessibility_score),
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_best_practices_score),
sqlfunc.count(CompanyWebsiteAnalysis.id),
).join(Company, Company.id == CompanyWebsiteAnalysis.company_id
).filter(
Company.status == 'active',
CompanyWebsiteAnalysis.pagespeed_seo_score.isnot(None),
).first()
if all_stats and all_stats[4] > 1:
all_benchmarks = {
'count': all_stats[4],
'avg_seo': round(float(all_stats[0] or 0)),
'avg_performance': round(float(all_stats[1] or 0)),
'avg_accessibility': round(float(all_stats[2] or 0)),
'avg_best_practices': round(float(all_stats[3] or 0)),
}
except Exception:
pass
# WHOIS domain expiry lookup
domain_info = {}
if analysis and company.website:
try:
import re
import requests as req
domain_match = re.search(r'https?://(?:www\.)?([^/]+)', company.website)
if domain_match:
domain = domain_match.group(1)
resp = req.get(
f'https://rdap.org/domain/{domain}',
timeout=5,
headers={'Accept': 'application/rdap+json'}
)
if resp.status_code == 200:
rdap = resp.json()
for event in rdap.get('events', []):
if event.get('eventAction') == 'expiration':
domain_info['expires'] = event.get('eventDate', '')[:10]
elif event.get('eventAction') == 'registration':
domain_info['registered'] = event.get('eventDate', '')[:10]
elif event.get('eventAction') == 'last changed':
domain_info['updated'] = event.get('eventDate', '')[:10]
# Registrar from entities
for entity in rdap.get('entities', []):
if 'registrar' in entity.get('roles', []):
vcard = entity.get('vcardArray', [None, []])[1]
for item in (vcard or []):
if item[0] == 'fn':
domain_info['registrar'] = item[3]
break
domain_info['domain'] = domain
except Exception:
pass
return render_template('admin/seo_detail.html',
company=company,
analysis=analysis,
recommendations=recommendations,
ip_info=ip_info
ip_info=ip_info,
benchmarks=benchmarks,
all_benchmarks=all_benchmarks,
domain_info=domain_info
)
finally:
db.close()

View File

@ -290,6 +290,51 @@
</div>
</div>
<!-- Porownanie z innymi firmami -->
{% if all_benchmarks %}
<div class="section">
<h2>Porownanie z innymi firmami Norda Biznes</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Srednie wyniki {{ all_benchmarks.count }} zbadanych firm w stowarzyszeniu{% if benchmarks %} oraz {{ benchmarks.count }} firm w kategorii "{{ benchmarks.category_name }}"{% endif %}.
</p>
<table style="width: 100%; border-collapse: collapse; font-size: var(--font-size-sm);">
<thead>
<tr style="border-bottom: 2px solid var(--border);">
<th style="text-align: left; padding: 8px;">Metryka</th>
<th style="text-align: center; padding: 8px; font-weight: 700; color: var(--primary);">{{ company.name[:25] }}</th>
{% if benchmarks %}<th style="text-align: center; padding: 8px;">{{ benchmarks.category_name[:20] }} ({{ benchmarks.count }})</th>{% endif %}
<th style="text-align: center; padding: 8px;">Wszystkie firmy ({{ all_benchmarks.count }})</th>
</tr>
</thead>
<tbody>
{% set metrics = [
('SEO', analysis.pagespeed_seo_score, benchmarks.get('avg_seo'), all_benchmarks.avg_seo),
('Performance', analysis.pagespeed_performance_score, benchmarks.get('avg_performance'), all_benchmarks.avg_performance),
('Dostepnosc', analysis.pagespeed_accessibility_score, benchmarks.get('avg_accessibility'), all_benchmarks.avg_accessibility),
('Best Practices', analysis.pagespeed_best_practices_score, benchmarks.get('avg_best_practices'), all_benchmarks.avg_best_practices),
] %}
{% for name, val, cat_avg, all_avg in metrics %}
<tr style="border-bottom: 1px solid var(--border);">
<td style="padding: 8px;">{{ name }}</td>
<td style="text-align: center; padding: 8px; font-weight: 700;">
{% if val is not none %}
<span class="{% if val >= 90 %}yes{% elif val >= 50 %}{% else %}no{% endif %}">{{ val }}</span>
{% if val > all_avg %} <span style="color: var(--success); font-size: 11px;"></span>
{% elif val < all_avg %} <span style="color: var(--danger); font-size: 11px;"></span>
{% else %} <span style="color: var(--text-secondary); font-size: 11px;"></span>{% endif %}
{% else %}-{% endif %}
</td>
{% if benchmarks %}
<td style="text-align: center; padding: 8px; color: var(--text-secondary);">{{ cat_avg if cat_avg else '-' }}</td>
{% endif %}
<td style="text-align: center; padding: 8px; color: var(--text-secondary);">{{ all_avg }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Zalecenia -->
<div class="section">
<h2>Zalecenia — co poprawic</h2>
@ -527,6 +572,34 @@
{% endif %}
</div>
{% if analysis.pagespeed_audits and analysis.pagespeed_audits is mapping and analysis.pagespeed_audits.get('performance') %}
{% set perf_data = analysis.pagespeed_audits.performance %}
<h3 style="margin-top: var(--spacing-lg); font-size: var(--font-size-base); color: var(--text-secondary);">Szczegolowe metryki wydajnosci (Lighthouse)</h3>
<div class="detail-grid" style="margin-top: var(--spacing-sm);">
{% if perf_data.get('speed-index') %}
{% set si = perf_data['speed-index'] %}
<div class="detail-item">
<span class="detail-label">Speed Index — jak szybko tresc staje sie widoczna</span>
<span class="detail-value {% if si.score is not none %}{% if si.score >= 0.9 %}yes{% elif si.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ si.get('displayValue', '-') }}</span>
</div>
{% endif %}
{% if perf_data.get('interactive') %}
{% set tti = perf_data['interactive'] %}
<div class="detail-item">
<span class="detail-label">Time to Interactive — kiedy strona reaguje na klikniecia</span>
<span class="detail-value {% if tti.score is not none %}{% if tti.score >= 0.9 %}yes{% elif tti.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ tti.get('displayValue', '-') }}</span>
</div>
{% endif %}
{% if perf_data.get('total-blocking-time') %}
{% set tbt = perf_data['total-blocking-time'] %}
<div class="detail-item">
<span class="detail-label">Total Blocking Time — jak dlugo JS blokuje strone</span>
<span class="detail-value {% if tbt.score is not none %}{% if tbt.score >= 0.9 %}yes{% elif tbt.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ tbt.get('displayValue', '-') }}</span>
</div>
{% endif %}
</div>
{% endif %}
{% if analysis.security_headers_count is not none %}
<h3 style="margin-top: var(--spacing-lg); font-size: var(--font-size-base); color: var(--text-secondary);">Zabezpieczenia</h3>
<div class="detail-grid" style="margin-top: var(--spacing-sm);">
@ -1002,10 +1075,40 @@
{% endif %}
<!-- Infrastruktura i szczegoly techniczne -->
{% if analysis.ssl_expires_at or analysis.final_url or analysis.hosting_provider or analysis.server_software or analysis.hosting_ip %}
{% if analysis.ssl_expires_at or analysis.final_url or analysis.hosting_provider or analysis.server_software or analysis.hosting_ip or domain_info %}
<div class="section">
<h2>Infrastruktura i szczegoly techniczne</h2>
<div class="detail-grid">
{% if domain_info.get('domain') %}
<div class="detail-item">
<span class="detail-label">Domena</span>
<span class="detail-value" style="font-family: monospace;">{{ domain_info.domain }}</span>
</div>
{% endif %}
{% if domain_info.get('registered') %}
<div class="detail-item">
<span class="detail-label">Domena zarejestrowana</span>
<span class="detail-value">{{ domain_info.registered }}</span>
</div>
{% endif %}
{% if domain_info.get('expires') %}
<div class="detail-item">
<span class="detail-label">Domena wygasa</span>
<span class="detail-value {% if domain_info.expires < (now().strftime('%Y-%m-%d') if now is defined else '') %}no{% endif %}" style="font-weight: 700;">{{ domain_info.expires }}</span>
</div>
{% endif %}
{% if domain_info.get('updated') %}
<div class="detail-item">
<span class="detail-label">Ostatnia zmiana domeny</span>
<span class="detail-value">{{ domain_info.updated }}</span>
</div>
{% endif %}
{% if domain_info.get('registrar') %}
<div class="detail-item">
<span class="detail-label">Rejestrator domeny (WHOIS)</span>
<span class="detail-value">{{ domain_info.registrar }}</span>
</div>
{% endif %}
{% if analysis.final_url and analysis.final_url != analysis.website_url %}
<div class="detail-item">
<span class="detail-label">URL po przekierowaniu</span>