# Data Model Review — Job Tracker **Companion to:** [SYSTEM_AUDIT_REPORT.md](SYSTEM_AUDIT_REPORT.md) ## 1. Entities (verified in `Models/`) `JobApplication`, `Company`, `Correspondence`, `Attachment`, `JobEvent`, `TailoredCvDraft`(+`Json`), `StructuredCvProfile`(+`Json`), `CvExtraction`, `GmailConnection`, `GmailReviewDecision`, `RuleSettings`, `UserRuleSettings`, `HumanLanguageCatalog`, `SystemEmailSettings`, `ApplicationUser`. ## 2. `JobApplication` — the god entity `[Design flaw]` ~40 columns spanning **eight** distinct concerns on one row: 1. Identity/ownership (`Id`, `OwnerUserId`) 2. Core role (`JobTitle`, `CompanyId`, `Status`, `DateApplied`, `Location`) 3. Salary — **both** free-text (`Salary`) *and* structured (`SalaryMin/Max/Currency/Period`) 4. Workflow (`NextAction`, `FollowUpAt`, `FeedbackRequestedAt`, `RecruiterMessageDraft`) 5. **Denormalised attachment flags** (`HasResume`, `HasCoverLetter`, `HasPortfolio`, `HasOtherAttachment`) 6. Soft delete (`IsDeleted`, `DeletedAt`) 7. Imported content (`Description`, `TranslatedDescription`, `DescriptionLanguage`, `Tags`, `Deadline`, `ShortSummary`) 8. Tailored CV — **both** inline (`TailoredCvText`, `TailoredCvUpdatedAt`) *and* related (`TailoredCvDraft`) ### 2.1 Denormalisation hazard `[Bug risk — High]` `HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment` duplicate information already derivable from the `Attachments` collection. Any code path that adds/removes an attachment without updating the boolean (or vice-versa) produces a **silent inconsistency** that the attachment-checklist UI will display wrong. These booleans should be **computed projections**, not stored state. If kept for query performance, they must be maintained in one place (a domain method) — verify no controller mutates them independently. ### 2.2 Dual tailored-CV source of truth `[Design flaw — High]` `TailoredCvText` (string on `JobApplication`) vs `TailoredCvDraft`/`TailoredCvDraftJson` (related entities). Two writable representations of "the tailored CV for this job" with no documented precedence. This is a classic bug incubator: read one, write the other, and the workspace shows stale content. ### 2.3 CV "versioning" is not modelled `[Design flaw — Medium]` Product step 8 promises *"CV version is linked to job."* The schema stores a **single current** tailored text per job, not a **version history**. There is no `CvVersion` table with immutable snapshots. The promised capability is only partially real. If versioning matters (it should, for A/B and audit), model it explicitly: `CvVersion(id, ownerUserId, sourceProfileId, jobApplicationId?, content, createdAt, label)`. ## 3. Relationships - `JobApplication *→1 Company` (FK `CompanyId`) — fine. - `JobApplication 1→* Correspondence / Attachment / JobEvent` — fine, but **FK columns are unindexed** (`Correspondence.JobApplicationId`, `JobEvent.JobApplicationId`) → N+1 and slow joins. - `Correspondence.ExternalThreadId` powers Gmail continuity (D007/D008) — good, but unindexed. ## 4. Indexing `[Performance — High]` Only `OwnerUserId` is indexed. Every list/board/reminders/analytics query filters on `IsDeleted` (unindexed), reminders/background jobs filter on `FollowUpAt` (unindexed), and detail loads join on the unindexed child FKs. **Add:** `IsDeleted`, `(IsDeleted, Status)`, `FollowUpAt`, `Correspondence.JobApplicationId`, `JobEvent.JobApplicationId`. SQLite- and MySQL-safe. ## 5. Tags/JSON-as-string `[Design flaw — Low]` `Tags` is a JSON-array string; `TailoredCvDraftJson`/`StructuredCvProfileJson` are JSON blobs. Workable with EF value converters, but unqueryable. Acceptable given SQLite; revisit if moving fully to MySQL/Postgres (use native JSON columns). ## 6. Recommended target schema (incremental) 1. Split `JobApplication` into `JobApplication` (core+workflow) + `JobImportContent` (description/translation/ summary/tags) — a 1:1 owned entity — so wide read paths don't drag import blobs. 2. Make attachment booleans computed (drop stored columns after a migration + backfill check). 3. Pick **one** tailored-CV representation (`TailoredCvDraft`) and deprecate `TailoredCvText`. 4. Introduce `CvVersion` for real versioning. 5. Add the five hot-path indexes (do this first — highest value, lowest risk). All five are additive/behaviour-preserving migrations guarded by the existing test suite.