{"record":{"id":"4608b965f7f96cb3","repo":"srbhr/Resume-Matcher","slug":"improved-resume-missing-personalinfo","errorCode":null,"errorMessage":"Improved resume missing personalInfo","messagePattern":"Improved resume missing personalInfo","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"apps/backend/app/routers/resumes.py","lineNumber":536,"sourceCode":"        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:\n        raise ValueError(f\"personalInfo fields changed: {', '.join(mismatches)}\")\n","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L518-L554","documentation":"The mirror check of the original-side null guard: the improved resume produced by the pipeline must carry a personalInfo object so it can be compared field-by-field against the original. If the improved data's personalInfo is None, the confirm endpoint raises this ValueError (surfaced as HTTP 400) rather than silently persisting a resume whose identity block was lost.","triggerScenarios":"POST /resumes/improve/confirm where the preview/pipeline output (improved_data from processed/improved payload) has personalInfo missing or null — usually because an LLM or diff-application step dropped the field, or a client tampered with the improved payload.","commonSituations":"LLM returning JSON that omits personalInfo; legacy full-output improve path producing schema drift; client constructing a confirm request body manually instead of echoing the preview result.","solutions":["Re-run POST /resumes/improve/preview so the safety nets (_preserve_personal_info etc.) regenerate a valid personalInfo block","Echo the preview's improved data verbatim in the confirm request; never hand-build it","Check improver/diff code changes if this reproduces deterministically (field block-list may be excluding personalInfo path)"],"exampleFix":"// before: confirm request with improved data missing the identity block\n{\"resume_id\": \"...\", \"job_id\": \"...\", \"improved_data\": {\"summary\": \"...\"}}\n// after: improved_data includes unchanged personalInfo copied from the preview\n{\"resume_id\": \"...\", \"job_id\": \"...\", \"improved_data\": {\"personalInfo\": {\"name\": \"Jane Doe\"}, \"summary\": \"...\"}}","handlingStrategy":"validation","validationCode":"// client: echo the preview's improved data verbatim, never rebuild it\nif (!payload.improved_data?.personalInfo) {\n  throw new Error('confirm payload must include personalInfo from the preview');\n}","typeGuard":"const hasPersonalInfo = (d: unknown): d is { personalInfo: Record<string, unknown> } =>\n  typeof d === 'object' && d !== null && 'personalInfo' in d && typeof (d as any).personalInfo === 'object' && (d as any).personalInfo !== null;","tryCatchPattern":"try {\n  await api.post('/resumes/improve/confirm', body);\n} catch (e) {\n  if (e.response?.status === 400) {\n    // re-run preview and retry with its canonical improved_data\n    const preview = await api.post('/resumes/improve/preview', previewPayload);\n    await api.post('/resumes/improve/confirm', buildConfirm(preview.data));\n  }\n}","preventionTips":["Always construct the confirm body from the preview response, field-for-field","Never let user edits touch the personalInfo block between preview and confirm","Schema-validate improved_data (e.g. with the ResumeData schema) before POSTing"],"tags":["validation","confirm-endpoint","personal-info","llm-output"],"backgroundTag":"missing-required-field","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}