refactor(seo): Rename FID to INP across entire codebase
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

Google replaced First Input Delay (FID) with Interaction to Next Paint
(INP) as a Core Web Vital in March 2024. This renames the DB column
from first_input_delay_ms to interaction_to_next_paint_ms, updates the
PageSpeed client to prefer the INP audit key, and fixes all references
across routes, services, scripts, and report generators. Updated INP
thresholds: good ≤200ms, needs improvement ≤500ms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-08 12:58:41 +01:00
parent ea3d26282f
commit ef39ebf8a3
9 changed files with 30 additions and 22 deletions

View File

@ -222,7 +222,7 @@ def _collect_seo_data(db, company) -> dict:
'nap_on_website': analysis.nap_on_website,
# Core Web Vitals
'lcp_ms': analysis.largest_contentful_paint_ms,
'inp_ms': getattr(analysis, 'interaction_to_next_paint_ms', None), # Replaced FID in March 2024
'inp_ms': analysis.interaction_to_next_paint_ms,
'cls': float(analysis.cumulative_layout_shift) if analysis.cumulative_layout_shift else None,
# Additional performance metrics (10 missing metrics)
'fcp_ms': getattr(analysis, 'first_contentful_paint_ms', None),

View File

@ -192,7 +192,7 @@ def _build_seo_audit_response(company, analysis):
},
'core_web_vitals': {
'largest_contentful_paint_ms': analysis.largest_contentful_paint_ms,
'first_input_delay_ms': analysis.first_input_delay_ms,
'interaction_to_next_paint_ms': analysis.interaction_to_next_paint_ms,
'cumulative_layout_shift': float(analysis.cumulative_layout_shift) if analysis.cumulative_layout_shift else None
},
'social': {

View File

@ -105,7 +105,7 @@ def seo_audit_dashboard(slug):
'last_modified_date': analysis.last_modified_at,
# Core Web Vitals
'lcp_ms': analysis.largest_contentful_paint_ms,
'inp_ms': analysis.first_input_delay_ms,
'inp_ms': analysis.interaction_to_next_paint_ms,
'cls': float(analysis.cumulative_layout_shift) if analysis.cumulative_layout_shift is not None else None,
# Heading structure
'h1_count': analysis.h1_count,

View File

@ -1111,7 +1111,7 @@ class CompanyWebsiteAnalysis(Base):
# Core Web Vitals
viewport_configured = Column(Boolean) # Whether viewport meta tag is properly configured
largest_contentful_paint_ms = Column(Integer) # Core Web Vital: LCP in milliseconds
first_input_delay_ms = Column(Integer) # Core Web Vital: FID in milliseconds
interaction_to_next_paint_ms = Column(Integer) # Core Web Vital: INP in milliseconds (replaced FID March 2024)
cumulative_layout_shift = Column(Numeric(5, 3)) # Core Web Vital: CLS score
# Open Graph & Social Meta

View File

@ -0,0 +1,8 @@
-- Migration 060: Rename first_input_delay_ms to interaction_to_next_paint_ms
-- Google replaced FID with INP (Interaction to Next Paint) as a Core Web Vital in March 2024
-- The PageSpeed API now returns INP data, so the column name should reflect the actual metric
ALTER TABLE company_website_analysis RENAME COLUMN first_input_delay_ms TO interaction_to_next_paint_ms;
-- Grant permissions
GRANT ALL ON TABLE company_website_analysis TO nordabiz_app;

View File

@ -243,7 +243,7 @@ class AuditReportGenerator:
'content_freshness_score': analysis.content_freshness_score,
'core_web_vitals': {
'lcp_ms': analysis.largest_contentful_paint_ms,
'fid_ms': analysis.first_input_delay_ms,
'inp_ms': analysis.interaction_to_next_paint_ms,
'cls': float(analysis.cumulative_layout_shift) if analysis.cumulative_layout_shift else None,
},
'seo_issues': analysis.seo_issues,

View File

@ -87,7 +87,7 @@ class PageSpeedScore:
class CoreWebVitals:
"""Core Web Vitals metrics from PageSpeed."""
lcp_ms: Optional[int] = None # Largest Contentful Paint
fid_ms: Optional[int] = None # First Input Delay
inp_ms: Optional[int] = None # Interaction to Next Paint (replaced FID March 2024)
cls: Optional[float] = None # Cumulative Layout Shift
fcp_ms: Optional[int] = None # First Contentful Paint
ttfb_ms: Optional[int] = None # Time to First Byte
@ -517,7 +517,7 @@ class GooglePageSpeedClient:
audits = lighthouse.get('audits', {})
core_web_vitals = CoreWebVitals(
lcp_ms=self._extract_metric_ms(audits.get('largest-contentful-paint')),
fid_ms=self._extract_metric_ms(audits.get('max-potential-fid')),
inp_ms=self._extract_metric_ms(audits.get('interaction-to-next-paint') or audits.get('max-potential-fid')),
cls=self._extract_cls(audits.get('cumulative-layout-shift')),
fcp_ms=self._extract_metric_ms(audits.get('first-contentful-paint')),
ttfb_ms=self._extract_metric_ms(audits.get('server-response-time')),

View File

@ -926,7 +926,7 @@ class SEOAuditor:
viewport_configured, is_mobile_friendly,
-- Core Web Vitals
largest_contentful_paint_ms, first_input_delay_ms, cumulative_layout_shift,
largest_contentful_paint_ms, interaction_to_next_paint_ms, cumulative_layout_shift,
-- Open Graph
has_og_tags, og_title, og_description, og_image,
@ -969,7 +969,7 @@ class SEOAuditor:
:has_sitemap, :has_robots_txt,
:viewport_configured, :is_mobile_friendly,
:largest_contentful_paint_ms, :first_input_delay_ms, :cumulative_layout_shift,
:largest_contentful_paint_ms, :interaction_to_next_paint_ms, :cumulative_layout_shift,
:has_og_tags, :og_title, :og_description, :og_image,
:has_twitter_cards,
@ -1028,7 +1028,7 @@ class SEOAuditor:
is_mobile_friendly = EXCLUDED.is_mobile_friendly,
largest_contentful_paint_ms = EXCLUDED.largest_contentful_paint_ms,
first_input_delay_ms = EXCLUDED.first_input_delay_ms,
interaction_to_next_paint_ms = EXCLUDED.interaction_to_next_paint_ms,
cumulative_layout_shift = EXCLUDED.cumulative_layout_shift,
has_og_tags = EXCLUDED.has_og_tags,
@ -1122,7 +1122,7 @@ class SEOAuditor:
# Core Web Vitals
'largest_contentful_paint_ms': cwv.get('lcp_ms'),
'first_input_delay_ms': cwv.get('fid_ms'),
'interaction_to_next_paint_ms': cwv.get('inp_ms'),
'cumulative_layout_shift': cwv.get('cls'),
# Open Graph

View File

@ -104,7 +104,7 @@ class SEOReportGenerator:
wa.has_canonical, wa.canonical_url, wa.is_indexable, wa.noindex_reason,
wa.has_sitemap, wa.has_robots_txt,
wa.viewport_configured, wa.is_mobile_friendly,
wa.largest_contentful_paint_ms, wa.first_input_delay_ms, wa.cumulative_layout_shift,
wa.largest_contentful_paint_ms, wa.interaction_to_next_paint_ms, wa.cumulative_layout_shift,
wa.has_og_tags, wa.og_title, wa.og_description, wa.og_image,
wa.has_twitter_cards, wa.html_lang, wa.has_hreflang,
wa.word_count_homepage,
@ -629,10 +629,10 @@ class SEOReportGenerator:
def _core_web_vitals_section(self, company: Dict[str, Any]) -> str:
"""Generate Core Web Vitals section HTML."""
lcp = company.get('largest_contentful_paint_ms')
fid = company.get('first_input_delay_ms')
inp = company.get('interaction_to_next_paint_ms')
cls = company.get('cumulative_layout_shift')
if lcp is None and fid is None and cls is None:
if lcp is None and inp is None and cls is None:
return ''
def lcp_status(val):
@ -644,12 +644,12 @@ class SEOReportGenerator:
return (f'{val}ms', 'badge-warning')
return (f'{val}ms', 'badge-danger')
def fid_status(val):
def inp_status(val):
if val is None:
return ('', 'badge-secondary')
if val <= 100:
if val <= 200:
return (f'{val}ms', 'badge-success')
if val <= 300:
if val <= 500:
return (f'{val}ms', 'badge-warning')
return (f'{val}ms', 'badge-danger')
@ -663,7 +663,7 @@ class SEOReportGenerator:
return (f'{val:.3f}', 'badge-danger')
lcp_val, lcp_class = lcp_status(lcp)
fid_val, fid_class = fid_status(fid)
inp_val, inp_class = inp_status(inp)
cls_val, cls_class = cls_status(cls)
return f'''
@ -682,14 +682,14 @@ class SEOReportGenerator:
</div>
</div>
<div class="info-card">
<h3>FID (First Input Delay)</h3>
<h3>INP (Interaction to Next Paint)</h3>
<div class="item">
<span class="label">Wynik</span>
<span class="value"><span class="badge {fid_class}">{fid_val}</span></span>
<span class="value"><span class="badge {inp_class}">{inp_val}</span></span>
</div>
<div class="item">
<span class="label">Cel</span>
<span class="value">&lt; 100ms</span>
<span class="value">&lt; 200ms</span>
</div>
</div>
<div class="info-card">
@ -1078,7 +1078,7 @@ class SEOReportGenerator:
}
company_data['seo_audit']['core_web_vitals'] = {
'lcp_ms': company.get('largest_contentful_paint_ms'),
'fid_ms': company.get('first_input_delay_ms'),
'inp_ms': company.get('interaction_to_next_paint_ms'),
'cls': float(company.get('cumulative_layout_shift')) if company.get('cumulative_layout_shift') else None,
}
company_data['seo_audit']['social'] = {