# Epic 5: Resume Generation & Output

**Goal:** Users can generate tailored PDF and DOCX resumes with full provenance

**FRs Covered:** FR20, FR21, FR22, FR23, FR24, FR25, FR26, FR27, FR31, FR32, FR33

---

## Story 5.1: Resume Data Model & Template System

As a **developer**,
I want **a structured resume data model and template infrastructure**,
So that **providers can render consistent output across formats**.

**Acceptance Criteria:**

**Given** the models directory exists
**When** I inspect `models/resume.py`
**Then** I find a `ResumeData` model with: contact info, summary, work units, skills
**And** I find a `ResumeSection` model for grouping content

**Given** selected Work Units from a plan
**When** I build a ResumeData instance
**Then** Work Units are transformed into resume-ready format
**And** problem/action/outcome becomes achievement bullets

**Given** the templates directory exists
**When** I inspect `templates/modern.html`
**Then** I find a Jinja2 template for PDF rendering
**And** it uses `{{ resume.name }}`, `{{ resume.sections }}`, etc.

**Given** a ResumeData instance and a template
**When** Jinja2 renders the template
**Then** all placeholders are replaced with actual data
**And** the output is valid HTML

**Given** the template system
**When** I create a new template
**Then** I only need to create HTML/CSS files
**And** the provider automatically discovers and uses them

**Given** executive templates exist (Research-Validated 2026-01-10)
**When** I inspect `templates/executive.html`
**Then** I find a 2-3 page layout optimized for senior professionals
**And** it uses results-first bullet formatting (RAS: Results-Action-Situation)
**And** it displays scope indicators (budget, team size, revenue) when present
**And** it includes a professional summary section

**Given** ATS-safe executive template exists
**When** I inspect `templates/ats-executive.html`
**Then** I find a single-column layout for maximum ATS compatibility
**And** it uses standard section headers (Professional Summary, Work Experience, Skills)
**And** formatting prioritizes parseability over visual design

**Technical Notes:**
- Create `models/resume.py` with ResumeData, ResumeSection
- Create `templates/modern.html` and `templates/modern.css`
- Create `templates/executive.html` and `templates/executive.css` per Architecture Section 1.4
- Create `templates/ats-executive.html` for ATS-optimized single-column layout
- Use Jinja2 for template rendering
- Templates are provider-agnostic (HTML for PDF, could extend for others)
- Executive templates support 2-3 page layouts with scope indicator display (Research-Validated 2026-01-10)

---

## Story 5.2: PDF Provider (WeasyPrint)

As a **user**,
I want **to generate a professional PDF resume**,
So that **I have a polished document ready for submission**.

**Acceptance Criteria:**

**Given** a ResumeData instance
**When** the PDFProvider renders it
**Then** a PDF file is generated
**And** the PDF is properly formatted with styles from CSS

**Given** the modern template
**When** a PDF is generated
**Then** it has professional typography and layout
**And** sections are clearly delineated
**And** it fits standard letter/A4 page sizes

**Given** I run `resume build --format pdf`
**When** the build completes
**Then** `dist/resume.pdf` is created
**And** the file is a valid PDF document

**Given** a Work Unit with a long outcome description
**When** the PDF is generated
**Then** text wraps appropriately
**And** page breaks occur at sensible locations

**Given** the PDF generation
**When** it completes
**Then** it finishes within 5 seconds (NFR2)

**Technical Notes:**
- Create `providers/pdf.py` with PDFProvider class
- Use WeasyPrint for HTML→PDF conversion
- Load CSS from `templates/modern.css`
- Handle fonts and page sizing

---

## Story 5.3: DOCX Provider (python-docx)

As a **user**,
I want **to generate an editable DOCX resume**,
So that **I can make final tweaks or submit where Word format is required**.

**Acceptance Criteria:**

**Given** a ResumeData instance
**When** the DOCXProvider renders it
**Then** a DOCX file is generated
**And** the document has proper Word formatting

**Given** I run `resume build --format docx`
**When** the build completes
**Then** `dist/resume.docx` is created
**And** the file opens correctly in Microsoft Word and Google Docs

**Given** the generated DOCX
**When** I open it in Word
**Then** headings use proper Word heading styles
**And** bullet points are actual Word bullets (not text dashes)
**And** the document is editable

**Given** I run `resume build` without `--format`
**When** the build completes
**Then** both PDF and DOCX are generated by default

**Technical Notes:**
- Create `providers/docx.py` with DOCXProvider class
- Use python-docx for document generation
- Apply consistent styling (fonts, spacing)
- Consider docxtpl for template-based approach

---

## Story 5.4: Build Command

As a **user**,
I want **to generate my resume with a single command**,
So that **I get output files ready for job applications**.

**Acceptance Criteria:**

**Given** I run `resume build --plan plan.yaml`
**When** the build executes
**Then** Work Units specified in the plan are used
**And** output files are generated in `dist/`

**Given** I run `resume build --jd senior-engineer.txt`
**When** the build executes
**Then** an implicit plan is generated (same as `resume plan`)
**And** output files are generated based on that plan

**Given** I run `resume build` with no arguments
**When** the build executes
**Then** an error message explains that `--plan` or `--jd` is required

**Given** I run `resume build --jd file.txt --format pdf`
**When** the build completes
**Then** only PDF output is generated (no DOCX)

**Given** I run `resume build --jd file.txt --output-dir ./applications/google/`
**When** the build completes
**Then** output files are written to the specified directory
**And** the directory is created if it doesn't exist

**Given** the build succeeds
**When** I check the exit code
**Then** it is 0

**Given** the build fails (e.g., missing template)
**When** I check the exit code
**Then** it is non-zero
**And** no partial output files are left in `dist/` (NFR7)

**Technical Notes:**
- Create `commands/build.py` with Click command
- Support `--plan`, `--jd`, `--format`, `--output-dir` flags
- Wire together planner, providers, and file output
- Clean up on failure (atomic writes)

---

## Story 5.5: Manifest & Provenance

As a **user**,
I want **a manifest file with every build**,
So that **I know exactly what went into each resume version**.

**Acceptance Criteria:**

**Given** a build completes successfully
**When** I check the output directory
**Then** I find `manifest.yaml` alongside the resume files

**Given** I inspect the manifest
**When** I read its contents
**Then** I see: timestamp, Work Unit IDs included, JD file hash, template used, scoring weights

**Given** I build two resumes from different JDs
**When** I compare their manifests
**Then** I can see exactly what differed between them

**Given** the manifest includes Work Unit IDs
**When** I review it later
**Then** I can trace back to the exact Work Units used

**Given** the same inputs (JD, Work Units, config)
**When** I run build twice
**Then** the output is identical (NFR5 - deterministic)
**And** manifests have different timestamps but same content hash

**Technical Notes:**
- Create `providers/manifest.py` with ManifestProvider
- Include SHA256 hash of JD content
- Store Work Unit IDs with their scores
- Add resume-as-code version number

---

## Story 5.6: Output Configuration

As a **user**,
I want **to configure output preferences**,
So that **I can customize defaults without CLI flags every time**.

**Acceptance Criteria:**

**Given** I set `output_dir: ./resumes` in `.resume.yaml`
**When** I run `resume build --jd file.txt`
**Then** output goes to `./resumes/` instead of `./dist/`

**Given** I set `default_template: ats-safe` in config
**When** I run `resume build --jd file.txt`
**Then** the ATS-safe template is used
**And** I can override with `--template modern`

**Given** I set `scoring_weights` in config
**When** the ranker runs
**Then** custom weights are applied to ranking factors

**Given** I run `resume config output_dir ./custom`
**When** the command completes
**Then** the project config is updated with the new value

**Given** I run `resume config --list`
**When** the command executes
**Then** I see all current configuration values with their sources

**Technical Notes:**
- Extend `config.py` to support output preferences
- Add `--template` flag to build command
- Support scoring weight configuration (title_weight, skills_weight, etc.)
- Implement `resume config` command for viewing/setting config

---
