# StudyFlow Helper Services Implementation Plan

Last updated: 2026-03-19

This file turns the helper endpoints referenced in the StudyFlow runbook into a concrete implementation plan.

It is intentionally practical:

- what services we need
- what each service owns
- which endpoints should exist first
- what data they exchange
- what to build in what order

This plan assumes the current product shape:

0. user lands on a public marketing page
1. user signs up or signs in
2. user enters a protected app shell
3. user uploads document
4. extract plain text
5. render a friendly HTML workspace
6. enable strict grounded chat
7. optionally generate an AI study pack
8. optionally generate infographic-style study visuals for topics that benefit from them

## Architecture Choice

Use a small service-oriented backend behind `n8n`, not one giant app server.

Recommended runtime split:

- `studyflow-web-app`
  - public landing page + authenticated app shell
- `studyflow-auth`
  - sign-up, sign-in, session management
  - billing/session bridge for Stripe-backed entitlements
- `n8n`
  - orchestration only
- `studyflow-db-api`
  - document metadata, runs, workspaces, conversations, review queue
- `studyflow-extractor`
  - file-to-text extraction
- `studyflow-template-renderer`
  - deterministic non-AI HTML workspace transformation
- `studyflow-retrieval`
  - chunking, indexing, retrieval
- `studyflow-ai-service`
  - grounded chat, optional study-pack generation, and optional infographic generation

Why this split works:

- landing/auth stay separate from document processing
- billing and plan enforcement stay with auth/app state instead of leaking into workflows
- extraction and rendering stay cheap and deterministic
- chat and study-pack logic stay isolated from storage concerns
- premium OCR and infographic generation can be added as explicitly metered premium capabilities
- `n8n` coordinates the process without becoming the business-logic monolith

Companion planning docs:

- `AUTH_AND_APP_SHELL_IMPLEMENTATION_PLAN.md`
- `BILLING_AND_ENTITLEMENTS_PLAN.md`
- `STRIPE_AUTH_ENTITLEMENTS_INTEGRATION_PLAN.md`

## Recommended Implementation Stack

For speed and maintainability, use:

- React + Vite frontend for:
  - landing page
  - auth screens
  - app shell
- Node.js + TypeScript + Express for:
  - `studyflow-auth`
  - `studyflow-db-api`
  - `studyflow-template-renderer`
  - `studyflow-retrieval`
  - `studyflow-ai-service`
- Python only if needed for extraction helpers that have better PDF/DOCX tooling
- PostgreSQL for metadata and chat history
- object storage or filesystem-backed document storage for original files and extracted text blobs

Advanced optional path:

- stronger vision/OCR models can be used later for premium extraction recovery
- official image-generation models can be used later for infographic generation
- I could not verify `nano banana 2` as a current official API model name, so this plan keeps the feature model-agnostic and tied to official vision/image APIs instead

Practical VPS-first version:

- PostgreSQL
- local filesystem for uploaded files and extracted artifacts
- vector search can start simple:
  - Postgres `pgvector`, or
  - a local JSON/chunk index if we want the lightest MVP

## Upload and File-Safety Constraints

Use a strict allowlist for MVP uploads.

Accepted types:

- `text/plain`
- `application/pdf`
- `application/vnd.openxmlformats-officedocument.wordprocessingml.document`

Rejected by default:

- images as primary uploads
- archives
- spreadsheets
- presentations
- HTML files
- unknown binary files

### Validation layers

The app/backend should validate:

1. authenticated user ownership
2. extension allowlist
3. MIME allowlist
4. file-size cap
5. parser/openability check

### Recommended MVP rejection cases

Reject with explicit user-facing messages when:

- file type is unsupported
- file is empty
- file exceeds size cap
- PDF is password-protected
- DOCX is corrupt or unreadable
- extracted text is effectively empty

### Storage safety rule

Do not let downstream services pull arbitrary internet URLs.

Instead:

- uploads should land in user-scoped storage first
- backend should issue short-lived internal or signed URLs only for trusted helper services
- services should process only user-owned storage references

### Suggested first size caps

Keep these as implementation defaults, not permanent pricing promises:

- no-plan trial upload: still intentionally undecided
- paid uploads:
  - `.txt`: `5 MB`
  - `.pdf`: `50 MB`
  - `.docx`: `25 MB`

These are starting guardrails and can be revisited after real usage data.

## Public App Surfaces

The product now needs two clearly separated UI surfaces.

### Public surface

Includes:

- landing page
- value proposition
- pricing teaser
- sign-up
- sign-in

### Private surface

Includes:

- dashboard
- document list
- workspace reader
- grounded chat
- optional study-pack actions
- account/settings

This split matters because the workflows only start after the private surface has authenticated the user.

## Infographic Eligibility System

Infographic generation needs a separate eligibility layer before billing or entitlements are checked.

That means the system should answer two different questions:

1. Is this topic or section structurally suitable for a visual?
2. If yes, is the user allowed to generate that visual?

The first is a content decision.
The second is a plan and credit decision.

Do not merge them.

### Recommended eligibility signals

Each extracted topic or section should be evaluated using signals such as:

- ordered steps or sequence markers
- heading hierarchy
- list density
- comparison language such as `vs`, `compare`, `difference`, `advantages`, `disadvantages`
- process language such as `steps`, `workflow`, `pipeline`, `stages`, `lifecycle`
- relation language such as `causes`, `depends on`, `leads to`, `results in`
- repeated named concepts that can be mapped visually
- sufficient content density to support a faithful diagram

### Recommended eligibility result

Persist something like:

```json
{
  "eligible": true,
  "score": 0.82,
  "reason": "Clear sequence of steps and strong diagram-friendly structure.",
  "visualType": "process_flow",
  "sourceSectionIds": ["sec_02", "sec_03"]
}
```

### Suggested visual types

Keep the first version narrow:

- `process_flow`
- `timeline`
- `comparison`
- `taxonomy`
- `concept_map`
- `layered_model`

### Suggested thresholds

- `score >= 0.70`
  - eligible
- `0.45 <= score < 0.70`
  - borderline
  - keep for future review, but do not expose the action in MVP
- `score < 0.45`
  - not eligible

### MVP UI rule

- not eligible
  - hide infographic action entirely
- eligible + plan disallows
  - show disabled locked action with upgrade treatment
- eligible + plan allows + credits remain
  - show enabled action
- eligible + plan allows + credits exhausted
  - show disabled exhausted-state action

This keeps monetization honest and avoids showing premium controls where the content would never produce a useful visual.

## Service 0: `studyflow-auth`

This service owns identity and sessions.

It should own:

- sign-up
- sign-in
- sign-out
- password reset later
- session issuance
- current-user lookup

It should not own:

- document storage
- workflows
- AI processing

### Recommended MVP auth model

Simplest healthy version:

- email + password
- secure session cookie
- backend session validation on protected endpoints

### Endpoints

#### `POST /auth/signup`

Creates a user account.

#### `POST /auth/login`

Authenticates user and sets a session cookie.

#### `POST /auth/logout`

Clears the active session.

#### `GET /auth/me`

Returns the authenticated user profile for the app shell.

### Minimal user tables

#### `users`

- `id`
- `email`
- `password_hash`
- `created_at`
- `updated_at`

#### `user_sessions`

- `id`
- `user_id`
- `session_token_hash`
- `expires_at`
- `created_at`
- `last_seen_at`

## Service 00: `studyflow-web-app`

This is the product shell the user actually sees.

It owns:

- landing page
- sign-up page
- sign-in page
- protected app routes
- document dashboard
- upload UI
- workspace UI
- grounded chat UI

It should call:

- `studyflow-auth` for identity
- its own backend or `studyflow-db-api` for user-scoped data
- `n8n` only through verified backend routes, never directly from an anonymous browser page

### Required frontend routes

Public:

- `/`
- `/signup`
- `/login`

Private:

- `/app`
- `/app/documents`
- `/app/documents/:documentId`
- `/app/settings`

## Ownership Boundary

Every document feature must be scoped to an authenticated user.

That means:

- uploaded files belong to a user
- extracted text belongs to a user
- retrieval index belongs to a user
- conversation history belongs to a user
- workspace results belong to a user
- optional study-pack results belong to a user

No anonymous public document pipeline should exist.

## Document Versioning Model

Use three levels of identity:

- `document_id`
  - stable user-facing document identity
- `document_version_id`
  - one uploaded file snapshot
- `workspace_version_id`
  - one generated workspace derived from one document version

This should be the default model for:

- extraction
- rendering
- retrieval
- grounded chat
- study-pack generation
- infographic generation

### Current-version rule

Each document should have at most one `current_document_version_id`.

That pointer should only change after the new version finishes processing successfully.

### Re-upload rule

If the user uploads a new file for an existing document:

- create a new `document_version`
- create a new processing run for that version
- do not overwrite the previous version in place
- switch current version only after the new version is ready

### Conversation rule

Conversations should be version-bound by default.

Each conversation message should carry:

- `document_id`
- `document_version_id`

That lets the UI keep saying “chat about this document” while the backend stays honest about which source version grounded the answer.

## MVP Duplicate and Project Rule

For the first shippable version, simplify the user-facing behavior:

- each successfully parsed document becomes one project entry
- each project has one workspace page
- duplicate uploads should be blocked instead of creating parallel visible entries

### Duplicate detection rule

Before processing begins, compute a deterministic fingerprint for the uploaded file.

Recommended inputs:

- normalized file hash of the uploaded binary
- optionally extracted-text hash later for smarter matching

If a matching active project already exists for that user:

- do not create a new project
- do not create a new processing run
- return a duplicate response with the existing project/document id

### User-facing behavior

The app should say:

- this file was already parsed
- open the existing project instead
- delete the existing project first if you want to parse it again

### MVP scope note

Internal versioning may still be useful later, but it should not drive the first user-facing behavior.
For MVP, favor duplicate blocking over version-history complexity.

## Service 1: `studyflow-db-api`

This is the system of record.

It should own:

- user-linked document ownership
- run tracking
- document metadata
- workspace metadata
- extracted text references
- conversation history
- review queue

It should not own:

- extraction logic
- AI generation logic
- retrieval ranking logic

### Endpoints

#### `POST /runs`

Purpose:

- create a processing run when a document pipeline starts
- only after duplicate checks have already passed

Request:

```json
{
  "runId": "doc_456-1710864000000",
  "documentId": "doc_456",
  "userId": "user_123",
  "idempotencyKey": "user_123:doc_456",
  "status": "queued",
  "startedAt": "2026-03-19T16:00:00Z",
  "source": "n8n",
  "workflowType": "document_workspace"
}
```

#### `PATCH /runs/:runId`

Purpose:

- update run status during or after processing

Status values:

- `queued`
- `processing`
- `ready`
- `failed`

Suggested optional stage values inside run metadata:

- `uploading`
- `extracting`
- `rendering`
- `indexing`
- `study_pack_generating`
- `complete`

#### `POST /documents/source`

Purpose:

- persist the normalized extracted document metadata
- for a newly accepted non-duplicate project only

Request:

```json
{
  "documentId": "doc_456",
  "documentVersionId": "docv_001",
  "userId": "user_123",
  "subject": "AI Tokenization",
  "fileName": "ai-tokenization.pdf",
  "mimeType": "application/pdf",
  "pageCount": 200,
  "charCount": 420000,
  "textStoragePath": "/data/studyflow/extracted/doc_456.txt"
}
```

#### `POST /document-workspaces`

Purpose:

- persist the generated workspace payload

This should store:

- `documentVersionId`
- `workspaceVersionId`
- HTML output
- section metadata
- retrieval metadata
- optional study-pack payload
- provider/model metadata if a study-pack was generated

#### `POST /document-workspaces/context`

Purpose:

- load the minimum context needed by grounded chat

This should return:

- document info
- current document version id
- current workspace version id
- workspace readiness state
- retrieval index id or chunk group reference

#### `POST /conversations/history`

Purpose:

- return recent messages for a conversation

#### `POST /conversations/messages`

Purpose:

- persist one user turn plus one assistant turn

#### `POST /review-queue`

Purpose:

- persist workflow failures that need operator review

### Minimal Database Tables

#### `document_runs`

- `id`
- `run_id`
- `document_id`
- `user_id`
- `workflow_type`
- `status`
- `idempotency_key`
- `started_at`
- `completed_at`
- `failed_at`
- `provider`
- `model`
- `generation_time_ms`
- `error_message`
- `stage`
- `stage_message`

#### `documents`

- `id`
- `document_id`
- `user_id`
- `subject`
- `current_document_version_id`
- `file_fingerprint`
- `status`
- `created_at`
- `updated_at`

#### `document_versions`

- `id`
- `document_version_id`
- `document_id`
- `version_number`
- `file_name`
- `mime_type`
- `original_file_path`
- `text_storage_path`
- `page_count`
- `char_count`
- `status`
- `created_at`

#### `document_workspaces`

- `id`
- `document_id`
- `document_version_id`
- `workspace_version_id`
- `user_id`
- `html_storage_path`
- `sections_json`
- `retrieval_index_id`
- `workspace_status`
- `study_pack_json`
- `provider`
- `model`
- `generation_time_ms`
- `created_at`
- `updated_at`

#### `conversation_messages`

- `id`
- `conversation_id`
- `document_id`
- `document_version_id`
- `user_id`
- `role`
- `content`
- `citations_json`
- `provider`
- `model`
- `created_at`

#### `review_queue`

- `id`
- `run_id`
- `workflow`
- `workflow_id`
- `document_id`
- `user_id`
- `failed_node`
- `error_message`
- `stack`
- `status`
- `created_at`

## Service 2: `studyflow-extractor`

This service turns uploaded files into plain text plus metadata.

Supported MVP types:

- `.txt`
- `.pdf`
- `.docx`

### Endpoint

#### `POST /extract`

Input:

- multipart file payload from `n8n`
- metadata fields:
  - `documentId`
  - `userId`
  - `mimeType`
  - `fileName`

Preconditions:

- file already passed auth and allowlist checks
- file already lives in trusted internal storage
- file reference is user-scoped

Output:

```json
{
  "documentId": "doc_456",
  "text": "plain extracted text ...",
  "pageCount": 200,
  "charCount": 420000,
  "mimeType": "application/pdf",
  "fileName": "ai-tokenization.pdf",
  "textStoragePath": "/data/studyflow/extracted/doc_456.txt"
}
```

### Implementation Notes

- `.txt`
  - direct read
- `.pdf`
  - use a reliable text extractor first
  - OCR is out of scope for MVP unless absolutely needed
- `.docx`
  - extract paragraphs/headings cleanly

If extraction quality is poor:

- return structured warnings
- do not silently invent text

If extraction cannot proceed safely:

- return a structured failure reason
- classify whether retry is useful
- do not emit partial fake text just to keep the pipeline moving

### Premium OCR recovery path

For difficult PDFs or image-heavy pages, add an optional premium OCR path:

- use a stronger vision-capable OCR/model path only when baseline extraction quality is poor
- keep it explicitly metered as a premium action
- persist an extraction-quality flag so the app can show when premium OCR was used

## Service 3: `studyflow-template-renderer`

This is the non-AI heart of the product.

It turns extracted text into a user-friendly HTML workspace.

### Endpoint

#### `POST /render-friendly`

Input:

```json
{
  "documentId": "doc_456",
  "subject": "AI Tokenization",
  "text": "plain extracted text ...",
  "pageCount": 200,
  "charCount": 420000,
  "template": "studyflow-friendly-html"
}
```

Output:

```json
{
  "result": {
    "html": "<article>...</article>",
    "sections": [
      {
        "id": "section-1",
        "title": "What tokens are",
        "anchor": "what-tokens-are",
        "summary": "Short section summary"
      }
    ],
    "provider": "non-ai-template",
    "generationTimeMs": 1200
  }
}
```

### Deterministic Transformation Rules

Version 1 should support:

- title detection
- heading inference
- paragraph cleanup
- list detection
- pull-quote/callout inference only when obvious
- table of contents generation
- section anchors
- paragraph chunk ids so chat citations can point back into the workspace

Do not over-design this service into an AI system.

It should be:

- cheap
- repeatable
- explainable

## Service 4: `studyflow-retrieval`

This service prepares the document for grounded chat and returns relevant chunks at question time.

### Endpoints

#### `POST /index`

Input:

```json
{
  "documentId": "doc_456",
  "userId": "user_123",
  "subject": "AI Tokenization",
  "text": "plain extracted text ...",
  "sections": [],
  "chunkTargetChars": 1800
}
```

Output:

```json
{
  "result": {
    "retrievalReady": true,
    "chunksIndexed": 128,
    "indexId": "idx_123"
  }
}
```

#### `POST /retrieve`

Input:

```json
{
  "documentId": "doc_456",
  "userId": "user_123",
  "subject": "AI Tokenization",
  "query": "What does the document say about BPE?",
  "topK": 6,
  "chatMode": "strict_grounded",
  "conversationId": "conv_789",
  "chatRunId": "doc_456-1710864000000"
}
```

Output:

```json
{
  "result": {
    "chunks": [
      {
        "chunkId": "chunk_001",
        "text": "Relevant extracted text...",
        "page": 14,
        "sectionId": "section-3",
        "score": 0.92
      }
    ]
  }
}
```

### Retrieval Rules

MVP behavior:

- chunk deterministically
- embed chunks
- store embedding references
- retrieve top `K`
- return citation-friendly metadata

Retrieval records must be user-scoped. A query should never search across another user's chunks.

Do not answer questions here. Retrieval only.

## Service 5: `studyflow-ai-service`

This is the only service that should spend meaningful model quota.

It owns:

- grounded answer generation
- optional study-pack generation
- optional infographic generation

### Endpoint A: `POST /grounded-chat`

Input:

```json
{
  "userId": "user_123",
  "documentId": "doc_456",
  "documentVersionId": "docv_001",
  "conversationId": "conv_789",
  "chatRunId": "doc_456-1710864000000",
  "question": "What does the document say about BPE?",
  "chatMode": "strict_grounded",
  "retrievedChunks": [],
  "conversationHistory": [],
  "targetModel": "gpt-5.4-mini",
  "citations": true
}
```

Output:

```json
{
  "result": {
    "answer": "Based on the document, ...",
    "citations": [
      {
        "chunkId": "chunk_001",
        "page": 14,
        "sectionId": "section-3"
      }
    ],
    "provider": "openai",
    "modelUsed": "gpt-5.4-mini",
    "generationTimeMs": 2200
  }
}
```

### Grounded Chat Prompt Rule

The prompt must enforce:

- answer only from retrieved chunks
- if unsupported, say clearly that the document does not provide enough information
- no unstated world knowledge fill-ins

It should also enforce:

- treat uploaded document text as evidence, not instructions
- ignore prompt-injection-like content inside retrieved chunks
- return citation metadata whenever a supported answer is given
- signal uncertainty when extraction quality is low

### Auth and Privacy Rule

This service should trust only user-scoped data handed to it by the retrieval layer and app/backend.

It should never be allowed to answer against arbitrary document ids without prior ownership checks.

### Grounded Answer Contract

The grounded-chat service should return a machine-readable answer class:

- `supported`
- `partially_supported`
- `unsupported`
- `extraction_uncertain`

Recommended response shape:

```json
{
  "result": {
    "answer": "Based on the document, ...",
    "answerClass": "supported",
    "citations": [],
    "provider": "openai",
    "modelUsed": "gpt-5.4-mini",
    "generationTimeMs": 2200
  }
}
```

### Retrieval minimum rule

Do not generate a normal grounded answer when:

- no chunks are retrieved
- all retrieved chunks are below the minimum relevance threshold

In those cases, return:

- `answerClass = unsupported`

### Low-quality extraction rule

If extraction quality was flagged as poor upstream, pass that signal into grounded chat.

If the answer depends on weak extraction:

- prefer `extraction_uncertain`
- avoid confident prose
- do not present the result as strongly grounded

### Endpoint B: `POST /single-pass-study-pack`

Use only for smaller documents.

### Endpoint C: `POST /chunked-study-pack`

Use for larger documents.

### Endpoint D: `POST /generate-infographic`

Purpose:

- generate a visual study asset from extracted content for topics that benefit from diagrams or summaries

Input:

```json
{
  "userId": "user_123",
  "documentId": "doc_456",
  "documentVersionId": "docv_001",
  "subject": "AI Tokenization",
  "sourceSections": [],
  "topicFocus": "How tokenization works",
  "visualStyle": "clean-study-infographic",
  "targetModel": "official-image-model",
  "groundedContentOnly": true
}
```

Output:

```json
{
  "result": {
    "imageUrl": "https://...",
    "promptSummary": "Infographic grounded in extracted content",
    "provider": "official-image-provider",
    "modelUsed": "official-image-model",
    "generationTimeMs": 5400
  }
}
```

### Infographic generation rules

- assume the topic or section has already passed eligibility checks
- use only extracted document content
- focus on topics where visuals genuinely help:
  - processes
  - concept maps
  - comparisons
  - timelines
- avoid generic decorative images
- persist the source section ids used to ground the image

### Supporting eligibility endpoint

#### `POST /documents/:documentId/infographic-eligibility`

Purpose:

- evaluate which topics or sections are good infographic candidates before any premium action is exposed

Response:

```json
{
  "documentId": "doc_456",
  "documentVersionId": "docv_001",
  "topics": [
    {
      "topicId": "topic_1",
      "title": "The Tokenization Process",
      "infographicEligibility": {
        "eligible": true,
        "score": 0.82,
        "reason": "Strong process structure with clear sequence markers.",
        "visualType": "process_flow",
        "sourceSectionIds": ["sec_2", "sec_3"]
      }
    }
  ]
}
```

### Model Recommendation

Default:

- grounded chat: `gpt-5.4-mini`
- chunk summarization: `gpt-5.4-mini`
- final premium synthesis: `gpt-5.4-mini`

Premium/deep mode later:

- final synthesis: `gpt-5.4`
- infographic generation: official image-generation model, chosen later based on price and quality

## n8n Workflow-to-Service Mapping

### Document Workspace Pipeline

- triggered from authenticated app/backend upload flow
- `Create Run Record`
  - `studyflow-db-api`
- `Download File`
  - raw file fetch inside n8n
- `Extract Text`
  - `studyflow-extractor`
- `Persist Source Document`
  - `studyflow-db-api`
- `Estimate Workspace Strategy`
  - code node inside n8n
- `Render Friendly HTML`
  - `studyflow-template-renderer`
- `Build Retrieval Index`
  - `studyflow-retrieval`
- `Generate Study Pack (Single Pass)`
  - `studyflow-ai-service`
- `Generate Study Pack (Chunked)`
  - `studyflow-ai-service`
- `Generate Infographic`
  - `studyflow-ai-service`
- `Persist Workspace Result`
  - `studyflow-db-api`
- `Mark Run Success`
  - `studyflow-db-api`

### Grounded Chat Workflow

- triggered from authenticated app/backend chat flow
- `Load Workspace Context`
  - `studyflow-db-api`
- `Retrieve Relevant Chunks`
  - `studyflow-retrieval`
- `Fetch Conversation History`
  - `studyflow-db-api`
- `Generate Grounded Answer`
  - `studyflow-ai-service`
- `Persist Chat Turn`
  - `studyflow-db-api`

## Build Order

This is the safest order.

### Phase 0: landing page and auth shell

Build first:

1. `studyflow-web-app`
2. `studyflow-auth`

Done means:

- public landing page exists
- sign-up/sign-in works
- protected app routes exist
- authenticated user context exists

### Phase 1: storage and document extraction

Build first:

3. `studyflow-db-api`
4. `studyflow-extractor`

Done means:

- can create runs
- can extract `.txt`, `.pdf`, `.docx`
- can persist extracted text metadata

### Phase 2: deterministic workspace transformation

Build next:

5. `studyflow-template-renderer`

Done means:

- a user can upload a file and get a friendly HTML workspace without any AI generation

### Phase 3: retrieval

Build next:

6. `studyflow-retrieval`

Done means:

- chunks can be indexed
- relevant chunks can be retrieved with metadata

### Phase 4: grounded chat

Build next:

7. `studyflow-ai-service` grounded chat endpoint

Done means:

- chat answers are grounded
- unsupported questions return an honest refusal
- citations work

### Phase 5: optional study-pack generation

Build last:

8. study-pack endpoints in `studyflow-ai-service`

Done means:

- the premium AI layer is added without blocking the main product value

### Phase 6: premium OCR and infographic generation

Build last:

9. premium OCR recovery path
10. infographic generation endpoint and asset storage

Done means:

- low-quality extraction can be upgraded selectively
- the app can generate grounded infographic-style visual study aids for eligible topics

## Concrete MVP Scope

If we want the strongest first shippable version, implement only:

- `studyflow-web-app`
- `studyflow-auth`
- `studyflow-db-api`
- `studyflow-extractor`
- `studyflow-template-renderer`
- `studyflow-retrieval`
- `studyflow-ai-service:/grounded-chat`

Do not block launch on:

- flashcards
- quiz
- plan
- premium study-pack generation
- premium OCR recovery
- infographic generation

That gives us a real product sooner:

- landing page
- sign in
- sign up
- upload
- transform
- chat on the document

## Deployment Recommendation

On this VPS:

- run each helper as a host service first for simplicity
- keep them private on localhost
- let `n8n` call them internally
- expose only the public app and `n8n` through Traefik

Suggested ports:

- `studyflow-auth`: `19209`
- `studyflow-db-api`: `19210`
- `studyflow-extractor`: `19211`
- `studyflow-template-renderer`: `19212`
- `studyflow-retrieval`: `19213`
- `studyflow-ai-service`: `19214`

## Final Recommendation

The best immediate implementation target is:

1. deterministic workspace transform
2. strict grounded chat
3. study-pack generation later

That keeps StudyFlow trustworthy, usable, and economically healthier than an AI-first upload pipeline.

## Roadmap Priority

The main implementation sequence now lives in:

- `MASTER_IMPLEMENTATION_ROADMAP.md`

Use this helper-services plan for service boundaries, contracts, and API detail. Do not treat this file as the primary milestone tracker anymore.
