{"record":{"id":"b53c5ed6f2487b51","repo":"srbhr/Resume-Matcher","slug":"original-resume-missing-personalinfo","errorCode":null,"errorMessage":"Original resume missing personalInfo","messagePattern":"Original resume missing personalInfo","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"apps/backend/app/routers/resumes.py","lineNumber":534,"sourceCode":"    except Exception as e:\n        logger.warning(\"Skipping resume diff due to calculation failure: %s\", e)\n        return None, None, f\"calculation_error: {str(e)}\"\n\n\ndef _validate_confirm_payload(\n    original_data: dict[str, Any] | None,\n    improved_data: dict[str, Any],\n) -> None:\n    if not original_data:\n        logger.warning(\n            \"Skipping confirm payload validation; structured resume data unavailable.\"\n        )\n        return\n    original_info = original_data.get(\"personalInfo\")\n    improved_info = improved_data.get(\"personalInfo\")\n    # JSON-008: Explicit null checks with clear error messages\n    if original_info is None:\n        raise ValueError(\"Original resume missing personalInfo\")\n    if improved_info is None:\n        raise ValueError(\"Improved resume missing personalInfo\")\n    if not isinstance(original_info, dict):\n        raise ValueError(\n            f\"Original personalInfo is not a dict: {type(original_info).__name__}\"\n        )\n    if not isinstance(improved_info, dict):\n        raise ValueError(\n            f\"Improved personalInfo is not a dict: {type(improved_info).__name__}\"\n        )\n    fields = set(original_info.keys()) | set(improved_info.keys())\n    mismatches = [\n        field\n        for field in sorted(fields)\n        if _normalize_personal_info_value(original_info.get(field))\n        != _normalize_personal_info_value(improved_info.get(field))\n    ]\n    if mismatches:","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L516-L552","documentation":"Part of the JSON-008 hardening in _validate_confirm_payload: before persisting a tailored resume, the confirm endpoint compares the original stored resume's processed_data.personalInfo with the improved data's personalInfo. If the original resume has no personalInfo key (or it is null), this ValueError is raised because the immutability check cannot be performed. The endpoint converts it into an HTTP 400 for the client.","triggerScenarios":"POST /resumes/improve/confirm with a resume_id whose stored processed_data lacks personalInfo or has it explicitly set to null — typical for older records parsed before personalInfo was part of the schema, or JSON resumes uploaded without a personalInfo object.","commonSituations":"Legacy database rows migrated from database.json with a partial schema; JSON-uploaded resumes missing the personalInfo field; data edited directly in SQLite removing the key.","solutions":["Re-upload the resume or call /{id}/retry-processing so processed_data is regenerated with a personalInfo object","Backfill personalInfo in the stored processed_data via PATCH /resumes/{id}","Confirm against a different (valid) master resume"],"exampleFix":"// before: stored JSON resume without personalInfo\n{\"personalInfo\": null, \"summary\": \"...\"}\n// after: backfilled processed_data\n{\"personalInfo\": {\"name\": \"Jane Doe\", \"email\": \"jane@example.com\", \"phone\": \"+1...\", \"location\": \"...\"}, \"summary\": \"...\"}","handlingStrategy":"validation","validationCode":"// client: ensure the resume has personalInfo before confirming\ndef assert_confirmable(original: dict):\n    if not isinstance(original.get(\"personalInfo\"), dict):\n        raise ValueError(\"stored resume lacks personalInfo; re-upload or retry-processing first\")","typeGuard":"def has_personal_info(data: dict) -> bool:\n    return isinstance(data.get(\"personalInfo\"), dict)","tryCatchPattern":"try:\n    resp = api.post('/resumes/improve/confirm', payload)\nexcept HTTPError as e:\n    if e.response.status_code == 400 and 'missing personalInfo' in e.response.text:\n        trigger_reprocess(resume_id)  # regenerate processed_data","preventionTips":["Re-upload or retry-processing legacy resumes imported from database.json before tailoring them","Never hand-edit processed_data in SQLite","Keep personalInfo present in any JSON resume uploads"],"tags":["validation","confirm-endpoint","personal-info","data-migration"],"backgroundTag":"missing-required-field","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}