{"slug": "architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for", "title": "Architectural Breakdown: I Asked AI to Improve My Resume. It Started Asking Me for Numbers Instead.", "summary": "A developer built a production-grade resume optimization framework that forces every resume bullet into a structured, numeric format before any language model processes it, arguing that prose-only prompts yield generic output. The system defines a ResumeBullet dataclass capturing action verb, metric type, before/after values, timeframe and context, then applies a deterministic four-dimension scoring function covering quantification strength, verb specificity, temporal precision and impact scope ahead of the LLM stage.", "body_md": "Most people treat AI resume tools like a magic wand. They paste their draft, hit generate, and hope for better phrasing. But when you actually push a language model into doing meaningful optimization work, it quickly becomes clear that prose alone is insufficient. The model starts asking for quantitative signals because that is what it needs to make decisions that matter.\n\nThis realization changed how I approach automated resume optimization entirely. What follows is not a beginner's tutorial. It is a production-grade framework for building a system that forces your resume through real metrics before any AI touches it.\n\nA standard prompt looks something like this:\n\n```\n# DON'T do this - pure text input leads to generic output\nresponse = client.chat.completions.create(\n    model=\"gpt-4o\",\n    messages=[\n        {\"role\": \"user\", \"content\": \"Improve my resume bullet points.\"}\n    ]\n)\n```\n\nThe output will always be vague advice wrapped in corporate language. Phrases like \"synergized cross-functional teams\" or \"spearheaded initiative\" are exactly the kind of noise that makes resumes unreadable to both humans and applicant tracking systems. Without numbers anchoring each claim, the model has no signal to ground its improvements.\n\nThe shift happens when you force every resume bullet into a structured numeric format before it ever reaches the language model:\n\n``` python\nfrom dataclasses import dataclass\nfrom typing import Optional\n\n@dataclass\nclass ResumeBullet:\n    action_verb: str          # \"Led\", \"Built\", \"Reduced\"\n    metric_type: str          # \"percentage\", \"absolute\", \"ratio\"\n    value_before: Optional[float]  # baseline measurement\n    value_after: Optional[float]   # outcome measurement\n    timeframe: Optional[str]       # \"Q3 2024\", \"6 months\"\n    context: str                 # the what and why\n\n    @property\n    def has_numbers(self) -> bool:\n        # A bullet without quantification should be flagged\n        # before any LLM processing begins\n        return self.value_before is not None and self.value_after is not None\n\n    @property\n    def impact_ratio(self) -> Optional[float]:\n        if not self.has_numbers:\n            return None\n        return (self.value_after - self.value_before) / abs(self.value_before)\n```\n\nThis structure forces a painful but necessary step: you must extract or estimate the real numbers behind every claim. That exercise alone improves your resume more than any AI paraphrase ever could.\n\nOnce you have quantified bullets, you can build a deterministic scoring function that runs before the LLM stage:\n\n``` php\nimport json\n\ndef calculate_bullet_score(bullet: ResumeBullet) -> dict:\n    \"\"\"\n    Produces a composite score from four independent dimensions.\n    Higher scores indicate stronger, more credible accomplishments.\n    \"\"\"\n    scores = {}\n\n    # Dimension 1: Quantification strength\n    if bullet.has_numbers:\n        scores[\"quantification\"] = min(bullet.impact_ratio * 10, 100)\n    else:\n        scores[\"quantification\"] = 0  # No numbers means this slot is empty\n\n    # Dimension 2: Action verb specificity\n    strong_verbs = {\n        \"built\": 90, \"architected\": 95, \"designed\": 85,\n        \"reduced\": 88, \"optimized\": 82, \"launched\": 78,\n        \"led\": 70, \"managed\": 55, \"helped\": 30\n    }\n    scores[\"verb_strength\"] = strong_verbs.get(bullet.action_verb.lower(), 40)\n\n    # Dimension 3: Time-bound credibility\n    score_time = 50 if bullet.timeframe else 0\n    if bullet.timeframe and \"quarter\" in bullet.timeframe.lower():\n        score_time = 70\n    if bullet.timeframe and \"month\" in bullet.timeframe.lower():\n        score_time = 85\n    scores[\"temporal_precision\"] = score_time\n\n    # Dimension 4: Scale of impact\n    scale_scores = {\"team\": 50, \"department\": 70, \"company\": 90, \"individual\": 30}\n    scores[\"scope_score\"] = scale_scores.get(bullet.context.split()[0].lower(), 40)\n\n    composite = sum(scores.values()) / len(scores)\n    return {\"scores\": scores, \"composite\": round(composite, 1)}\n```\n\nThis pipeline gives you something most resume tools never provide: a transparent audit trail showing exactly which bullet points are weak and why.\n\nHere is the architecture that actually works in practice:\n\n``` php\ndef optimize_resume_stage_one(raw_bullets: list[dict]) -> list[ResumeBullet]:\n    \"\"\"\n    STAGE 1: Structural enforcement.\n    Converts freeform bullets into quantified objects.\n    Rejects or flags any bullet missing hard numbers.\n    \"\"\"\n    structured = []\n    for raw in raw_bullets:\n        bullet = ResumeBullet(\n            action_verb=raw.get(\"verb\", \"\"),\n            metric_type=raw.get(\"type\", \"\"),\n            value_before=raw.get(\"before\"),\n            value_after=raw.get(\"after\"),\n            timeframe=raw.get(\"timeframe\"),\n            context=raw.get(\"context\", \"\")\n        )\n        if not bullet.has_numbers:\n            # Flag for manual review instead of silently proceeding\n            print(f\"[FLAG] Needs quantification: {bullet.context}\")\n        structured.append(bullet)\n    return structured\n\ndef optimize_resume_stage_two(\n    bullets: list[ResumeBullet],\n    client,\n    job_description: str\n) -> list[str]:\n    \"\"\"\n    STAGE 2: LLM rewriting.\n    The model now has concrete numbers to preserve and emphasize.\n    It rephrases around the data instead of inventing fluff.\n    \"\"\"\n    prompt = f\"\"\"\n    Optimize these quantified resume bullets for ATS readability.\n    Job description: {job_description}\n\n    RULES:\n    1. NEVER remove or soften existing numbers\n    2. Replace weak verbs with specific action terms\n    3. Keep each bullet under 2 lines\n    4. Front-load the metric whenever possible\n\n    Bullets:\n    {[f\"{b.action_verb} | {b.value_before} → {b.value_after} | {b.context}\"\n      for b in bullets]}\n\n    Return ONLY the optimized bullets as a JSON array.\n    \"\"\"\n    response = client.chat.completions.create(\n        model=\"claude-sonnet-4-20250514\",\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        temperature=0.3  # Low temperature preserves factual accuracy\n    )\n    return json.loads(response.choices[0].message.content)\n```\n\nWhen I ran this two-stage system against my own resume, the results were startling. Stage One flagged seven out of eleven bullets as missing hard numbers. Some of those gaps were honest oversights. Others were deliberate choices I had made because quantifying felt harder than writing.\n\nThe model did not need to invent metrics. It needed the original data point to exist in the first place. Once those numbers were present, Stage Two produced dramatically different quality output. The AI stopped padding language and started sharpening it.\n\n```\n# Example transformation with actual numbers\nbefore = \"Improved API response times significantly\"\nafter_optimized = \"Reduced p99 API latency from 840ms to 120ms by implementing Redis caching layer and query batch optimization\"\n\n# The second bullet is objectively better because it preserves the signal.\n# AI amplifies signal. It cannot create it from silence.\n```\n\nThe lesson extends far beyond resume writing. Any time you ask an AI to improve something, the quality of your output is bounded by the quality of your structured input. Garbage in produces polished garbage. Numbers in produces sharper output.\n\nStart by auditing every claim on your resume against this simple question: can I attach a measurable number to this statement? If the answer is no, you have found the exact bullet point that is weakening your entire document. Fix it there first. Then let the AI handle the language.\n\nThe system below captures the full pipeline end to end:\n\n``` python\ndef full_resume_pipeline(\n    bullets: list[dict],\n    job_desc: str,\n    client\n) -> dict:\n    \"\"\"\n    Complete optimization pipeline from raw input to scored output.\n    \"\"\"\n    stage_one = optimize_resume_stage_one(bullets)\n    scores = [calculate_bullet_score(b) for b in stage_one]\n\n    # Filter out bullets scoring below 40 before sending to LLM\n    qualified = [\n        b for b, s in zip(stage_one, scores)\n        if s[\"composite\"] >= 40\n    ]\n    low_scoring = [\n        b for b, s in zip(stage_one, scores)\n        if s[\"composite\"] < 40\n    ]\n\n    stage_two = optimize_resume_stage_two(qualified, client, job_desc)\n\n    return {\n        \"qualified_bullets\": stage_two,\n        \"flagged_for_review\": [b.context for b in low_scoring],\n        \"score_summary\": {s[\"composite\"] for s in scores}\n    }\n```\n\nQuantify first. Optimize second. The order matters more than most people realize.\n\n**What is one bullet point on your resume right now that feels impactful but cannot survive being checked against a number?**", "url": "https://wpnews.pro/news/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for", "canonical_source": "https://dev.to/agenticstack/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for-numbers-instead-44dl", "published_at": "2026-09-30 00:08:23+00:00", "updated_at": "2026-09-30 00:16:45.305872+00:00", "lang": "en", "topics": ["ai-tools", "large-language-models", "natural-language-processing"], "entities": ["GPT-4o"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for", "markdown": "https://wpnews.pro/news/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for.md", "text": "https://wpnews.pro/news/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for.txt", "jsonld": "https://wpnews.pro/news/architectural-breakdown-i-asked-ai-to-improve-my-resume-it-started-asking-me-for.jsonld"}}