diff --git a/content.js b/content.js index 0c10e45..abfa7aa 100644 --- a/content.js +++ b/content.js @@ -66,11 +66,12 @@ '.topcard__title', ]); - // LinkedIn renders screen-reader-only headings throughout its chrome - // ("0 notifications", "1 new message"). They are real

elements with - // real text, so any document-wide heading scan will happily return one as - // the job title. Require the element to be visibly rendered and to not look - // like navigation furniture. + // LinkedIn's job panel is full of text that is neither navigation nor the + // job title: screen-reader-only headings ("0 notifications"), and product + // furniture inside the detail column itself ("Are these results helpful?", + // "Your profile and resume match...", "Get personalized tips..."). A + // heuristic DOM scan cannot reliably tell those from a job title, so the + // scan is now the LAST resort rather than the second. function isVisible(el) { if (!el || !el.offsetParent) return false; if (el.closest('[aria-hidden="true"]')) return false; @@ -80,20 +81,48 @@ return !/visually-hidden|sr-only|a11y-text/.test(cls); } - const NAV_NOISE = /notification|message|invitation|my network|feed update|search|premium|^\d+\s|jobs|people|similar|featured|recommended/i; + const NAV_NOISE = new RegExp([ + // chrome / navigation + 'notification', 'message', 'invitation', 'my network', 'feed update', + 'premium', 'search', 'similar', 'featured', 'recommended', + // in-panel product furniture — the class of string that broke this before + 'helpful', 'results', 'feedback', 'your profile', 'match', 'tips', + 'people you', 'about the job', 'sign in', 'join now', 'people also', + 'jobs based on', 'reach out', + // counters + '^\\d+\\s' + ].join('|'), 'i'); function plausibleTitle(text) { return text.length > 4 && text.length < 100 && !NAV_NOISE.test(text); } - // Fallback: a heading inside the job detail panel only — never page-wide. + // Preferred fallback: the browser tab title. LinkedIn sets it to + // "(N) Job Title | Company | LinkedIn" on every job page, it is not part of + // the React DOM that keeps getting restructured, and it cannot pick up a + // stray card heading. This is far more reliable than guessing at headings, + // so it runs before the DOM scan, not after it. + if (!data.jobTitle) { + // The "(N)" prefix is LinkedIn's unread counter and can read "(99+)". + const m = document.title.match(/^(?:\(\d+\+?\)\s*)?(.+?)\s*[|·]/); + if (m) { + const candidate = m[1].trim(); + if (plausibleTitle(candidate) && !/^linkedin$/i.test(candidate)) { + data.jobTitle = candidate; + } + } + } + + // Last resort: a heading inside the job detail panel only — never page-wide. if (!data.jobTitle) { const jobPanel = document.querySelector( '.scaffold-layout__detail-column, .jobs-search__job-details, .job-view-layout, ' + '.jobs-details, [class*="jobs-unified-top-card"], main' ); if (jobPanel) { - for (const h of jobPanel.querySelectorAll('h1, h2')) { + // h1 first: the job title is a level-1 heading, while the cards that + // polluted this before ("Are these results helpful?") are h2/h3. + for (const h of [...jobPanel.querySelectorAll('h1'), ...jobPanel.querySelectorAll('h2')]) { const text = h.textContent.trim(); if (isVisible(h) && plausibleTitle(text)) { data.jobTitle = text; @@ -103,13 +132,6 @@ } } - // Last resort: the browser tab title, which LinkedIn formats as - // "(N) Job Title | Company | LinkedIn". Still better than a nav label. - if (!data.jobTitle) { - const m = document.title.match(/^(?:\(\d+\)\s*)?(.+?)\s*\|/); - if (m && plausibleTitle(m[1].trim())) data.jobTitle = m[1].trim(); - } - // ── Company Name data.company = trySelectors([ '.job-details-jobs-unified-top-card__company-name a', diff --git a/server/generate_cv.php b/server/generate_cv.php index d1f545a..da89edb 100644 --- a/server/generate_cv.php +++ b/server/generate_cv.php @@ -520,9 +520,34 @@ PROMPT; if ($pageCount <= $TARGET_PAGES) break; } - // ATS parsers key on the filename too — "Name - Role.pdf" reads cleanly. - $safeName = preg_replace('/[^a-zA-Z0-9\-_ ]/', '', $id['fullName']); - $safeTitle = preg_replace('/[^a-zA-Z0-9\-_ ]/', '', $jobTitle ?: 'CV'); + // ATS parsers key on the filename too — "Name - Role.pdf" reads cleanly, + // and a recruiter sees it before opening anything. + // + // Scraping a job title out of LinkedIn's DOM is fragile, and a bad + // scrape has shipped filenames like "Hamza Ayed - 0 notifications.pdf" + // and "Hamza Ayed - Are these results helpful.pdf". The client is fixed, + // but the server must not depend on the client being right: reject + // anything that does not look like a job title and fall back to the + // profile's own positioning instead. + $rawTitle = trim($jobTitle); + $looksLikeUiText = preg_match( + '/helpful|result|feedback|notification|message|your profile|match|tips|about the job|^\d/i', + $rawTitle + ); + // Real job titles carry a role noun. Requiring one rejects stray UI + // strings without hardcoding a list of every possible title. + $hasRoleNoun = preg_match( + '/engineer|developer|architect|lead|manager|consultant|specialist|analyst|designer|' . + 'programmer|scientist|director|head of|principal|staff|senior|founding|cto|technologist/i', + $rawTitle + ); + + if ($rawTitle === '' || $looksLikeUiText || !$hasRoleNoun) { + $rawTitle = $id['primaryTitle']; + } + + $safeName = preg_replace('/[^\p{L}0-9\-_ ]/u', '', $id['fullName']); + $safeTitle = preg_replace('/[^\p{L}0-9\-_ ()\/]/u', '', $rawTitle); $safeTitle = trim(preg_replace('/\s+/', ' ', $safeTitle)); $fileName = trim("{$safeName} - {$safeTitle}") . '.pdf'; diff --git a/server/profile.json b/server/profile.json index 0a11f16..3908976 100644 --- a/server/profile.json +++ b/server/profile.json @@ -383,6 +383,6 @@ ] }, "_generated": "AUTO-GENERATED from profile_data.js — do not edit by hand. Run: node sync_profile.js", - "_generatedAt": "2026-08-05T14:34:44.629Z", + "_generatedAt": "2026-08-05T14:41:16.766Z", "profileText": "HAMZA AYED — Senior Mobile Architect\nRide-Hailing & Real-Time Systems · FinTech in Emerging Markets\n\nSUMMARY:\nSenior Mobile Architect who built and launched two production-grade ride-hailing platforms (Tripz — Egypt, Intaleq — Syria) from zero to live operations. Designed and delivered full mobility ecosystems including Rider, Driver, Admin and Customer Support applications, along with backend APIs, security layers, DevOps pipelines and fintech integrations. Specialized in real-time systems, self-hosted geo-services, high-latency optimization and infrastructure cost reduction.\n\nTARGET ROLES: Senior / Lead Mobile Engineer | Mobile Architect | Founding Engineer (Mobility, Logistics, FinTech)\n\nHEADLINE METRICS:\n- 8 applications engineered and launched across 2 ride-hailing platforms (Tripz, Intaleq)\n- Under 4 months from zero to live operations for the full Tripz ecosystem\n- ~80% lower third-party mapping API spend — over $10,000/month — via self-hosted infrastructure\n- 5,750+ drivers onboarded across 2 markets\n\nAT A GLANCE:\n- Tripz (Egypt): Scaled platform to 2,464 riders and 4,318 drivers. Built and launched 4 distinct apps plus full backend in under 4 months with near-zero map infrastructure cost.\n- Intaleq (Syria): Acquired 2,811 riders and 1,440 drivers in the first 2 months. Engineered a custom operational payment layer to bypass API limitations in a highly constrained market.\n\nTECHNICAL SKILLS:\n- Mobile & Architecture: Flutter, Dart, GetX, Provider, Clean Architecture, Offline-First Design, REST APIs, State Management, Native Android (Java), App Store Connect, Google Play Console, iOS / Android Release Management, Firebase Cloud Messaging (FCM), Push Notifications, Background Location Tracking\n- Real-Time & Geo Systems: WebSockets, Real-Time Dispatching, Live Tracking, GraphHopper, PostGIS, Martin (vector tile server), OpenStreetMap (OSM), MapLibre GL, Self-Hosted Map Tiles, Geospatial Queries, High-Latency Network Optimization\n- Backend & DevOps: PHP, Laravel, PostgreSQL, MySQL, Redis, Docker, Linux, Nginx, Git, CI/CD (Gitea), Infrastructure Cost Optimization, Self-Hosted Infrastructure\n- Security & FinTech: Payment Gateway Integration (7+ providers), HMAC Security, RASP, Fake GPS Detection, Anti-Tampering, KYC Automation\n\nEXPERIENCE:\n- Senior Mobile Systems Architect — Intaleq | Jan 2025 – Present | MENA Region (Remote)\n • Solely architected and launched a ride-hailing platform in a high-constraint market (Syria), reaching 2,811 riders and 1,440 drivers within the first two months.\n • Designed and implemented Rider, Driver, Admin and Customer Support applications.\n • Built a custom payment helper application to integrate legacy, non-API local payment gateways, enabling reliable driver balance top-ups despite infrastructure limitations.\n • Ran fully self-hosted geo infrastructure — GraphHopper routing, Martin vector tile serving and PostGIS spatial queries on OpenStreetMap data — removing third-party mapping API dependency and its recurring cost.\n • Designed an offline-first caching mechanism ensuring data integrity and uninterrupted driver–rider interactions during unstable network conditions.\n • Optimized deployment cycles by 40% through a secure, self-hosted CI/CD pipeline using Gitea, ensuring data sovereignty and rapid release efficiency.\n- Lead Mobile Engineer — Tripz | Jan 2024 – Dec 2024 | Cairo, Egypt (Remote) (alternate framing for startup/founding roles: Founding Mobile Architect)\n • Solely architected, built and launched a complete ride-hailing ecosystem (Rider, Driver, Admin, Customer Support apps plus backend API) in under four months, scaling to 2,464 riders and 4,318 drivers.\n • Designed the real-time dispatch and live tracking architecture on WebSockets with Redis-backed state, sustaining continuous driver location streams over high-latency MENA mobile networks.\n • Cut third-party mapping API spend by approximately 80% — over $10,000/month — by self-hosting map tiles, routing logic and polyline generation on dedicated servers.\n • Replaced traditional call-center hardware by engineering a secure, mobile-first Customer Support application, enabling support agents to operate entirely remotely.\n • Automated driver onboarding through AI-powered KYC integration, reducing manual review time and minimizing fraudulent registrations.\n • Implemented API-level HMAC security, anti-tampering mechanisms and Fake GPS detection to protect geo-spatial and financial data.\n • Collaborated closely with business stakeholders and operations teams to align product and architecture decisions with on-the-ground market realities.\n- Lead Mobile Engineer (Flutter / Native) — Independent Consultant | Jan 2017 – Dec 2023 | Various Clients — MENA & Remote\n • Delivered 30+ production-grade mobile applications across e-commerce, delivery, healthcare and news sectors, acting as technical decision-maker for early-stage clients.\n • Migrated legacy Android (Java) applications to Flutter, cutting maintenance complexity and delivery timelines by roughly 50%.\n • Owned App Store and Google Play release management end-to-end, from store listings and review submission through versioned production rollout.\n • Built custom Flutter plugins and tailored API layers to integrate with complex legacy backend systems.\n\nOPEN SOURCE (published, verifiable):\n- intaleq_maps (Flutter SDK — pub.dev) — pub.dev/packages/intaleq_maps. Map rendering, offline tile caching and route plotting, optimised for low-bandwidth environments.\n- intaleq-maps-gl (JavaScript library — npm) — npmjs.com/package/intaleq-maps-gl. Mapbox GL compatible web library for custom OSM tiles and routing integration.\n\nEDUCATION:\n- BSc Mathematics, Mu'tah University, Jordan. Applied mathematical foundations to state-machine design and geo-spatial routing.\n- IBM Data Analyst Professional Certificate\n- Google Data Analytics Professional Certificate\n- IBM Data Analytics with Excel and R Specialization\n\nCAPABILITY BOUNDARIES (do not overstate):\n STRONG IN:\n + Flutter / Dart mobile architecture at production scale\n + Real-time systems: WebSockets, dispatching, live tracking\n + Self-hosted geo infrastructure: tiles, routing, polylines\n + Payment gateway integration and constrained-market fintech workarounds\n + Mobile-layer security: HMAC, anti-tampering, Fake GPS detection\n + PHP / Laravel / MySQL backend supporting mobile products\n + Docker, Linux, Nginx, self-hosted CI/CD\n LIMITED / NOT EXPERIENCED IN:\n - NO formal people management: no direct reports, no hiring, no performance reviews. Both platforms were architected and built SOLO. Leadership here is technical — architecture ownership, end-to-end delivery, stakeholder alignment — never headcount. Do not frame this profile for Engineering Manager roles.\n - AI is limited to INTEGRATING third-party vision/LLM APIs (KYC automation, Gemini API). No model training, no MLOps, no deep learning research.\n - Cloud experience is self-hosted/VPS-centric — not deep AWS/GCP/Azure managed-service experience.\n - No Kubernetes in production.\n - No native Swift/SwiftUI or Kotlin-first development — Flutter is the primary mobile path.\n - No data engineering (Spark, Hadoop, Airflow, warehouses).\n NEVER CLAIM: TensorFlow, PyTorch, Scikit-learn, Spark, Hadoop, Kubernetes, MLOps, model training, NLP research, Swift, SwiftUI, Kotlin Multiplatform, blockchain, Salesforce, SAP\n\nLANGUAGES: Arabic (Native), English (Professional Working Proficiency)\nLOCATION: Amman, Jordan — Available immediately. Open to Remote, Hybrid and Relocation (GCC / MENA / Europe).\nCONTACT: hamzaayedflutter@gmail.com | +962 798 583 052 | linkedin.com/in/hamza-ayed | github.com/Hamza-Ayed | intaleqapp.com/hamza.html" } \ No newline at end of file