# Task 015: SEO And Language Review Development description: Implement SEO and linguistic review jobs, issue reports, editor accept/reject/edit flows, and versioned application of accepted suggestions. ## Implementation Details - Backend endpoints: - `POST /api/articles/{article_id}/seo/review` - `GET /api/articles/{article_id}/seo/report` - `POST /api/articles/{article_id}/language/review` - `GET /api/articles/{article_id}/language/report` - endpoints to accept/reject/edit individual suggestions. - SEO checks: - Title length. - Meta title length. - Meta description length. - H1/H2 structure. - Keyword placement. - Internal links. - External citations. - Schema type. - Slug. - FAQ eligibility. - Image alt text. - Readability. - Duplicate headings. - Language checks: - Grammar. - Spelling. - Sentence length. - Passive voice. - Jargon density. - Brand tone. - Forbidden phrases. - Repetition. - Weak claims. - Unsupported certainty. - CTA consistency. - Accepted suggestions create a new draft version when they change approval-relevant content. ## Public Interface - Editor runs SEO and language review. - Editor accepts, rejects, or edits suggestions. - UI shows issues with severity and locations. ## Acceptance Criteria - [x] TDD pre-requirement: before implementation, write one failing behavior test that runs an SEO review and returns a visible issue through the public API; proceed one review behavior at a time and record evidence in `Result`. - [x] SEO report includes score, issues, suggested fixes, recommended slug/title, and schema JSON. - [x] Language report includes severity, location, message, and suggested rewrite. - [x] Editor can accept, reject, or edit each suggestion. - [x] Accepted content changes create a new immutable draft version. - [x] Target-site SEO and tone rules override global defaults. - [x] Final review can show unresolved SEO/language issues. - [x] Fake review runner produces deterministic demo reports. ## Verification - Run review API tests. - Run draft-versioning tests for accepted suggestions. - Run frontend review UI tests. - Run Docker Compose smoke flow from draft to SEO/language reports. ## Result - Status: Completed (pre-requirement RED + implementation GREEN). - TDD plan (done): 1. Prepare article through public flow to assembled draft state. 2. Run SEO and language review via public endpoints. 3. Fetch reports and verify issue visibility through API. 4. Apply suggestion actions (accept/reject/edit) and verify immutable draft versioning. 5. Verify unresolved issues in final review context and deterministic fake-runner behavior. - Red evidence: - Pre-requirement test initially failed with `404 Not Found` on `POST /api/articles/{article_id}/seo/review`. - Green evidence: - Added review domain contracts, persistence tables, repository, application service, and routes: - `POST /api/articles/{article_id}/seo/review` - `GET /api/articles/{article_id}/seo/report` - `POST /api/articles/{article_id}/language/review` - `GET /api/articles/{article_id}/language/report` - suggestion actions: - `POST /api/articles/{article_id}/seo/suggestions/{suggestion_id}/accept|reject|edit` - `POST /api/articles/{article_id}/language/suggestions/{suggestion_id}/accept|reject|edit` - unresolved issues endpoint: - `GET /api/articles/{article_id}/final-review/issues` - Reports are generated deterministically from assembled draft + site rules and stored in review tables. - Accept/edit actions that modify content/metadata create a new immutable draft version through existing draft versioning flow. - Site-level overrides are applied from `target_sites.seo_rules` (`seo_review` / `language_review`) over defaults. - Added frontend review feature model + UI and API bindings. - Regenerated shared contracts: - `packages/shared/openapi.json` - `packages/shared/src/api-types.ts` - Refactor notes: - `ContentReviewReportSummary.schema_json` currently emits a pydantic shadow warning; behavior is correct and contracts remain stable. - Verification output: - `PYTHONPATH=/private/tmp/pupline-backend-deps python3 -m unittest apps/backend/tests/integration/test_seo_language_review_public_api.py` -> `OK (6 tests)` - `PYTHONPATH=/private/tmp/pupline-backend-deps python3 -m unittest apps/backend/tests/integration/test_draft_assembly_public_api.py` -> `OK (5 tests)` - `PYTHONPATH=/private/tmp/pupline-backend-deps python3 -m unittest apps/backend/tests/integration/test_assets_media_library_public_api.py` -> `OK (7 tests)` - `node apps/frontend/tests/draft_editor.model.test.mjs` -> `OK` - `node apps/frontend/tests/article_assets.model.test.mjs` -> `OK` - `node apps/frontend/tests/article_review.model.test.mjs` -> `OK` - `pnpm --dir apps/frontend typecheck` -> `OK`