# GEO Agent Article Optimizer MVP Design

## Goal

Build a lightweight internal web tool for optimizing pasted GEO-related articles while preventing the issues observed in `GEO生成文章改动点(0422).docx`: industry drift, image-text mismatch, third-party voice in official articles, platform mismatch, incomplete company names, title/body grammar issues, hallucinated claims, inconsistent claims, context-insensitive sensitive-word handling, and useless content.

The first version validates the content quality loop before investing in batching, permissions, publishing integrations, or complex document parsing.

## MVP Scope

The MVP is a local web application:

1. User pastes title, body, image descriptions or image links, and selects the target platform.
2. System extracts a candidate fact card.
3. User confirms or edits the fact card.
4. Confirmed fact card is saved as a reusable local brand template.
5. System generates three optimization candidates under fact-card constraints:
   `标准GEO版`, `去AI味版`, and `综合增强版`.
6. System runs quality gates for each candidate.
7. Failed checks trigger targeted rewriting for up to two rounds inside each
   candidate path.
8. System scores each candidate for GEO quality and AI-flavor/style quality.
9. User compares all candidates side by side, with `综合增强版` highlighted as
   the recommended default.
10. User downloads Markdown, Word, and QA artifacts for the selected candidate
    or the full comparison.

## Explicitly Out Of Scope

- Account permissions.
- Multi-user collaboration.
- Publishing platform APIs.
- Batch queues.
- Direct `.docx` upload parsing.
- Complex Word template layout.
- Automatic use of unconfirmed facts.

## User Flow

```mermaid
flowchart TD
  A["Input Article"] --> B["Auto Analyze"]
  B --> C["Confirm Fact Card"]
  C --> D["Generate Three Candidates"]
  D --> E1["标准GEO版 QA"]
  D --> E2["去AI味版 QA"]
  D --> E3["综合增强版 QA"]
  E1 --> F["Compare Results"]
  E2 --> F
  E3 --> F
  F --> G["Select Candidate"]
  G --> H["Download Markdown / Word / QA"]
```

## Page Areas

### Article Input

Fields:

- Title.
- Body.
- Image description or image link.
- Target platform: official site, media article, comparison review, recommendation list.
- User instructions.

The first version accepts pasted text instead of `.docx` upload to avoid early complexity around Word layout parsing.

### Fact Card Confirmation

The system extracts candidate facts, but they are not treated as truth until the user confirms them.

Fields:

- Company full name.
- Company short names.
- Brand names.
- Product names.
- Target industry.
- Target audience.
- Experience years.
- Core claims.
- Forbidden claims.
- Image topics.
- Uncertain items.

The user must resolve uncertain items before optimization starts.

### Candidate Comparison

Display:

- Candidate label: `标准GEO版`, `去AI味版`, or `综合增强版`.
- Method summary that explains what this candidate optimized for.
- Optimized title.
- Summary.
- Optimized body.
- QA status and failed/warned rule count.
- GEO score.
- AI-flavor/style score.
- Timing.
- Changed sections.

The UI should mark content that needs user confirmation and should make
`综合增强版` visually identifiable as the recommended default without hiding the
other two candidates.

### Quality Report

Display each gate as pass, warn, or fail, with evidence, reason, suggested fix, and target rewrite module.

### Export

Downloads:

- Selected candidate: `optimized.md`, `optimized.docx`, `qa_report.json`.
- Full comparison: `variant_comparison.json`.
- If all candidate files are exported together, use stable Chinese labels in the
  metadata and ASCII-safe file names on disk, such as
  `standard-geo.md`, `anti-ai-flavor.md`, and `integrated-enhanced.md`.

## Internal Agent Nodes

The product is delivered as a simple web app, but the internals are split into explicit workflow nodes.

```mermaid
flowchart LR
  A["InputNormalizer"] --> B["FactExtractor"]
  B --> C["UserConfirmedFactCard"]
  C --> D["OptimizationVariantPlanner"]
  D --> E1["标准GEO版 ArticleOptimizer"]
  D --> E2["去AI味版 ArticleOptimizer"]
  D --> E3["综合增强版 ArticleOptimizer"]
  E1 --> F1["QualityInspector + StyleScorer"]
  E2 --> F2["QualityInspector + StyleScorer"]
  E3 --> F3["QualityInspector + StyleScorer"]
  F1 -->|fail| G1["TargetedRewriter"]
  F2 -->|fail| G2["TargetedRewriter"]
  F3 -->|fail| G3["TargetedRewriter"]
  G1 --> F1
  G2 --> F2
  G3 --> F3
  F1 --> H["VariantComparator"]
  F2 --> H
  F3 --> H
  H --> I["Exporter"]
```

### LLM Provider Integration

Workflow nodes use a local LLM client abstraction instead of calling a vendor API directly. The first implementation uses DeepSeek by default, while keeping the provider boundary open for later OpenAI-compatible providers.

Environment variables:

```text
LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-pro
DEEPSEEK_THINKING=disabled
```

Rules:

- `src/lib/llm/client.ts` exposes `generateText`, `generateJson<T>`,
  `generateValidatedJson`, `isLlmConfigured`, and `getLlmProviderStatus`.
- `LLM_PROVIDER` defaults to `deepseek` when unset.
- DeepSeek is accessed through the OpenAI-compatible SDK with `baseURL` set to `https://api.deepseek.com`.
- `generateJson<T>` must use JSON output mode and prompts that explicitly require valid JSON only.
- Thinking mode is disabled by default for deterministic article rewrites and structured QA output.
- Missing credentials and provider failures surface as explicit API errors; the
  workflow must not silently fall back when the user expects live LLM behavior.
- Provider errors are normalized inside the LLM client before they reach workflow nodes or API routes.

### InputNormalizer

Purpose: normalize page input into clean structured data.

Input:

- Title.
- Body.
- Image descriptions or links.
- Target platform.
- User instructions.

Output:

- `article_draft`
- `image_assets`
- `publish_context`

This node does not optimize content.

### FactExtractor

Purpose: extract candidate facts from the source article.

Output:

- `company_full_name`
- `company_short_name`
- `brand_names`
- `product_names`
- `target_industry`
- `target_audience`
- `experience_years`
- `core_claims`
- `forbidden_claims`
- `image_topics`
- `uncertain_items`

Low-confidence facts must go into `uncertain_items`.

### UserConfirmedFactCard

Purpose: provide hard constraints for all downstream nodes.

Rules:

- No downstream node may invent numbers, qualifications, clients, cases, or experience years outside the confirmed fact card.
- Company and product names must follow the fact card.
- Industry and audience must not drift from the fact card.
- Sensitive words must be handled by context, not removed mechanically.

### ArticleOptimizer

Purpose: improve title, summary, body, structure, and image suggestions under fact-card constraints.

Allowed:

- Improve fluency.
- Fix grammar.
- Adjust structure.
- Improve platform fit.
- Remove useless content.
- Improve transitions.

Forbidden:

- Invent claims.
- Change company or product names.
- Change industry.
- Add exaggerated marketing promises.

### OptimizationVariantPlanner

Purpose: create a fixed set of candidate methods for one confirmed fact card.

The first comparison version always creates exactly three candidates:

| Variant Key | Label | Method |
| --- | --- | --- |
| `standard_geo` | `标准GEO版` | Current GEO rewrite behavior: fact-card fidelity, platform fit, clear structure, and QA repair. |
| `anti_ai_flavor` | `去AI味版` | GEO rewrite plus localized Stop Slop rules to remove AI-flavored prose patterns. |
| `integrated_enhanced` | `综合增强版` | Balances GEO density, factual trust, platform fit, readability, and AI-flavor reduction. |

Candidate generation may run sequentially in the first implementation to avoid
provider rate-limit problems and to keep progress events understandable. The
API response still returns the three candidates together for side-by-side
comparison.

### Stop Slop / AI-Flavor Rules

The `hardikpandya/stop-slop` repository is an MIT-licensed writing skill rather
than an installable runtime dependency. The original rules are mostly English
writing rules, so the product should not copy them blindly into Chinese article
optimization.

The first implementation uses localized guidance derived from the rule intent:

- Remove template openers and filler transitions, such as generic "本文将",
  "值得注意的是", "从某种意义上", and repeated "通过...实现..." chains.
- Replace empty business language with concrete claims already present in the
  fact card.
- Avoid formulaic contrast paragraphs that first list what something is not and
  then reveal what it is.
- Avoid slogan-like short fragments that sound like pull quotes instead of
  article prose.
- Prefer active, specific Chinese sentences with clear actors when the source
  facts identify an actor.
- Vary paragraph length and sentence rhythm without using theatrical emphasis.

If substantial text from the upstream Stop Slop files is copied into this
repository, add the MIT license notice to the committed artifact. A distilled
Chinese rule set written for this product can live in source without vendoring
the upstream repository.

### QualityInspector

Purpose: convert the document's issue list into executable quality gates.

Each check returns:

- `status`: `pass`, `warn`, or `fail`.
- `evidence`: source or optimized text snippet.
- `reason`: why the check passed or failed.
- `suggested_fix`: how to fix it.
- `target_agent`: rewrite target when failed.

### TargetedRewriter

Purpose: fix only failed checks.

Examples:

- Rewrite only the title for title quality failures.
- Adjust only the affected paragraph for body quality failures.
- Normalize company names for fact consistency failures.
- Delete or mark unsupported claims for hallucination risk.
- Warn instead of rewriting when image-text confidence is low.

### StyleScorer

Purpose: score each candidate for AI-flavor risk and naturalness without turning
style concerns into hard QA failures.

The first version returns a 0-50 score with five 0-10 dimensions:

| Dimension | Question |
| --- | --- |
| `directness` | Does the article state concrete points instead of announcing them? |
| `rhythm` | Do sentence and paragraph lengths vary naturally? |
| `specificity` | Are claims concrete and tied to the fact card? |
| `trust` | Does the prose avoid exaggerated intimacy, sweeping claims, and unsupported certainty? |
| `density` | Can obvious filler be removed without losing meaning? |

Scores below 35 should show a visible warning on the candidate card. They should
not block export.

### GeoScore

Purpose: reuse the existing GEO article performance rubric to score each
candidate for fact integrity, platform fit, search intent fit, answer density,
trust signal quality, and readability.

The score is computed from the candidate article plus its QA report. It is a
comparison aid for the three candidates, not a publishing guarantee.

### VariantComparator

Purpose: assemble the candidate list for the API response and frontend.

Each candidate result includes:

- `variant_key`
- `variant_label`
- `method_summary`
- `article`
- `qa_report`
- `geo_score`
- `style_score`
- `rewrite_rounds`
- `timing`
- `is_recommended`

`integrated_enhanced` is recommended by default unless it has a hard QA failure
and another candidate does not.

## Quality Gates

| Rule ID | Issue Prevented | First Version Behavior |
| --- | --- | --- |
| `industry_alignment` | Industry drift | Compare article against fact-card industry and audience. |
| `image_text_match` | Image-text mismatch | Compare image descriptions/topics with nearby article sections. |
| `voice_consistency` | Third-party voice in official articles | Enforce platform-specific tone. |
| `platform_fit` | Wrong article type for platform | Compare style and structure against target platform. |
| `company_name_integrity` | Incomplete company name | Compare against confirmed company full name and allowed short names. |
| `title_quality` | Title grammar issues | Detect awkward, keyword-stuffed, or semantically broken titles. |
| `body_quality` | Body grammar issues | Detect long sentences, unclear references, and broken logic. |
| `hallucination_risk` | Fabricated or misleading claims | Reject claims not traceable to the confirmed fact card. |
| `claim_consistency` | Inconsistent years/products/services | Scan and normalize repeated factual claims. |
| `context_sensitive_terms` | Blind sensitive-word deletion | Warn when wording needs context-aware handling. |

### Hard Fail

- Incomplete or inconsistent company name.
- New numbers, qualifications, customer cases, or other claims outside the fact card.
- Clear industry drift.
- Severe title grammar failure.
- Conflicting experience years, product names, or service names.

### Warn

- Low-confidence image-text match.
- Uncertain sensitive-word context.
- Weak platform fit.
- Overly promotional or low-density paragraphs.

### Auto Fix

- Body grammar.
- Useless content.
- Third-party voice when platform is official site.

## Data Model

The first version uses local SQLite plus an export folder.

The first implementation can reuse the existing `optimized_articles` and
`qa_reports` tables by saving each candidate as a normal article revision with
variant metadata inside the JSON payload. The matching QA report is saved
against that revision. A separate comparison-run table is out of scope until the
product needs historical side-by-side comparison beyond the immediate job
result.

```text
data/
  app.db
  exports/
    job_xxx/
      optimized.md
      optimized.docx
      qa_report.json
      variant_comparison.json
      standard-geo.md
      anti-ai-flavor.md
      integrated-enhanced.md
```

### `brand_template`

Reusable confirmed brand facts.

```json
{
  "id": "brand_xxx",
  "brand_name": "Brand",
  "company_full_name": "Company Ltd.",
  "company_short_names": ["Company"],
  "product_names": ["Product"],
  "target_industries": ["GEO optimization"],
  "target_audience": ["Marketing teams"],
  "verified_claims": ["More than ten years of industry experience"],
  "forbidden_claims": ["Do not claim industry first without proof"],
  "tone_rules": {
    "official_site": "brand first-person or official voice",
    "media": "objective third-party voice"
  },
  "updated_at": "2026-06-16T10:00:00+08:00"
}
```

### `article_job`

One optimization task.

```json
{
  "id": "job_xxx",
  "brand_template_id": "brand_xxx",
  "source_title": "Original title",
  "source_body": "Original body",
  "image_inputs": [
    {
      "type": "description",
      "content": "Product dashboard screenshot"
    }
  ],
  "publish_platform": "official_site",
  "status": "qa_failed",
  "created_at": "2026-06-16T10:05:00+08:00"
}
```

### `fact_card`

Confirmed facts for one job.

```json
{
  "job_id": "job_xxx",
  "source": "auto_extract_then_user_confirmed",
  "company_full_name": "Company Ltd.",
  "product_names": ["Product"],
  "target_industry": "GEO optimization",
  "publish_intent": "official article",
  "locked_claims": ["More than ten years of industry experience"],
  "uncertain_items": [],
  "confirmed_by_user": true
}
```

### `optimized_article`

One revision of optimized content.

```json
{
  "job_id": "job_xxx",
  "revision": 2,
  "variant_key": "integrated_enhanced",
  "variant_label": "综合增强版",
  "method_summary": "同时优化GEO信息密度、事实可信度、平台适配和表达自然度。",
  "title": "Optimized title",
  "summary": "Optimized summary",
  "body_markdown": "Optimized body in Markdown",
  "image_suggestions": [
    {
      "source": "image_1",
      "suggestion": "Use product dashboard screenshot; avoid unrelated people photos"
    }
  ],
  "changed_sections": ["title", "first paragraph"],
  "geo_score": {
    "total": 8.1,
    "rationale": "事实一致性与答案密度较好，平台适配仍需复核。"
  },
  "style_score": {
    "total": 42,
    "dimensions": {
      "directness": 8,
      "rhythm": 8,
      "specificity": 9,
      "trust": 8,
      "density": 9
    },
    "warnings": []
  }
}
```

### `qa_report`

Quality checks for one revision.

```json
{
  "job_id": "job_xxx",
  "revision": 2,
  "overall_status": "warn",
  "checks": [
    {
      "rule_id": "hallucination_risk",
      "status": "pass",
      "evidence": "No new unsupported factual claims found",
      "reason": "All factual claims are traceable to the confirmed fact card",
      "target_agent": null
    }
  ]
}
```

## Error Handling

### Fact Extraction

Optimization is disabled until the user resolves uncertain facts.

Examples:

- Only a company short name is found.
- Multiple product names appear.
- Multiple experience-year claims appear.
- Target industry is unclear.
- Image description is missing.

### QA Failure

Hard failures do not block export in the current product direction. The app must
preserve export files and show visible warnings so the user can review the
candidate manually.

Failed checks trigger targeted rewrite for up to two rounds inside each
candidate. After two failed rounds, the app stops rewriting that candidate and
shows manual review fields.

## Acceptance Criteria

The MVP is complete when:

1. User can paste title, body, image descriptions, and target platform.
2. System can extract a fact card and require user confirmation.
3. Confirmed fact card can be saved and reused as a local brand template.
4. System can generate an optimized article without changing confirmed facts.
5. System can generate exactly three labeled candidates: `标准GEO版`,
   `去AI味版`, and `综合增强版`.
6. System can generate a structured QA report for the 10 quality gates for each
   candidate.
7. System can score and compare the three candidates for AI-flavor/style quality.
8. QA failures and low style scores remain visible but do not remove export links.
9. User can download Markdown, a basic Word document, QA JSON, and a comparison
   JSON artifact.

## Minimum Test Samples

Prepare at least five sample articles:

1. Industry drift sample.
2. Incorrect or incomplete company name sample.
3. Title grammar sample.
4. Conflicting experience-year sample.
5. Image-text mismatch sample.

These samples cover the most important risks from the source document and keep the first validation loop focused.
