From 2ca5a74fa42754c4ba14f2a601dc5e22ce41b093 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Wed, 5 Aug 2026 16:46:02 +0300 Subject: [PATCH] Auto-deploy: 2026-08-05 16:46:02 --- content.js | 2 + server/cv_template.html | 13 ++- server/generate_cv.php | 177 +++++++++++++++++++++++++--------------- server/profile.json | 2 +- 4 files changed, 123 insertions(+), 71 deletions(-) diff --git a/content.js b/content.js index cf25be0..0c10e45 100644 --- a/content.js +++ b/content.js @@ -592,8 +592,10 @@ // the server had to fall back because the model tried to fabricate. const ats = response.data.ats || {}; let msg = '✅ PDF downloaded'; + if (ats.pages) msg += ` — ${ats.pages}p`; if (ats.score) msg += ` — est. ATS match ${ats.score}%`; if (ats.tailored === false) msg += ' (untailored fallback)'; + if (ats.pages > 2) msg += ' ⚠️ over 2 pages'; if (ats.violations && ats.violations.length) { msg += ` ⚠️ blocked fabricated claims: ${ats.violations.join(', ')}`; } diff --git a/server/cv_template.html b/server/cv_template.html index 5bb4e35..3723f13 100644 --- a/server/cv_template.html +++ b/server/cv_template.html @@ -21,11 +21,13 @@ font-feature-settings: "liga" 0, "clig" 0; letter-spacing: 0; text-rendering: geometricPrecision; - font-size: 10.5pt; + /* Density values are injected by generate_cv.php, which renders, measures + the real page count, and tightens these until the CV fits two pages. */ + font-size: {{FONT_SIZE}}; color: #1a1a1a; - line-height: 1.42; + line-height: {{LINE_HEIGHT}}; margin: 0; - padding: 34px 40px; + padding: {{PAGE_PADDING}}; } h1 { @@ -78,6 +80,11 @@ .skill-line { margin: 0 0 4px 0; } .label { font-weight: bold; } + + /* Headline metrics on one line. As separate bullets they cost four lines to + say what one says, and the master CV's four-column band would extract as + scrambled text in an ATS. */ + .metrics { margin: 0 0 5px 0; } diff --git a/server/generate_cv.php b/server/generate_cv.php index 3cfbcf4..f556772 100644 --- a/server/generate_cv.php +++ b/server/generate_cv.php @@ -286,18 +286,6 @@ PROMPT; . '
' . e($id['linkedin']) . '  |  ' . e($id['portfolio']) . '  |  ' . e($id['productSite']) . '
'; - // Metrics are rendered as one linear list. The master CV shows them as a - // four-column band, which looks good to a human and extracts as garbage in - // an ATS — this is the parser-safe equivalent of the same information. - $achievements = ''; - $skillsBlock = ''; foreach ($profile['skills'] as $group => $items) { $skillsBlock .= '

' . e($group) . ': ' @@ -314,74 +302,127 @@ PROMPT; } } - $experienceBlock = ''; - foreach ($profile['experience'] as $ri => $role) { - $experienceBlock .= '

'; - $experienceBlock .= '

' . e($role['title']) . '

'; - $experienceBlock .= '

' . e($role['company']) . ' — ' . e($role['location']) . '

'; - $experienceBlock .= '

' . e($role['start']) . ' – ' . e($role['end']) . '

'; - - // Prefer the tailored rewrite; fall back to the master bullets whenever - // the model returned nothing usable for this role. - $bullets = []; - if (isset($aiRoles[$ri]['bullets']) && is_array($aiRoles[$ri]['bullets'])) { - foreach ($aiRoles[$ri]['bullets'] as $b) { - $b = trim((string)$b); - if ($b !== '') $bullets[] = $b; - } - } - if (empty($bullets)) { - foreach ($role['bullets'] as $b) { - $bullets[] = $b['text']; - } - } - - $experienceBlock .= '
'; - } - $edu = $profile['education']; $educationBlock = '

' . e($edu['degree']) . ' — ' . e($edu['institution']) . '. ' . e($edu['note']) . '

'; - $certBlock = ''; + $certBlock = '

' . e(implode(' · ', $profile['certifications'])) . '

'; $availability = $market === 'amman' ? 'Based in Amman, Jordan. Available immediately for on-site, hybrid and remote roles.' : $id['availability']; - $html = file_get_contents(__DIR__ . '/cv_template.html'); - $html = strtr($html, [ - '{{FULL_NAME}}' => e($id['fullName']), - '{{JOB_HEADLINE}}' => e($headline), - '{{CONTACT_BLOCK}}' => $contactBlock, - '{{TAILORED_SUMMARY}}' => e($summary), - '{{ACHIEVEMENTS_BLOCK}}' => $achievements, - '{{DYNAMIC_SKILLS}}' => e($coreSkills), - '{{SKILLS_BLOCK}}' => $skillsBlock, - '{{EXPERIENCE_BLOCK}}' => $experienceBlock, - '{{EDUCATION_BLOCK}}' => $educationBlock, - '{{CERTIFICATIONS_BLOCK}}' => $certBlock, - '{{LANGUAGES}}' => e($id['languages']), - '{{LOCATION}}' => e($id['location']), - '{{AVAILABILITY}}' => e($availability), - ]); + // ── Adaptive compaction ───────────────────────────────────────────────── + // A three-page CV for a mobile role reads as an inability to prioritise, + // and recruiters routinely stop at page two. Rather than guess at font + // sizes, render the document, ask Dompdf how many pages it actually + // produced, and tighten until it fits. Each level drops the least valuable + // content first — never a metric, never a role, never a date. + $DENSITY = [ + // bulletCaps are per role, oldest role last. + 0 => ['font' => '10.5pt', 'lh' => '1.42', 'pad' => '34px 40px', 'caps' => [4, 4, 3], 'glance' => true], + 1 => ['font' => '10pt', 'lh' => '1.34', 'pad' => '26px 34px', 'caps' => [4, 4, 2], 'glance' => false], + 2 => ['font' => '9.5pt', 'lh' => '1.28', 'pad' => '22px 30px', 'caps' => [3, 3, 2], 'glance' => false], + ]; + $TARGET_PAGES = 2; + + $template = file_get_contents(__DIR__ . '/cv_template.html'); + + $buildHtml = function (array $d) use ( + $profile, $id, $template, $aiRoles, $headline, $summary, $coreSkills, + $contactBlock, $skillsBlock, $educationBlock, $certBlock, $availability + ) { + // Headline metrics collapse to a single dense line. As four separate + // bullets they cost four lines to say what one line says. + $metricParts = []; + foreach ($profile['headlineMetrics'] as $m) { + $metricParts[] = '' . e($m['value']) . ' ' . e($m['label']); + } + $achievements = '

' . implode('  ·  ', $metricParts) . '

'; + + // The "at a glance" paragraphs restate numbers that already appear in + // the experience bullets, so they are the first thing to go. + if ($d['glance']) { + $achievements .= ''; + } + + $experienceBlock = ''; + foreach ($profile['experience'] as $ri => $role) { + $experienceBlock .= '
'; + $experienceBlock .= '

' . e($role['title']) . '

'; + $experienceBlock .= '

' . e($role['company']) . ' — ' . e($role['location']) . '

'; + $experienceBlock .= '

' . e($role['start']) . ' – ' . e($role['end']) . '

'; + + $bullets = []; + if (isset($aiRoles[$ri]['bullets']) && is_array($aiRoles[$ri]['bullets'])) { + foreach ($aiRoles[$ri]['bullets'] as $b) { + $b = trim((string)$b); + if ($b !== '') $bullets[] = $b; + } + } + if (empty($bullets)) { + foreach ($role['bullets'] as $b) { + $bullets[] = $b['text']; + } + } + + // The prompt asks for 3-4 bullets per role, but instruction-following + // is not a guarantee — the model frequently returns every bullet it + // was given. Enforce the cap here so page count never depends on it. + $cap = $d['caps'][$ri] ?? 2; + $bullets = array_slice($bullets, 0, $cap); + + $experienceBlock .= '
'; + } + + return strtr($template, [ + '{{FONT_SIZE}}' => $d['font'], + '{{LINE_HEIGHT}}' => $d['lh'], + '{{PAGE_PADDING}}' => $d['pad'], + '{{FULL_NAME}}' => e($id['fullName']), + '{{JOB_HEADLINE}}' => e($headline), + '{{CONTACT_BLOCK}}' => $contactBlock, + '{{TAILORED_SUMMARY}}' => e($summary), + '{{ACHIEVEMENTS_BLOCK}}' => $achievements, + '{{DYNAMIC_SKILLS}}' => e($coreSkills), + '{{SKILLS_BLOCK}}' => $skillsBlock, + '{{EXPERIENCE_BLOCK}}' => $experienceBlock, + '{{EDUCATION_BLOCK}}' => $educationBlock, + '{{CERTIFICATIONS_BLOCK}}' => $certBlock, + '{{LANGUAGES}}' => e($id['languages']), + '{{LOCATION}}' => e($id['location']), + '{{AVAILABILITY}}' => e($availability), + ]); + }; try { $options = new Options(); $options->set('isHtml5ParserEnabled', true); $options->set('defaultFont', 'Helvetica'); - $dompdf = new Dompdf($options); - $dompdf->loadHtml($html, 'UTF-8'); - $dompdf->setPaper('A4', 'portrait'); - $dompdf->render(); - $pdfOutput = $dompdf->output(); + + $pdfOutput = null; + $pageCount = null; + $densityUsed = 0; + + foreach ($DENSITY as $level => $d) { + $dompdf = new Dompdf($options); + $dompdf->loadHtml($buildHtml($d), 'UTF-8'); + $dompdf->setPaper('A4', 'portrait'); + $dompdf->render(); + + $pdfOutput = $dompdf->output(); + $pageCount = $dompdf->getCanvas()->get_page_count(); + $densityUsed = $level; + + 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']); @@ -399,6 +440,8 @@ PROMPT; "missing" => $ai['ats_keywords_missing'] ?? [], "tailored" => $integrityClean && !empty($aiRoles), "violations" => $violations, + "pages" => $pageCount, + "density" => $densityUsed, ] ]); } catch (Exception $ex) { diff --git a/server/profile.json b/server/profile.json index 28c33d5..96714f0 100644 --- a/server/profile.json +++ b/server/profile.json @@ -339,6 +339,6 @@ ] }, "_generated": "AUTO-GENERATED from profile_data.js — do not edit by hand. Run: node sync_profile.js", - "_generatedAt": "2026-08-05T13:35:29.708Z", + "_generatedAt": "2026-08-05T13:46:02.670Z", "profileText": "HAMZA AYED — Senior Mobile Architect\nRide-Hailing & Real-Time Systems · FinTech in Emerging Markets\n\nSUMMARY:\nSenior Mobile Architect and Founding Engineer with proven experience building and launching 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- 5,275 — Riders acquired across 2 launches\n- 5,750+ — Drivers onboarded\n- ~80% — Map infrastructure cost cut\n- 8 apps — Shipped across 2 platforms\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), iOS / Android Release Management\n- Real-Time & Geo Systems: WebSockets, Real-Time Dispatching, Live Tracking, Self-Hosted Maps, Map Tiles, Polylines, Routing Engines, Geospatial Queries, High-Latency Network Optimization\n- Backend & DevOps: PHP, Laravel, MySQL, Docker, Linux, Nginx, 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 • 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 • Leveraged fully self-hosted map tiles and routing engines to eliminate dependency on expensive third-party mapping APIs, keeping operational costs near zero.\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- Founding Mobile Architect — Tripz | Jan 2024 – Dec 2024 | Cairo, Egypt (Remote)\n • 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 real-time dispatch and live tracking using WebSockets, optimized for high-latency MENA network environments.\n • Reduced map infrastructure expenses by approximately 80% versus third-party API pricing 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) — Global Tech Projects | Jan 2017 – Dec 2023 | Freelance / Consultant\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 • Built custom Flutter plugins and tailored API layers to integrate with complex legacy backend systems.\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 - 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 | intaleqapp.com/hamza.html" } \ No newline at end of file