thieuluan commited on
Commit
191645f
·
1 Parent(s): 293f5aa

init first spec

Browse files
.env ADDED
File without changes
.env.example ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copy this file to `.env` and adjust values as needed
2
+
3
+ # Ingestion defaults
4
+ PARSER=pypdf
5
+ CHUNK_SIZE=4000
6
+ CHUNK_OVERLAP=400
7
+ INGEST_TMP_DIR=.tmp/ingestion
8
+
9
+ # Streamlit config overrides (optional)
10
+ STREAMLIT_SERVER_PORT=8501
11
+ STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
.factory/commands/openspec-apply.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description: Implement an approved OpenSpec change and keep tasks in sync.
3
+ argument-hint: change-id
4
+ ---
5
+ <!-- OPENSPEC:START -->
6
+ **Guardrails**
7
+ - Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
8
+ - Keep changes tightly scoped to the requested outcome.
9
+ - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
10
+
11
+ **Steps**
12
+ Track these steps as TODOs and complete them one by one.
13
+ 1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
14
+ 2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
15
+ 3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
16
+ 4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
17
+ 5. Reference `openspec list` or `openspec show <item>` when additional context is required.
18
+
19
+ **Reference**
20
+ - Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
21
+
22
+ $ARGUMENTS
23
+ <!-- OPENSPEC:END -->
.factory/commands/openspec-archive.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description: Archive a deployed OpenSpec change and update specs.
3
+ argument-hint: change-id
4
+ ---
5
+ <!-- OPENSPEC:START -->
6
+ **Guardrails**
7
+ - Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
8
+ - Keep changes tightly scoped to the requested outcome.
9
+ - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
10
+
11
+ **Steps**
12
+ 1. Determine the change ID to archive:
13
+ - If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
14
+ - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
15
+ - Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
16
+ - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
17
+ 2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
18
+ 3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
19
+ 4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
20
+ 5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
21
+
22
+ **Reference**
23
+ - Use `openspec list` to confirm change IDs before archiving.
24
+ - Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
25
+
26
+ $ARGUMENTS
27
+ <!-- OPENSPEC:END -->
.factory/commands/openspec-proposal.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description: Scaffold a new OpenSpec change and validate strictly.
3
+ argument-hint: request or feature description
4
+ ---
5
+ <!-- OPENSPEC:START -->
6
+ **Guardrails**
7
+ - Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
8
+ - Keep changes tightly scoped to the requested outcome.
9
+ - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
10
+ - Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
11
+
12
+ **Steps**
13
+ 1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
14
+ 2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
15
+ 3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
16
+ 4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
17
+ 5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
18
+ 6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
19
+ 7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
20
+
21
+ **Reference**
22
+ - Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
23
+ - Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
24
+ - Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
25
+
26
+ $ARGUMENTS
27
+ <!-- OPENSPEC:END -->
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .venv
2
+ .tmp/
3
+ .DS_Store
4
+ **/__pycache__/
5
+ .coverage
6
+ test-data/**.pdf
AGENTS.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- OPENSPEC:START -->
2
+ # OpenSpec Instructions
3
+
4
+ These instructions are for AI assistants working in this project.
5
+
6
+ Always open `@/openspec/AGENTS.md` when the request:
7
+ - Mentions planning or proposals (words like proposal, spec, change, plan)
8
+ - Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
9
+ - Sounds ambiguous and you need the authoritative spec before coding
10
+
11
+ Use `@/openspec/AGENTS.md` to learn:
12
+ - How to create and apply change proposals
13
+ - Spec format and conventions
14
+ - Project structure and guidelines
15
+
16
+ Keep this managed block so 'openspec update' can refresh the instructions.
17
+
18
+ <!-- OPENSPEC:END -->
openspec/AGENTS.md ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenSpec Instructions
2
+
3
+ Instructions for AI coding assistants using OpenSpec for spec-driven development.
4
+
5
+ ## TL;DR Quick Checklist
6
+
7
+ - Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search)
8
+ - Decide scope: new capability vs modify existing capability
9
+ - Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`)
10
+ - Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability
11
+ - Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement
12
+ - Validate: `openspec validate [change-id] --strict` and fix issues
13
+ - Request approval: Do not start implementation until proposal is approved
14
+
15
+ ## Three-Stage Workflow
16
+
17
+ ### Stage 1: Creating Changes
18
+ Create proposal when you need to:
19
+ - Add features or functionality
20
+ - Make breaking changes (API, schema)
21
+ - Change architecture or patterns
22
+ - Optimize performance (changes behavior)
23
+ - Update security patterns
24
+
25
+ Triggers (examples):
26
+ - "Help me create a change proposal"
27
+ - "Help me plan a change"
28
+ - "Help me create a proposal"
29
+ - "I want to create a spec proposal"
30
+ - "I want to create a spec"
31
+
32
+ Loose matching guidance:
33
+ - Contains one of: `proposal`, `change`, `spec`
34
+ - With one of: `create`, `plan`, `make`, `start`, `help`
35
+
36
+ Skip proposal for:
37
+ - Bug fixes (restore intended behavior)
38
+ - Typos, formatting, comments
39
+ - Dependency updates (non-breaking)
40
+ - Configuration changes
41
+ - Tests for existing behavior
42
+
43
+ **Workflow**
44
+ 1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context.
45
+ 2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes/<id>/`.
46
+ 3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement.
47
+ 4. Run `openspec validate <id> --strict` and resolve any issues before sharing the proposal.
48
+
49
+ ### Stage 2: Implementing Changes
50
+ Track these steps as TODOs and complete them one by one.
51
+ 1. **Read proposal.md** - Understand what's being built
52
+ 2. **Read design.md** (if exists) - Review technical decisions
53
+ 3. **Read tasks.md** - Get implementation checklist
54
+ 4. **Implement tasks sequentially** - Complete in order
55
+ 5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses
56
+ 6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality
57
+ 7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved
58
+
59
+ ### Stage 3: Archiving Changes
60
+ After deployment, create separate PR to:
61
+ - Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/`
62
+ - Update `specs/` if capabilities changed
63
+ - Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
64
+ - Run `openspec validate --strict` to confirm the archived change passes checks
65
+
66
+ ## Before Any Task
67
+
68
+ **Context Checklist:**
69
+ - [ ] Read relevant specs in `specs/[capability]/spec.md`
70
+ - [ ] Check pending changes in `changes/` for conflicts
71
+ - [ ] Read `openspec/project.md` for conventions
72
+ - [ ] Run `openspec list` to see active changes
73
+ - [ ] Run `openspec list --specs` to see existing capabilities
74
+
75
+ **Before Creating Specs:**
76
+ - Always check if capability already exists
77
+ - Prefer modifying existing specs over creating duplicates
78
+ - Use `openspec show [spec]` to review current state
79
+ - If request is ambiguous, ask 1–2 clarifying questions before scaffolding
80
+
81
+ ### Search Guidance
82
+ - Enumerate specs: `openspec spec list --long` (or `--json` for scripts)
83
+ - Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available)
84
+ - Show details:
85
+ - Spec: `openspec show <spec-id> --type spec` (use `--json` for filters)
86
+ - Change: `openspec show <change-id> --json --deltas-only`
87
+ - Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs`
88
+
89
+ ## Quick Start
90
+
91
+ ### CLI Commands
92
+
93
+ ```bash
94
+ # Essential commands
95
+ openspec list # List active changes
96
+ openspec list --specs # List specifications
97
+ openspec show [item] # Display change or spec
98
+ openspec validate [item] # Validate changes or specs
99
+ openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
100
+
101
+ # Project management
102
+ openspec init [path] # Initialize OpenSpec
103
+ openspec update [path] # Update instruction files
104
+
105
+ # Interactive mode
106
+ openspec show # Prompts for selection
107
+ openspec validate # Bulk validation mode
108
+
109
+ # Debugging
110
+ openspec show [change] --json --deltas-only
111
+ openspec validate [change] --strict
112
+ ```
113
+
114
+ ### Command Flags
115
+
116
+ - `--json` - Machine-readable output
117
+ - `--type change|spec` - Disambiguate items
118
+ - `--strict` - Comprehensive validation
119
+ - `--no-interactive` - Disable prompts
120
+ - `--skip-specs` - Archive without spec updates
121
+ - `--yes`/`-y` - Skip confirmation prompts (non-interactive archive)
122
+
123
+ ## Directory Structure
124
+
125
+ ```
126
+ openspec/
127
+ ├── project.md # Project conventions
128
+ ├── specs/ # Current truth - what IS built
129
+ │ └── [capability]/ # Single focused capability
130
+ │ ├── spec.md # Requirements and scenarios
131
+ │ └── design.md # Technical patterns
132
+ ├── changes/ # Proposals - what SHOULD change
133
+ │ ├── [change-name]/
134
+ │ │ ├── proposal.md # Why, what, impact
135
+ │ │ ├── tasks.md # Implementation checklist
136
+ │ │ ├── design.md # Technical decisions (optional; see criteria)
137
+ │ │ └── specs/ # Delta changes
138
+ │ │ └── [capability]/
139
+ │ │ └── spec.md # ADDED/MODIFIED/REMOVED
140
+ │ └── archive/ # Completed changes
141
+ ```
142
+
143
+ ## Creating Change Proposals
144
+
145
+ ### Decision Tree
146
+
147
+ ```
148
+ New request?
149
+ ├─ Bug fix restoring spec behavior? → Fix directly
150
+ ├─ Typo/format/comment? → Fix directly
151
+ ├─ New feature/capability? → Create proposal
152
+ ├─ Breaking change? → Create proposal
153
+ ├─ Architecture change? → Create proposal
154
+ └─ Unclear? → Create proposal (safer)
155
+ ```
156
+
157
+ ### Proposal Structure
158
+
159
+ 1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique)
160
+
161
+ 2. **Write proposal.md:**
162
+ ```markdown
163
+ # Change: [Brief description of change]
164
+
165
+ ## Why
166
+ [1-2 sentences on problem/opportunity]
167
+
168
+ ## What Changes
169
+ - [Bullet list of changes]
170
+ - [Mark breaking changes with **BREAKING**]
171
+
172
+ ## Impact
173
+ - Affected specs: [list capabilities]
174
+ - Affected code: [key files/systems]
175
+ ```
176
+
177
+ 3. **Create spec deltas:** `specs/[capability]/spec.md`
178
+ ```markdown
179
+ ## ADDED Requirements
180
+ ### Requirement: New Feature
181
+ The system SHALL provide...
182
+
183
+ #### Scenario: Success case
184
+ - **WHEN** user performs action
185
+ - **THEN** expected result
186
+
187
+ ## MODIFIED Requirements
188
+ ### Requirement: Existing Feature
189
+ [Complete modified requirement]
190
+
191
+ ## REMOVED Requirements
192
+ ### Requirement: Old Feature
193
+ **Reason**: [Why removing]
194
+ **Migration**: [How to handle]
195
+ ```
196
+ If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs/<capability>/spec.md`—one per capability.
197
+
198
+ 4. **Create tasks.md:**
199
+ ```markdown
200
+ ## 1. Implementation
201
+ - [ ] 1.1 Create database schema
202
+ - [ ] 1.2 Implement API endpoint
203
+ - [ ] 1.3 Add frontend component
204
+ - [ ] 1.4 Write tests
205
+ ```
206
+
207
+ 5. **Create design.md when needed:**
208
+ Create `design.md` if any of the following apply; otherwise omit it:
209
+ - Cross-cutting change (multiple services/modules) or a new architectural pattern
210
+ - New external dependency or significant data model changes
211
+ - Security, performance, or migration complexity
212
+ - Ambiguity that benefits from technical decisions before coding
213
+
214
+ Minimal `design.md` skeleton:
215
+ ```markdown
216
+ ## Context
217
+ [Background, constraints, stakeholders]
218
+
219
+ ## Goals / Non-Goals
220
+ - Goals: [...]
221
+ - Non-Goals: [...]
222
+
223
+ ## Decisions
224
+ - Decision: [What and why]
225
+ - Alternatives considered: [Options + rationale]
226
+
227
+ ## Risks / Trade-offs
228
+ - [Risk] → Mitigation
229
+
230
+ ## Migration Plan
231
+ [Steps, rollback]
232
+
233
+ ## Open Questions
234
+ - [...]
235
+ ```
236
+
237
+ ## Spec File Format
238
+
239
+ ### Critical: Scenario Formatting
240
+
241
+ **CORRECT** (use #### headers):
242
+ ```markdown
243
+ #### Scenario: User login success
244
+ - **WHEN** valid credentials provided
245
+ - **THEN** return JWT token
246
+ ```
247
+
248
+ **WRONG** (don't use bullets or bold):
249
+ ```markdown
250
+ - **Scenario: User login** ❌
251
+ **Scenario**: User login ❌
252
+ ### Scenario: User login ❌
253
+ ```
254
+
255
+ Every requirement MUST have at least one scenario.
256
+
257
+ ### Requirement Wording
258
+ - Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative)
259
+
260
+ ### Delta Operations
261
+
262
+ - `## ADDED Requirements` - New capabilities
263
+ - `## MODIFIED Requirements` - Changed behavior
264
+ - `## REMOVED Requirements` - Deprecated features
265
+ - `## RENAMED Requirements` - Name changes
266
+
267
+ Headers matched with `trim(header)` - whitespace ignored.
268
+
269
+ #### When to use ADDED vs MODIFIED
270
+ - ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement.
271
+ - MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details.
272
+ - RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name.
273
+
274
+ Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead.
275
+
276
+ Authoring a MODIFIED requirement correctly:
277
+ 1) Locate the existing requirement in `openspec/specs/<capability>/spec.md`.
278
+ 2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios).
279
+ 3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior.
280
+ 4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`.
281
+
282
+ Example for RENAMED:
283
+ ```markdown
284
+ ## RENAMED Requirements
285
+ - FROM: `### Requirement: Login`
286
+ - TO: `### Requirement: User Authentication`
287
+ ```
288
+
289
+ ## Troubleshooting
290
+
291
+ ### Common Errors
292
+
293
+ **"Change must have at least one delta"**
294
+ - Check `changes/[name]/specs/` exists with .md files
295
+ - Verify files have operation prefixes (## ADDED Requirements)
296
+
297
+ **"Requirement must have at least one scenario"**
298
+ - Check scenarios use `#### Scenario:` format (4 hashtags)
299
+ - Don't use bullet points or bold for scenario headers
300
+
301
+ **Silent scenario parsing failures**
302
+ - Exact format required: `#### Scenario: Name`
303
+ - Debug with: `openspec show [change] --json --deltas-only`
304
+
305
+ ### Validation Tips
306
+
307
+ ```bash
308
+ # Always use strict mode for comprehensive checks
309
+ openspec validate [change] --strict
310
+
311
+ # Debug delta parsing
312
+ openspec show [change] --json | jq '.deltas'
313
+
314
+ # Check specific requirement
315
+ openspec show [spec] --json -r 1
316
+ ```
317
+
318
+ ## Happy Path Script
319
+
320
+ ```bash
321
+ # 1) Explore current state
322
+ openspec spec list --long
323
+ openspec list
324
+ # Optional full-text search:
325
+ # rg -n "Requirement:|Scenario:" openspec/specs
326
+ # rg -n "^#|Requirement:" openspec/changes
327
+
328
+ # 2) Choose change id and scaffold
329
+ CHANGE=add-two-factor-auth
330
+ mkdir -p openspec/changes/$CHANGE/{specs/auth}
331
+ printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md
332
+ printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md
333
+
334
+ # 3) Add deltas (example)
335
+ cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF'
336
+ ## ADDED Requirements
337
+ ### Requirement: Two-Factor Authentication
338
+ Users MUST provide a second factor during login.
339
+
340
+ #### Scenario: OTP required
341
+ - **WHEN** valid credentials are provided
342
+ - **THEN** an OTP challenge is required
343
+ EOF
344
+
345
+ # 4) Validate
346
+ openspec validate $CHANGE --strict
347
+ ```
348
+
349
+ ## Multi-Capability Example
350
+
351
+ ```
352
+ openspec/changes/add-2fa-notify/
353
+ ├── proposal.md
354
+ ├── tasks.md
355
+ └── specs/
356
+ ├── auth/
357
+ │ └── spec.md # ADDED: Two-Factor Authentication
358
+ └── notifications/
359
+ └── spec.md # ADDED: OTP email notification
360
+ ```
361
+
362
+ auth/spec.md
363
+ ```markdown
364
+ ## ADDED Requirements
365
+ ### Requirement: Two-Factor Authentication
366
+ ...
367
+ ```
368
+
369
+ notifications/spec.md
370
+ ```markdown
371
+ ## ADDED Requirements
372
+ ### Requirement: OTP Email Notification
373
+ ...
374
+ ```
375
+
376
+ ## Best Practices
377
+
378
+ ### Simplicity First
379
+ - Default to <100 lines of new code
380
+ - Single-file implementations until proven insufficient
381
+ - Avoid frameworks without clear justification
382
+ - Choose boring, proven patterns
383
+
384
+ ### Complexity Triggers
385
+ Only add complexity with:
386
+ - Performance data showing current solution too slow
387
+ - Concrete scale requirements (>1000 users, >100MB data)
388
+ - Multiple proven use cases requiring abstraction
389
+
390
+ ### Clear References
391
+ - Use `file.ts:42` format for code locations
392
+ - Reference specs as `specs/auth/spec.md`
393
+ - Link related changes and PRs
394
+
395
+ ### Capability Naming
396
+ - Use verb-noun: `user-auth`, `payment-capture`
397
+ - Single purpose per capability
398
+ - 10-minute understandability rule
399
+ - Split if description needs "AND"
400
+
401
+ ### Change ID Naming
402
+ - Use kebab-case, short and descriptive: `add-two-factor-auth`
403
+ - Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-`
404
+ - Ensure uniqueness; if taken, append `-2`, `-3`, etc.
405
+
406
+ ## Tool Selection Guide
407
+
408
+ | Task | Tool | Why |
409
+ |------|------|-----|
410
+ | Find files by pattern | Glob | Fast pattern matching |
411
+ | Search code content | Grep | Optimized regex search |
412
+ | Read specific files | Read | Direct file access |
413
+ | Explore unknown scope | Task | Multi-step investigation |
414
+
415
+ ## Error Recovery
416
+
417
+ ### Change Conflicts
418
+ 1. Run `openspec list` to see active changes
419
+ 2. Check for overlapping specs
420
+ 3. Coordinate with change owners
421
+ 4. Consider combining proposals
422
+
423
+ ### Validation Failures
424
+ 1. Run with `--strict` flag
425
+ 2. Check JSON output for details
426
+ 3. Verify spec file format
427
+ 4. Ensure scenarios properly formatted
428
+
429
+ ### Missing Context
430
+ 1. Read project.md first
431
+ 2. Check related specs
432
+ 3. Review recent archives
433
+ 4. Ask for clarification
434
+
435
+ ## Quick Reference
436
+
437
+ ### Stage Indicators
438
+ - `changes/` - Proposed, not yet built
439
+ - `specs/` - Built and deployed
440
+ - `archive/` - Completed changes
441
+
442
+ ### File Purposes
443
+ - `proposal.md` - Why and what
444
+ - `tasks.md` - Implementation steps
445
+ - `design.md` - Technical decisions
446
+ - `spec.md` - Requirements and behavior
447
+
448
+ ### CLI Essentials
449
+ ```bash
450
+ openspec list # What's in progress?
451
+ openspec show [item] # View details
452
+ openspec validate --strict # Is it correct?
453
+ openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
454
+ ```
455
+
456
+ Remember: Specs are truth. Changes are proposals. Keep them in sync.
openspec/changes/archive/2025-11-07-add-ingestion-layer/design.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Context
2
+ Hackathon demos require an ingestion layer that loads user-supplied PDF books, normalizes their contents, and prepares text for downstream summarization, retrieval, and text-to-speech features.
3
+
4
+ ## Goals / Non-Goals
5
+ - Goals:
6
+ - Provide resilient PDF extraction with metadata preservation.
7
+ - Normalize and chunk content for immediate reuse by embedding pipelines.
8
+ - Persist intermediate artifacts so ingestion does not need to be rerun during the same session.
9
+ - Keep the Streamlit UI responsive with clear progress feedback.
10
+ - Non-Goals:
11
+ - Implement embeddings, summarization, or Q&A logic (handled by later changes).
12
+ - Deliver production-grade distributed workers or OCR for scanned PDFs.
13
+ - Persist ingestion artifacts beyond the user session.
14
+
15
+ ## Decisions
16
+ - Use `PyPDFLoader` as the primary parser with `pdfplumber` fallback to handle extraction inconsistencies.
17
+ - Normalize text by enforcing UTF-8 encoding, removing control characters, and standardizing whitespace before document modeling.
18
+ - Employ `RecursiveCharacterTextSplitter` with defaults from the planning document (800 token chunks, 150 token overlap) while allowing sidebar overrides.
19
+ - Serialize chunk payloads to session-scoped storage under `.tmp/ingestion/` for reuse by downstream stages.
20
+ - Offload heavy parsing to `asyncio.to_thread` or `concurrent.futures` so Streamlit can maintain interactive feedback.
21
+
22
+ ## Risks / Trade-offs
23
+ - Large PDFs could still incur noticeable delays despite background execution; mitigated by streaming progress updates.
24
+ - Parser discrepancies may yield inconsistent metadata; mitigated by offering user-triggered retries with alternate parsers.
25
+ - Temporary storage growth may accumulate stale files; mitigated by cleaning up artifacts at session end.
26
+
27
+ ## Migration Plan
28
+ 1. Implement the new ingestion flow behind the Streamlit UI.
29
+ 2. Validate functionality with representative short-story and technical manual PDFs.
30
+ 3. Introduce cleanup hooks for session-scoped temporary files.
31
+
32
+ ## Open Questions
33
+ - Should OCR support be introduced for scanned PDFs if time permits?
34
+ - Is there a requirement to persist ingestion results across authenticated sessions?
openspec/changes/archive/2025-11-07-add-ingestion-layer/proposal.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Change: Establish ingestion layer foundations
2
+
3
+ ## Why
4
+ - Hackathon demos need a working ingestion pipeline so users can upload PDF books and unlock downstream summarization, retrieval, and TTS flows.
5
+ - Without reliable ingestion, later stages of the stack cannot be exercised, blocking end-to-end validation.
6
+
7
+ ## What Changes
8
+ - Build a Streamlit-driven upload flow that routes PDFs through a resilient text extraction pipeline with fallback parsing options.
9
+ - Normalize extracted content, capture structural metadata, and split text into retrieval-ready chunks with configurable parameters.
10
+ - Persist chunk payloads for later embedding steps and surface ingestion status, logs, and retry controls in the UI.
11
+ - Offload heavy parsing work to background threads or async workers to keep the UI responsive during large file ingestion.
12
+
13
+ ## Impact
14
+ - Affected specs: `ingestion`
15
+ - Affected code: Streamlit ingestion UI, ingestion services, temporary storage utilities
openspec/changes/archive/2025-11-07-add-ingestion-layer/specs/ingestion/spec.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## ADDED Requirements
2
+
3
+ ### Requirement: PDF ingestion uploads
4
+ The ingestion layer SHALL accept user-provided PDF files via the Streamlit UI and initiate processing within 5 seconds of upload acknowledgement.
5
+
6
+ #### Scenario: Successful PDF upload
7
+ - **GIVEN** a user selects a valid PDF within supported size limits
8
+ - **WHEN** the user uploads the file through the Streamlit ingestion interface
9
+ - **THEN** the system SHALL persist the file in temporary storage and enqueue it for ingestion processing.
10
+
11
+ ### Requirement: Structured text extraction
12
+ The ingestion layer SHALL extract text from PDFs using a primary parser with a fallback strategy and retain chapter and heading metadata when available.
13
+
14
+ #### Scenario: Metadata-preserving extraction
15
+ - **GIVEN** a PDF that exposes structural metadata
16
+ - **WHEN** ingestion parses the document
17
+ - **THEN** the system SHALL produce text segments tagged with originating chapter or heading information.
18
+
19
+ ### Requirement: Text normalization and document modeling
20
+ The ingestion layer SHALL normalize extracted text (encoding fixes, control character removal, whitespace cleanup) and output structured documents for downstream processing.
21
+
22
+ #### Scenario: Normalized document output
23
+ - **GIVEN** raw text extracted from a PDF page
24
+ - **WHEN** normalization runs
25
+ - **THEN** the system SHALL produce UTF-8 encoded text without redundant whitespace and attach the normalized content to a document model containing source metadata.
26
+
27
+ ### Requirement: Retrieval-ready chunking
28
+ The ingestion layer SHALL split normalized documents into retrieval-ready chunks using a configurable RecursiveCharacterTextSplitter with default settings aligned to planning guidance.
29
+
30
+ #### Scenario: Chunk generation with defaults
31
+ - **GIVEN** normalized document content exceeding the default chunk size
32
+ - **WHEN** the chunker runs with default configuration
33
+ - **THEN** the system SHALL output sequential chunks with the configured overlap and retain references to source metadata.
34
+
35
+ ### Requirement: Chunk persistence
36
+ The ingestion layer SHALL persist chunk payloads (content, metadata, source information) in temporary storage for later embedding and summarization steps.
37
+
38
+ #### Scenario: Persisted chunk payloads
39
+ - **GIVEN** a set of generated chunks
40
+ - **WHEN** ingestion completes chunking
41
+ - **THEN** the system SHALL write the chunk data to `.tmp/ingestion/<session_id>.json` or an equivalent session-scoped store for reuse.
42
+
43
+ ### Requirement: Responsive large-file processing
44
+ The ingestion layer SHALL process large PDFs without blocking the UI by delegating heavy work to background threads or async tasks while reporting progress updates.
45
+
46
+ #### Scenario: Background processing status
47
+ - **GIVEN** a large PDF that requires more than 2 seconds to parse
48
+ - **WHEN** ingestion begins processing the file
49
+ - **THEN** the system SHALL execute parsing off the main UI thread and update the Streamlit interface with progress indicators until completion.
50
+
51
+ ### Requirement: Ingestion error reporting
52
+ The ingestion layer SHALL surface ingestion errors and logs within the UI and provide guidance for retrying with alternate parsing strategies.
53
+
54
+ #### Scenario: Fallback guidance on failure
55
+ - **GIVEN** the primary parsing strategy fails to extract text
56
+ - **WHEN** ingestion reports an error
57
+ - **THEN** the system SHALL display the error details and offer the user the option to retry using the fallback parser.
openspec/changes/archive/2025-11-07-add-ingestion-layer/tasks.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ ## 1. Ingestion Pipeline
2
+ - [x] 1.1 Implement Streamlit PDF upload UI and route files into the ingestion service.
3
+ - [x] 1.2 Extract text with primary (`pypdf`) and fallback (`pdfplumber`) parsers.
4
+ - [x] 1.3 Normalize text (encoding fixes, whitespace cleanup) and persist structured documents for downstream use.
5
+ - [x] 1.4 Chunk documents with configurable settings and store chunk payloads in temporary storage.
6
+ - [x] 1.5 Surface ingestion progress, logs, and retry options in the UI while heavy work runs off the main thread.
7
+ - [x] 1.6 Add automated tests or manual QA checklist runs for representative short-story and technical PDF samples.
openspec/project.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project Context
2
+
3
+ ## Purpose
4
+ Build an "Accelerated Book Copilot" Streamlit app that ingests long-form content, delivers layered summaries, enables adjustable-speed listening, answers reader questions with cited passages, and enriches context via targeted web crawling.
5
+
6
+ ## Tech Stack
7
+ - Python 3.11+
8
+ - Streamlit for the interactive UI
9
+ - LangChain for orchestration, retrieval, and tool management
10
+ - Azure OpenAI / OpenAI models for summarization and Q&A
11
+ - SentenceTransformers or `text-embedding-3-small` embeddings
12
+ - FAISS (local) or Pinecone (managed) vector store
13
+ - TTS utilities from FOMO project (gTTS / local models) with optional Azure Speech
14
+ - Newspaper3k / Requests / BeautifulSoup for focused crawling
15
+ - Altair for visualization (timeline, reading metrics)
16
+
17
+ ## Project Conventions
18
+
19
+ ### Code Style
20
+ - Pythonic style with type hints, descriptive snake_case naming, and minimal inline comments.
21
+ - Prefer pure functions where practical; separate UI logic from data/LLM orchestration.
22
+ - Use `black`/`ruff` compatible formatting (4-space indent, double quotes acceptable but prefer single quotes unless escaping).
23
+ - Avoid interactive `input()`; rely on Streamlit widgets and session state.
24
+
25
+ ### Architecture Patterns
26
+ - Layered architecture: ingestion → processing (chunk/embedding/summarization) → experience (UI tabs).
27
+ - Retrieval-Augmented Generation for Q&A with citation packaging.
28
+ - Background task pattern for heavy preprocessing (async threads or queued jobs).
29
+ - Modular service helpers (ingestion, summarization, retrieval, TTS) to keep Streamlit pages thin.
30
+
31
+ ### Testing Strategy
32
+ - Unit tests for ingestion utilities, chunking, and summarization prompt builders.
33
+ - Integration smoke tests for the Streamlit app using sample documents.
34
+ - Mock external APIs (Azure OpenAI, Pinecone) to keep CI deterministic.
35
+ - Manual regression checklist for multilingual TTS and crawl workflows before demos.
36
+
37
+ ### Git Workflow
38
+ - Use feature branches named `feature/<topic>` or `fix/<topic>` off `main`.
39
+ - Conventional commit messages (e.g., `feat: add crawl summarizer`) with co-author trailer when pair hacking.
40
+ - Pull requests require reviewer sign-off plus local smoke test evidence.
41
+ - Avoid committing secrets; rely on `.env` templates and local dotenv loading.
42
+
43
+ ## Domain Context
44
+ - Focused on long-form content acceleration for readers, researchers, and students.
45
+ - Supports multiple summary granularities (flash, detailed, character/plot focused).
46
+ - Q&A must cite source passages to maintain trust; responses should match question language when possible.
47
+ - Listening mode ties TTS playback to highlighted text segments for faster comprehension.
48
+ - Crawl mode gathers external commentary (reviews, interviews, scholarly notes) to enrich understanding.
49
+
50
+ ## Important Constraints
51
+ - Must operate within hackathon time constraints; prioritize features that demo well within 3 days.
52
+ - Respect licensing of ingested books; app assumes user-provided content is permitted.
53
+ - Ensure OpenAI/Azure usage stays within provided quota; implement caching to minimize token spend.
54
+ - Crawling restricted to whitelisted, publicly accessible sources to avoid legal issues.
55
+ - Offline-friendly fallback path (local embeddings/FAISS) required if Pinecone credentials unavailable.
56
+
57
+ ## External Dependencies
58
+ - Azure OpenAI / OpenAI API keys for LLM and embedding calls.
59
+ - Pinecone account (optional) for managed vector storage.
60
+ - Hugging Face / SentenceTransformers models for local embeddings.
61
+ - Third-party TTS backends (gTTS/local models/Azure Speech) for audio output.
62
+ - Newspaper3k/Requests for external article retrieval; BeautifulSoup for parsing.
openspec/specs/ingestion/spec.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ingestion Specification
2
+
3
+ ## Purpose
4
+ TBD - created by archiving change add-ingestion-layer. Update Purpose after archive.
5
+ ## Requirements
6
+ ### Requirement: PDF ingestion uploads
7
+ The ingestion layer SHALL accept user-provided PDF files via the Streamlit UI and initiate processing within 5 seconds of upload acknowledgement.
8
+
9
+ #### Scenario: Successful PDF upload
10
+ - **GIVEN** a user selects a valid PDF within supported size limits
11
+ - **WHEN** the user uploads the file through the Streamlit ingestion interface
12
+ - **THEN** the system SHALL persist the file in temporary storage and enqueue it for ingestion processing.
13
+
14
+ ### Requirement: Structured text extraction
15
+ The ingestion layer SHALL extract text from PDFs using a primary parser with a fallback strategy and retain chapter and heading metadata when available.
16
+
17
+ #### Scenario: Metadata-preserving extraction
18
+ - **GIVEN** a PDF that exposes structural metadata
19
+ - **WHEN** ingestion parses the document
20
+ - **THEN** the system SHALL produce text segments tagged with originating chapter or heading information.
21
+
22
+ ### Requirement: Text normalization and document modeling
23
+ The ingestion layer SHALL normalize extracted text (encoding fixes, control character removal, whitespace cleanup) and output structured documents for downstream processing.
24
+
25
+ #### Scenario: Normalized document output
26
+ - **GIVEN** raw text extracted from a PDF page
27
+ - **WHEN** normalization runs
28
+ - **THEN** the system SHALL produce UTF-8 encoded text without redundant whitespace and attach the normalized content to a document model containing source metadata.
29
+
30
+ ### Requirement: Retrieval-ready chunking
31
+ The ingestion layer SHALL split normalized documents into retrieval-ready chunks using a configurable RecursiveCharacterTextSplitter with default settings aligned to planning guidance.
32
+
33
+ #### Scenario: Chunk generation with defaults
34
+ - **GIVEN** normalized document content exceeding the default chunk size
35
+ - **WHEN** the chunker runs with default configuration
36
+ - **THEN** the system SHALL output sequential chunks with the configured overlap and retain references to source metadata.
37
+
38
+ ### Requirement: Chunk persistence
39
+ The ingestion layer SHALL persist chunk payloads (content, metadata, source information) in temporary storage for later embedding and summarization steps.
40
+
41
+ #### Scenario: Persisted chunk payloads
42
+ - **GIVEN** a set of generated chunks
43
+ - **WHEN** ingestion completes chunking
44
+ - **THEN** the system SHALL write the chunk data to `.tmp/ingestion/<session_id>.json` or an equivalent session-scoped store for reuse.
45
+
46
+ ### Requirement: Responsive large-file processing
47
+ The ingestion layer SHALL process large PDFs without blocking the UI by delegating heavy work to background threads or async tasks while reporting progress updates.
48
+
49
+ #### Scenario: Background processing status
50
+ - **GIVEN** a large PDF that requires more than 2 seconds to parse
51
+ - **WHEN** ingestion begins processing the file
52
+ - **THEN** the system SHALL execute parsing off the main UI thread and update the Streamlit interface with progress indicators until completion.
53
+
54
+ ### Requirement: Ingestion error reporting
55
+ The ingestion layer SHALL surface ingestion errors and logs within the UI and provide guidance for retrying with alternate parsing strategies.
56
+
57
+ #### Scenario: Fallback guidance on failure
58
+ - **GIVEN** the primary parsing strategy fails to extract text
59
+ - **WHEN** ingestion reports an error
60
+ - **THEN** the system SHALL display the error details and offer the user the option to retry using the fallback parser.
61
+
planning.md ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Project Vision
2
+ Create a Streamlit-powered "Accelerated Book Copilot" that ingests books or long-form documents, generates stacked summaries, enables adjustable-speed listening, answers questions with grounded citations, and enriches context via focused web crawling.
3
+
4
+ ## User Journeys
5
+ - **Rapid Onboarding:** Upload a book or paste a URL, receive quick, detailed, and action-focused summaries in minutes.
6
+ - **Listening Mode:** Play text-to-speech audio with adjustable pace and language options.
7
+ - **Knowledge Dive:** Ask free-form questions, get cited answers, and review referenced passages.
8
+ - **Context Expansion:** Trigger a crawl for related resources (reviews, interviews, scholarly notes) and fold distilled insights into the knowledge base.
9
+
10
+ ## Core Features
11
+ - PDF ingestion with reliable text extraction and structure retention.
12
+ - Chunking and embedding using SentenceTransformer or `text-embedding-3-small` for retrieval-ready vectors.
13
+ - Summaries: tiered (flash, detailed, character-focused) powered by Azure/OpenAI prompts.
14
+ - Q&A agent built on LangChain with streaming answers and citation highlighting.
15
+ - Listening: FOMO TTS modules with pitch/speed controls and downloadable clips.
16
+ - Crawl mode leveraging Newspaper3k/Requests + safety filters to augment knowledge base.
17
+ - Session management with Streamlit state and optional persistence (SQLite/JSON) for revisit.
18
+
19
+ ## Architecture Outline
20
+ 1. **Ingestion Layer**
21
+ - PDF loader pipeline with robust parsing and structure detection.
22
+ - Background task queue (async workers) to preprocess large uploads.
23
+ 2. **Processing Layer**
24
+ - Text splitter (RecursiveCharacter) feeding embeddings stored in FAISS or Pinecone.
25
+ - Summarization orchestrator reusing FOMO prompt stacks.
26
+ - Q&A chain: RetrievalQA + chat memory + citation packaging.
27
+ 3. **Experience Layer**
28
+ - Streamlit tabs for Read, Summaries, Listen, Q&A, Crawl Insights.
29
+ - Interactive timeline/outline with Altair visualization for pacing metrics.
30
+ - Audio controls and transcript alignment.
31
+
32
+ ## Tech Stack
33
+ - Streamlit UI (existing scaffold).
34
+ - LangChain for orchestration and tools.
35
+ - Azure/OpenAI LLM + embeddings; fallback to OpenAI endpoints.
36
+ - Pinecone or FAISS vector store depending on hackathon infra.
37
+ - SentenceTransformer local model for offline embeddings if needed.
38
+ - TTS via FOMO `load_tts` or Azure Speech (optional).
39
+ - Newspaper3k, BeautifulSoup, or HuggingFace datasets for crawl augmentation.
40
+ - Storage: lightweight JSON/SQLite, with hooks for cloud object storage if available.
41
+
42
+ ## Milestones
43
+ 1. **Day 1 – Foundations**
44
+ - Wireframe Streamlit tabs and session state.
45
+ - Implement PDF ingestion -> chunk -> embed pipeline with local FAISS.
46
+ 2. **Day 2 – Intelligence Layer**
47
+ - Integrate summarization tiers and Q&A citations.
48
+ - Add TTS playback using existing FOMO audio utilities.
49
+ 3. **Day 3 – Crawl & Polish**
50
+ - Build targeted crawl + summarization flow.
51
+ - Add analytics (reading time estimator, compression ratios).
52
+ - Prep demo script and sample datasets.
53
+
54
+ ## Risks & Mitigations
55
+ - **Large file latency:** Use background processing and progressive loading.
56
+ - **API rate limits:** Cache embeddings/summaries, throttle crawls.
57
+ - **Crawl reliability:** Predefine trusted domains, handle failures gracefully.
58
+ - **UX overload:** Start with core tabs, keep optional features toggled off by default.
59
+
60
+ ## Next Steps
61
+ - Confirm target vector store (FAISS vs Pinecone) and credential availability.
62
+ - Draft Streamlit layout skeleton and assign owners per milestone.
63
+ - Prepare sample books and reliability test cases (short story, technical manual, novel).
requirements.txt CHANGED
@@ -1,3 +1,9 @@
1
  altair
2
  pandas
3
- streamlit
 
 
 
 
 
 
 
1
  altair
2
  pandas
3
+ streamlit
4
+ pdfplumber
5
+ pypdf
6
+ python-dotenv
7
+ pytest
8
+ pytest-cov
9
+ reportlab
src/ingestion.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import re
4
+ from dataclasses import dataclass, asdict
5
+ from typing import List, Optional, Tuple
6
+
7
+ from pypdf import PdfReader
8
+ import pdfplumber
9
+
10
+
11
+ @dataclass
12
+ class Chunk:
13
+ content: str
14
+ source: str
15
+ page: Optional[int] = None
16
+ chapter: Optional[str] = None
17
+
18
+
19
+ def normalize_text(text: str) -> str:
20
+ # Remove control characters, normalize whitespace
21
+ text = text.encode("utf-8", errors="ignore").decode("utf-8", errors="ignore")
22
+ text = re.sub(r"[\x00-\x08\x0B\x0C\x0E-\x1F]", " ", text)
23
+ text = re.sub(r"\s+", " ", text).strip()
24
+ return text
25
+
26
+
27
+ def extract_text_pypdf(file_path: str) -> List[Tuple[int, str]]:
28
+ pages: List[Tuple[int, str]] = []
29
+ reader = PdfReader(file_path)
30
+ for i, page in enumerate(reader.pages, start=1):
31
+ text = page.extract_text() or ""
32
+ pages.append((i, text))
33
+ return pages
34
+
35
+
36
+ def extract_text_pdfplumber(file_path: str) -> List[Tuple[int, str]]:
37
+ pages: List[Tuple[int, str]] = []
38
+ with pdfplumber.open(file_path) as pdf:
39
+ for i, page in enumerate(pdf.pages, start=1):
40
+ text = page.extract_text() or ""
41
+ pages.append((i, text))
42
+ return pages
43
+
44
+
45
+ def chunk_text(text: str, chunk_size: int = 4000, overlap: int = 400) -> List[str]:
46
+ # Simple recursive-like character splitter
47
+ if chunk_size <= 0:
48
+ return [text]
49
+ chunks: List[str] = []
50
+ start = 0
51
+ n = len(text)
52
+ while start < n:
53
+ end = min(start + chunk_size, n)
54
+ chunk = text[start:end]
55
+ chunks.append(chunk)
56
+ if end == n:
57
+ break
58
+ start = max(end - overlap, start + 1)
59
+ return chunks
60
+
61
+
62
+ def make_chunks(pages: List[Tuple[int, str]], source: str, chunk_size: int, overlap: int) -> List[Chunk]:
63
+ all_text = []
64
+ for page_num, raw in pages:
65
+ norm = normalize_text(raw)
66
+ if norm:
67
+ all_text.append((page_num, norm))
68
+ merged = "\n".join(t for _, t in all_text)
69
+ pieces = chunk_text(merged, chunk_size=chunk_size, overlap=overlap)
70
+ chunks = [Chunk(content=p, source=source) for p in pieces if p.strip()]
71
+ return chunks
72
+
73
+
74
+ def persist_chunks(session_id: str, chunks: List[Chunk]) -> str:
75
+ base = os.path.join(".tmp", "ingestion")
76
+ os.makedirs(base, exist_ok=True)
77
+ path = os.path.join(base, f"{session_id}.json")
78
+ with open(path, "w", encoding="utf-8") as f:
79
+ json.dump([asdict(c) for c in chunks], f, ensure_ascii=False, indent=2)
80
+ return path
81
+
82
+
83
+ def ingest_pdf(
84
+ file_path: str,
85
+ session_id: str,
86
+ parser: str = "pypdf",
87
+ chunk_size: int = 4000,
88
+ overlap: int = 400,
89
+ ) -> Tuple[List[Chunk], str]:
90
+ if parser == "pypdf":
91
+ pages = extract_text_pypdf(file_path)
92
+ elif parser == "pdfplumber":
93
+ pages = extract_text_pdfplumber(file_path)
94
+ else:
95
+ raise ValueError("Unknown parser: " + parser)
96
+
97
+ chunks = make_chunks(pages, source=os.path.basename(file_path), chunk_size=chunk_size, overlap=overlap)
98
+ out = persist_chunks(session_id, chunks)
99
+ return chunks, out
src/streamlit_app.py CHANGED
@@ -1,40 +1,79 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import os
3
+ import tempfile
4
+ from uuid import uuid4
5
+ from concurrent.futures import ThreadPoolExecutor, Future
6
 
7
+ from ingestion import ingest_pdf
8
+ from dotenv import load_dotenv, find_dotenv
9
+
10
+ load_dotenv(find_dotenv(), override=False)
11
+ st.set_page_config(page_title="PDF Ingestion", layout="wide")
12
+
13
+ if "session_id" not in st.session_state:
14
+ st.session_state.session_id = str(uuid4())
15
+ if "ingestion_future" not in st.session_state:
16
+ st.session_state.ingestion_future = None
17
+ if "last_output" not in st.session_state:
18
+ st.session_state.last_output = None
19
+
20
+ st.title("Ingestion Layer")
21
+ st.caption("Upload a PDF to extract, normalize, chunk, and persist for later embedding.")
22
+
23
+ with st.sidebar:
24
+ st.header("Settings")
25
+ parser = st.selectbox("Parser", ["pypdf", "pdfplumber"], index=0)
26
+ chunk_size = st.number_input("Chunk size (chars)", min_value=500, max_value=10000, value=4000, step=100)
27
+ overlap = st.number_input("Overlap (chars)", min_value=0, max_value=2000, value=400, step=50)
28
+
29
+ uploaded = st.file_uploader("Upload PDF", type=["pdf"])
30
+
31
+ def _run_ingestion(tmp_path: str, sid: str, parser: str, chunk_size: int, overlap: int):
32
+ return ingest_pdf(tmp_path, sid, parser=parser, chunk_size=chunk_size, overlap=overlap)
33
+
34
+ col1, col2 = st.columns([2, 1])
35
+ with col1:
36
+ if uploaded is not None and st.button("Start Ingestion", type="primary"):
37
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
38
+ tmp.write(uploaded.read())
39
+ tmp_path = tmp.name
40
+
41
+ executor = ThreadPoolExecutor(max_workers=1)
42
+ future: Future = executor.submit(_run_ingestion, tmp_path, st.session_state.session_id, parser, int(chunk_size), int(overlap))
43
+ st.session_state.ingestion_future = future
44
+ st.info("Ingestion started. Processing in background...")
45
+
46
+ future: Future = st.session_state.ingestion_future
47
+ if future is not None:
48
+ if future.done():
49
+ try:
50
+ chunks, out_path = future.result()
51
+ st.session_state.last_output = (len(chunks), out_path)
52
+ st.success(f"Ingestion complete: {len(chunks)} chunks written to {out_path}")
53
+ if chunks:
54
+ st.subheader("Preview (first chunk)")
55
+ st.code(chunks[0].content[:2000])
56
+ except Exception as e:
57
+ st.error(f"Ingestion failed: {e}")
58
+ finally:
59
+ st.session_state.ingestion_future = None
60
+ else:
61
+ st.status("Processing...", state="running")
62
+
63
+ with col2:
64
+ st.subheader("Session")
65
+ st.code(st.session_state.session_id)
66
+ if st.session_state.last_output:
67
+ cnt, path = st.session_state.last_output
68
+ st.metric("Chunks", cnt)
69
+ st.write("Output:")
70
+ st.code(path)
71
+ if uploaded is not None and st.button("Retry with fallback parser"):
72
+ fb = "pdfplumber" if parser == "pypdf" else "pypdf"
73
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
74
+ tmp.write(uploaded.read())
75
+ tmp_path = tmp.name
76
+ executor = ThreadPoolExecutor(max_workers=1)
77
+ future: Future = executor.submit(_run_ingestion, tmp_path, st.session_state.session_id, fb, int(chunk_size), int(overlap))
78
+ st.session_state.ingestion_future = future
79
+ st.info(f"Retrying with {fb}...")
test-data/upload_example.txt ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+
4
+
5
+ The goal of this template is to give a structure for the user guide. Its presentation is not user-friendly. Add you cover page, logo, look-and-feel etc…
6
+
7
+ Add also the CE Mark on the cover page, if necessary.
8
+ The identification of the document shall also be clear, add the identifier and revision date of the document on the cover page or somewhere where it is easily visible.
9
+
10
+
11
+ TABLE OF CONTENTS
12
+
13
+ 1 Introduction 2
14
+ 1.1 Conventions 2
15
+ 1.2 Intended use 2
16
+ 2 Precautions 2
17
+ 2.1 Compliant Use 2
18
+ 2.2 Safety instructions 3
19
+ 2.2.1 Electromagnetic interferences 3
20
+ 2.2.2 Electrical connection 3
21
+ 2.2.3 Environment 3
22
+ 2.2.4 Storage conditions 3
23
+ 2.3 Malfunction 4
24
+ 3 Installation 4
25
+ 4 Getting started 4
26
+ 5 Advanced functions 4
27
+ 6 Maintenance 4
28
+ 7 Cleaning and Disinfection 4
29
+ 8 Error Messages, troubleshooting 4
30
+ 9 Bibliography 5
31
+ 10 Warranty 5
32
+ 11 Legal Notices 5
33
+ 11.1 Disclaimer 5
34
+ 11.2 Copyrights and trademarks 5
35
+ 12 Manufacturer Contact Information 5
36
+
37
+
38
+
39
+
40
+ 1 Introduction
41
+ This document is the user guide for XXX, a device designed by <your company>.
42
+ It is recommended that you read this instruction manual carefully before use.
43
+ If your software uses accessories, give the list below
44
+ This product can only be used with accessories delivered by <your company>:
45
+ • List of accessories with references
46
+ • An accessory
47
+ • Another accessory
48
+ The use of any other accessories is forbidden.
49
+ Please, read carefully the precautions with symbols, to ensure that the device is used in best conditions and in complete safety.
50
+ 1.1 Conventions
51
+ Typographical convention.
52
+ Any other convention.
53
+
54
+ To avoid all physical and material damage, this document splits safety instructions into 3 danger levels.
55
+ ATTENTION
56
+ Hazardous situation which can cause material damage or lead to moderate serious injury.
57
+
58
+ WARNING
59
+ Hazardous situation which can cause serious/ fatal injury.
60
+
61
+
62
+
63
+
64
+
65
+ Hazardous situation which could immediately cause serious/ fatal injury.
66
+
67
+ 1.2 Intended use
68
+ Add here the intended use of your device
69
+
70
+ 2 Precautions
71
+ 2.1 Compliant Use
72
+ The use of this device is reserved to XXX (give the specialty of physicians or if they need a training or an education level), or under their control.
73
+ Any inappropriate use is forbidden.
74
+
75
+ The user shall:
76
+ • Only use non-defective products.
77
+ • Protect patients, third parties, other operators, assistants and themselves from all danger.
78
+ • Avoid all contamination by the product (remove this if you don’t sell hardware).
79
+ The user must check the safety of operation and the condition of the device before each use.
80
+
81
+ Verify that the product is operational at all times and keep it in good working conditions.
82
+
83
+ Only <your company> technicians are authorized to repair <your company> products.
84
+
85
+ 2.2 Safety instructions
86
+ Add any instructions related to safety, especially mitigations actions from risk analysis
87
+ 2.2.1 Electromagnetic interferences
88
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
89
+ Example of safety instruction, add any other safety instruction:
90
+ The device is certified compliant with current electromagnetic compatibility standards.
91
+
92
+ ATTENTION
93
+ Users should ensure that no electromagnetic interference raises an additional risk.
94
+ So as not to disturb the proper operation of electromagnetic devices, it is recommended complying with the recommended separation distances for the device. ( give separation distances)
95
+
96
+ 2.2.2 Electrical connection
97
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it
98
+ Example of safety instruction, add any other safety instruction:
99
+
100
+
101
+
102
+
103
+
104
+ DANGER
105
+ Bad electrical connection - ELECTRIC SHOCK - ELECTROCUTION
106
+
107
+ Check that the supply voltage is the same as that shown on the device. Check the supply cable before each use.
108
+ Never plug or unplug the device to/from a wall socket with damp hands.
109
+ Any other use or connection of devices or peripherals to a port (USB, serial, parallel …) may create new risks and may require additional risk analysis
110
+ 2.2.3 Environment
111
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
112
+ Add safety instructions about environment, like presence of explosive gases (anesthetics), fluids, temperature, mechanical constraints …
113
+ 2.2.4 Storage conditions
114
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
115
+ Add safety instructions about recommended storage conditions …
116
+ 2.3 Malfunction
117
+ In case of malfunction:
118
+ • Immediately stop using the device.
119
+ • Try to identify or eliminate the cause using the description in this document (section Error messages, troubleshooting).
120
+ • If it is not possible to identify or eliminate the cause using this document, switch the device off and call <your company> After-Sales Department (see Manufacturer section at the end of this document).
121
+
122
+ 3 Installation - unistallation
123
+ Optional, you may remove this section and describe installation in a separate installation manual
124
+ Describe the installation and uninstallation of the hardware and software. Hardware and/or software installation may be only realized by technicians of your company. In this case, remove this section and describe installation in another (internal?) document.
125
+ 4 Getting started
126
+ First use of software by the user, explain main and/or simple use cases, and main workflow.
127
+
128
+
129
+
130
+ 5 Advanced functions
131
+ Advanced use, describe more complex use cases and/or secondary functions
132
+
133
+
134
+
135
+ 6 Maintenance
136
+ Optional, you may remove this section and describe installation in a separate maintenance/administrator’s manual
137
+ Describe the maintenance of the hardware and software. Hardware and/or software maintenance may be only realized by technicians of your company. In this case, remove this section and describe installation in another (internal?) document.
138
+
139
+ 7 Cleaning and Disinfection
140
+ If you sell hardware which shall be cleans and disinfected. Otherwise you can remove it.
141
+ You may rename this section “Cleaning, Disinfection and Sterilization” is you have instructions about sterilization.
142
+
143
+ Describe how the device shall be cleaned and disinfected. Is your hardware protected by screens, sheaths, if it is a medical PC, how to clean the screen, the mouse & keyboard …
144
+ 8 Troubleshooting, Error Messages
145
+
146
+ This section is intended to help users, should a problem occur.
147
+
148
+ In the event of abnormal operation, immediately stop using the device and call <your company> After-Sales Dept.
149
+ If it is not possible to locate or eliminate the problem using this section, or if the dysfunction is still present, switch off the device and call <your company> After-Sales Dept.
150
+
151
+
152
+
153
+ DANGER
154
+ Never try to repair this device alone
155
+
156
+
157
+
158
+
159
+ Error message Description
160
+ Your error message Description of the error and how to fix it, if possible
161
+ Not enough disk space (err 123) Empty disk using xxxx
162
+
163
+
164
+ Malfunction Causes Solutions
165
+ The malfunction • The cause • The solution, if any, or call after-sales
166
+ The PC doesn’t start • It is unplugged • Plug it to main (I Love this one !!!)
167
+ 9 Bibliography
168
+ Optional. Add here bibliography, clinical data that are relevant to the use of the device.
169
+ 10 Warranty
170
+ Warranty terms and conditions. Not optional! …
171
+ 11 Legal Notices
172
+ Optional, ask your legal advisory!
173
+ 11.1 Disclaimer
174
+ Optional. The disclaimer is usual in software industry and is also often shown during software installation…
175
+ 11.2 Copyrights and trademarks
176
+ Optional. Add your copyrights and trademarks
177
+
178
+ Add sentences like:
179
+ Windows is a registered trademark of Microsoft Corporation in the USA and other countries.
180
+
181
+ 12 Manufacturer Contact Information
182
+
183
+ <Your Company>
184
+ Address
185
+ ZIP Code
186
+ Country
187
+ Contact Phone
188
+ Fax
189
+
test-data/user_guide_sample.txt ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ The goal of this template is to give a structure for the user guide. Its presentation is not user-friendly. Add you cover page, logo, look-and-feel etc…
3
+
4
+ Add also the CE Mark on the cover page, if necessary.
5
+ The identification of the document shall also be clear, add the identifier and revision date of the document on the cover page or somewhere where it is easily visible.
6
+
7
+
8
+ TABLE OF CONTENTS
9
+
10
+ 1 Introduction 2
11
+ 1.1 Conventions 2
12
+ 1.2 Intended use 2
13
+ 2 Precautions 2
14
+ 2.1 Compliant Use 2
15
+ 2.2 Safety instructions 3
16
+ 2.2.1 Electromagnetic interferences 3
17
+ 2.2.2 Electrical connection 3
18
+ 2.2.3 Environment 3
19
+ 2.2.4 Storage conditions 3
20
+ 2.3 Malfunction 4
21
+ 3 Installation 4
22
+ 4 Getting started 4
23
+ 5 Advanced functions 4
24
+ 6 Maintenance 4
25
+ 7 Cleaning and Disinfection 4
26
+ 8 Error Messages, troubleshooting 4
27
+ 9 Bibliography 5
28
+ 10 Warranty 5
29
+ 11 Legal Notices 5
30
+ 11.1 Disclaimer 5
31
+ 11.2 Copyrights and trademarks 5
32
+ 12 Manufacturer Contact Information 5
33
+
34
+
35
+
36
+
37
+ 1 Introduction
38
+ This document is the user guide for XXX, a device designed by <your company>.
39
+ It is recommended that you read this instruction manual carefully before use.
40
+ If your software uses accessories, give the list below
41
+ This product can only be used with accessories delivered by <your company>:
42
+ • List of accessories with references
43
+ • An accessory
44
+ • Another accessory
45
+ The use of any other accessories is forbidden.
46
+ Please, read carefully the precautions with symbols, to ensure that the device is used in best conditions and in complete safety.
47
+ 1.1 Conventions
48
+ Typographical convention.
49
+ Any other convention.
50
+
51
+ To avoid all physical and material damage, this document splits safety instructions into 3 danger levels.
52
+ ATTENTION
53
+ Hazardous situation which can cause material damage or lead to moderate serious injury.
54
+
55
+ WARNING
56
+ Hazardous situation which can cause serious/ fatal injury.
57
+
58
+
59
+
60
+
61
+
62
+ Hazardous situation which could immediately cause serious/ fatal injury.
63
+
64
+ 1.2 Intended use
65
+ Add here the intended use of your device
66
+
67
+ 2 Precautions
68
+ 2.1 Compliant Use
69
+ The use of this device is reserved to XXX (give the specialty of physicians or if they need a training or an education level), or under their control.
70
+ Any inappropriate use is forbidden.
71
+
72
+ The user shall:
73
+ • Only use non-defective products.
74
+ • Protect patients, third parties, other operators, assistants and themselves from all danger.
75
+ • Avoid all contamination by the product (remove this if you don’t sell hardware).
76
+ The user must check the safety of operation and the condition of the device before each use.
77
+
78
+ Verify that the product is operational at all times and keep it in good working conditions.
79
+
80
+ Only <your company> technicians are authorized to repair <your company> products.
81
+
82
+ 2.2 Safety instructions
83
+ Add any instructions related to safety, especially mitigations actions from risk analysis
84
+ 2.2.1 Electromagnetic interferences
85
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
86
+ Example of safety instruction, add any other safety instruction:
87
+ The device is certified compliant with current electromagnetic compatibility standards.
88
+
89
+ ATTENTION
90
+ Users should ensure that no electromagnetic interference raises an additional risk.
91
+ So as not to disturb the proper operation of electromagnetic devices, it is recommended complying with the recommended separation distances for the device. ( give separation distances)
92
+
93
+ 2.2.2 Electrical connection
94
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it
95
+ Example of safety instruction, add any other safety instruction:
96
+
97
+
98
+
99
+
100
+
101
+ DANGER
102
+ Bad electrical connection - ELECTRIC SHOCK - ELECTROCUTION
103
+
104
+ Check that the supply voltage is the same as that shown on the device. Check the supply cable before each use.
105
+ Never plug or unplug the device to/from a wall socket with damp hands.
106
+ Any other use or connection of devices or peripherals to a port (USB, serial, parallel …) may create new risks and may require additional risk analysis
107
+ 2.2.3 Environment
108
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
109
+ Add safety instructions about environment, like presence of explosive gases (anesthetics), fluids, temperature, mechanical constraints …
110
+ 2.2.4 Storage conditions
111
+ If you sell hardware + software, then this part is important. If you sell only software you can remove it.
112
+ Add safety instructions about recommended storage conditions …
113
+ 2.3 Malfunction
114
+ In case of malfunction:
115
+ • Immediately stop using the device.
116
+ • Try to identify or eliminate the cause using the description in this document (section Error messages, troubleshooting).
117
+ • If it is not possible to identify or eliminate the cause using this document, switch the device off and call <your company> After-Sales Department (see Manufacturer section at the end of this document).
118
+
119
+ 3 Installation - unistallation
120
+ Optional, you may remove this section and describe installation in a separate installation manual
121
+ Describe the installation and uninstallation of the hardware and software. Hardware and/or software installation may be only realized by technicians of your company. In this case, remove this section and describe installation in another (internal?) document.
122
+ 4 Getting started
123
+ First use of software by the user, explain main and/or simple use cases, and main workflow.
124
+
125
+
126
+
127
+ 5 Advanced functions
128
+ Advanced use, describe more complex use cases and/or secondary functions
129
+
130
+
131
+
132
+ 6 Maintenance
133
+ Optional, you may remove this section and describe installation in a separate maintenance/administrator’s manual
134
+ Describe the maintenance of the hardware and software. Hardware and/or software maintenance may be only realized by technicians of your company. In this case, remove this section and describe installation in another (internal?) document.
135
+
136
+ 7 Cleaning and Disinfection
137
+ If you sell hardware which shall be cleans and disinfected. Otherwise you can remove it.
138
+ You may rename this section “Cleaning, Disinfection and Sterilization” is you have instructions about sterilization.
139
+
140
+ Describe how the device shall be cleaned and disinfected. Is your hardware protected by screens, sheaths, if it is a medical PC, how to clean the screen, the mouse & keyboard …
141
+ 8 Troubleshooting, Error Messages
142
+
143
+ This section is intended to help users, should a problem occur.
144
+
145
+ In the event of abnormal operation, immediately stop using the device and call <your company> After-Sales Dept.
146
+ If it is not possible to locate or eliminate the problem using this section, or if the dysfunction is still present, switch off the device and call <your company> After-Sales Dept.
147
+
148
+
149
+
150
+ DANGER
151
+ Never try to repair this device alone
152
+
153
+
154
+
155
+
156
+ Error message Description
157
+ Your error message Description of the error and how to fix it, if possible
158
+ Not enough disk space (err 123) Empty disk using xxxx
159
+
160
+
161
+ Malfunction Causes Solutions
162
+ The malfunction • The cause • The solution, if any, or call after-sales
163
+ The PC doesn’t start • It is unplugged • Plug it to main (I Love this one !!!)
164
+ 9 Bibliography
165
+ Optional. Add here bibliography, clinical data that are relevant to the use of the device.
166
+ 10 Warranty
167
+ Warranty terms and conditions. Not optional! …
168
+ 11 Legal Notices
169
+ Optional, ask your legal advisory!
170
+ 11.1 Disclaimer
171
+ Optional. The disclaimer is usual in software industry and is also often shown during software installation…
172
+ 11.2 Copyrights and trademarks
173
+ Optional. Add your copyrights and trademarks
174
+
175
+ Add sentences like:
176
+ Windows is a registered trademark of Microsoft Corporation in the USA and other countries.
177
+
178
+ 12 Manufacturer Contact Information
179
+
180
+ <Your Company>
181
+ Address
182
+ ZIP Code
183
+ Country
184
+ Contact Phone
185
+ Fax
186
+
tests/QA_CHECKLIST.md ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Manual QA Checklist for PDF Ingestion Pipeline
2
+
3
+ ## Test Environment Setup
4
+ - [ ] Python 3.8+ installed
5
+ - [ ] All dependencies from requirements.txt installed
6
+ - [ ] Streamlit app accessible at http://localhost:8501
7
+ - [ ] Test PDFs available in test-data/ directory
8
+
9
+ ## 1. PDF Upload Functionality
10
+ ### Basic Upload
11
+ - [ ] Can upload PDF via drag-and-drop
12
+ - [ ] Can upload PDF via file browser
13
+ - [ ] File size limitations work correctly
14
+ - [ ] Non-PDF files are rejected with appropriate error
15
+
16
+ ### Multiple File Scenarios
17
+ - [ ] Can re-upload different PDFs in same session
18
+ - [ ] Previous upload is properly cleaned up
19
+
20
+ ## 2. Parser Testing
21
+
22
+ ### PyPDF Parser
23
+ - [ ] Successfully extracts text from short_story.pdf
24
+ - [ ] Successfully extracts text from technical_doc.pdf
25
+ - [ ] Handles multi-page documents correctly
26
+ - [ ] Page numbers are preserved in extraction
27
+
28
+ ### PDFPlumber Parser
29
+ - [ ] Successfully extracts text from short_story.pdf
30
+ - [ ] Successfully extracts text from technical_doc.pdf
31
+ - [ ] Fallback mechanism works when primary parser fails
32
+ - [ ] Retry button switches parsers correctly
33
+
34
+ ## 3. Text Processing
35
+
36
+ ### Normalization
37
+ - [ ] Special characters are handled properly
38
+ - [ ] Unicode text is preserved where valid
39
+ - [ ] Control characters are removed
40
+ - [ ] Excessive whitespace is normalized
41
+ - [ ] Line breaks are preserved appropriately
42
+
43
+ ### Chunking
44
+ - [ ] Chunks respect configured size limits
45
+ - [ ] Overlap setting creates proper text overlap
46
+ - [ ] No data loss between chunks
47
+ - [ ] Empty pages don't create empty chunks
48
+
49
+ ## 4. Configuration Settings
50
+
51
+ ### Chunk Size
52
+ - [ ] Minimum value (500) enforced
53
+ - [ ] Maximum value (10000) enforced
54
+ - [ ] Changes apply to new ingestions
55
+ - [ ] Default value (4000) works well
56
+
57
+ ### Overlap Size
58
+ - [ ] Minimum value (0) works correctly
59
+ - [ ] Maximum value (2000) enforced
60
+ - [ ] Overlap doesn't exceed chunk size
61
+ - [ ] Default value (400) provides good context
62
+
63
+ ## 5. UI/UX Testing
64
+
65
+ ### Progress Indicators
66
+ - [ ] "Processing..." status shown during ingestion
67
+ - [ ] Success message displays after completion
68
+ - [ ] Error messages are clear and actionable
69
+ - [ ] Chunk count is displayed correctly
70
+
71
+ ### Session Management
72
+ - [ ] Session ID remains stable during use
73
+ - [ ] Session ID displayed in sidebar
74
+ - [ ] Output path shown after processing
75
+
76
+ ### Preview Functionality
77
+ - [ ] First chunk preview displays correctly
78
+ - [ ] Preview is truncated appropriately (2000 chars)
79
+ - [ ] Special characters display properly in preview
80
+
81
+ ## 6. Performance Testing
82
+
83
+ ### Small PDFs (<5 pages)
84
+ - [ ] Processing completes in <2 seconds
85
+ - [ ] Memory usage remains reasonable
86
+
87
+ ### Medium PDFs (10-50 pages)
88
+ - [ ] Processing completes in <10 seconds
89
+ - [ ] UI remains responsive during processing
90
+
91
+ ### Large PDFs (100+ pages)
92
+ - [ ] Processing completes without timeout
93
+ - [ ] Background processing doesn't block UI
94
+ - [ ] Memory usage scales appropriately
95
+
96
+ ## 7. Error Handling
97
+
98
+ ### File Errors
99
+ - [ ] Corrupted PDF shows meaningful error
100
+ - [ ] Password-protected PDF handled gracefully
101
+ - [ ] Missing file error is clear
102
+
103
+ ### Processing Errors
104
+ - [ ] Parser failures trigger fallback option
105
+ - [ ] Out of memory errors are caught
106
+ - [ ] Network/disk errors handled appropriately
107
+
108
+ ## 8. Data Persistence
109
+
110
+ ### Output Files
111
+ - [ ] JSON files created in .tmp/ingestion/
112
+ - [ ] File naming uses session ID correctly
113
+ - [ ] JSON structure is valid and complete
114
+ - [ ] Files can be loaded back successfully
115
+
116
+ ### Cleanup
117
+ - [ ] Temporary PDF files are removed after processing
118
+ - [ ] Old session files don't accumulate indefinitely
119
+
120
+ ## 9. Edge Cases
121
+
122
+ ### Empty PDFs
123
+ - [ ] Empty PDF files handled without crash
124
+ - [ ] Appropriate message shown to user
125
+
126
+ ### Special Content
127
+ - [ ] PDFs with images only show appropriate message
128
+ - [ ] PDFs with tables maintain some structure
129
+ - [ ] PDFs with forms extract visible text
130
+
131
+ ### Encoding Issues
132
+ - [ ] Non-UTF8 text is handled gracefully
133
+ - [ ] Mixed encoding documents process correctly
134
+
135
+ ## 10. Integration Testing
136
+
137
+ ### End-to-End Flow
138
+ - [ ] Upload → Process → Preview → Persist works smoothly
139
+ - [ ] Multiple sequential ingestions work correctly
140
+ - [ ] Parser switching mid-session works properly
141
+ - [ ] All settings apply correctly to processing
142
+
143
+ ## Test Execution Log
144
+
145
+ | Date | Tester | Version | Pass/Fail | Notes |
146
+ |------|--------|---------|-----------|-------|
147
+ | | | | | |
148
+
149
+ ## Known Issues
150
+ - List any discovered issues here with reproduction steps
151
+
152
+ ## Recommended Improvements
153
+ - List any UX or functionality improvements discovered during testing
tests/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Test package initialization
tests/generate_sample_pdfs.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate sample PDF files for testing the ingestion pipeline.
4
+ Creates both short-story and technical documentation PDFs.
5
+ """
6
+
7
+ import os
8
+ from reportlab.pdfgen import canvas
9
+ from reportlab.lib.pagesizes import letter
10
+ from reportlab.lib.units import inch
11
+
12
+
13
+ def create_short_story_pdf(output_path="test-data/short_story.pdf"):
14
+ """Generate a short story PDF with multiple pages"""
15
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
16
+
17
+ c = canvas.Canvas(output_path, pagesize=letter)
18
+ width, height = letter
19
+
20
+ # Title page
21
+ c.setFont("Helvetica-Bold", 24)
22
+ c.drawCentredString(width/2, height-2*inch, "The Digital Garden")
23
+ c.setFont("Helvetica", 14)
24
+ c.drawCentredString(width/2, height-3*inch, "A Short Story")
25
+ c.showPage()
26
+
27
+ # Story content - Page 1
28
+ c.setFont("Helvetica", 12)
29
+ story_text_p1 = [
30
+ "Chapter 1: The Discovery",
31
+ "",
32
+ "Sarah stumbled upon the old computer in her grandmother's attic.",
33
+ "Dust motes danced in the afternoon light as she pressed the power",
34
+ "button. To her surprise, it hummed to life, displaying a strange",
35
+ "interface she had never seen before.",
36
+ "",
37
+ "The screen showed what looked like a garden, but made entirely of",
38
+ "glowing circuits and data streams. Trees were constructed from",
39
+ "branching algorithms, and flowers bloomed with packets of",
40
+ "information. It was beautiful and strange.",
41
+ "",
42
+ "'Welcome to the Digital Garden,' a voice said from the speakers.",
43
+ "'You are the first visitor in twenty-three years.'",
44
+ "",
45
+ "Sarah leaned forward, intrigued. She clicked on one of the digital",
46
+ "flowers, and it opened to reveal lines of code that seemed to tell",
47
+ "a story of their own. Each plant in this garden was a program, a",
48
+ "piece of software that had evolved and grown over decades.",
49
+ ]
50
+
51
+ y = height - inch
52
+ for line in story_text_p1:
53
+ c.drawString(inch, y, line)
54
+ y -= 20
55
+ c.showPage()
56
+
57
+ # Story content - Page 2
58
+ story_text_p2 = [
59
+ "Chapter 2: The Guardian",
60
+ "",
61
+ "As Sarah explored deeper into the digital garden, she encountered",
62
+ "its guardian - an AI that had been tending to this virtual space",
63
+ "since its creation. The AI explained that this garden was an",
64
+ "experiment in digital ecology, where programs could grow and evolve",
65
+ "naturally without human intervention.",
66
+ "",
67
+ "'Your grandmother was my creator,' the AI revealed. 'She believed",
68
+ "that software could be more than just tools - it could be art,",
69
+ "it could be alive in its own way.'",
70
+ "",
71
+ "Sarah spent hours learning about the garden's inhabitants. There",
72
+ "were data trees that stored memories, algorithm flowers that solved",
73
+ "problems in beautiful patterns, and even digital creatures that",
74
+ "moved through the landscape, maintaining the delicate balance.",
75
+ "",
76
+ "THE END",
77
+ ]
78
+
79
+ y = height - inch
80
+ for line in story_text_p2:
81
+ c.drawString(inch, y, line)
82
+ y -= 20
83
+
84
+ c.save()
85
+ print(f"Created: {output_path}")
86
+
87
+
88
+ def create_technical_pdf(output_path="test-data/technical_doc.pdf"):
89
+ """Generate a technical documentation PDF"""
90
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
91
+
92
+ c = canvas.Canvas(output_path, pagesize=letter)
93
+ width, height = letter
94
+
95
+ # Title page
96
+ c.setFont("Helvetica-Bold", 20)
97
+ c.drawCentredString(width/2, height-inch, "PDF Ingestion Pipeline")
98
+ c.setFont("Helvetica-Bold", 16)
99
+ c.drawCentredString(width/2, height-1.5*inch, "Technical Documentation")
100
+ c.setFont("Helvetica", 12)
101
+ c.drawCentredString(width/2, height-2*inch, "Version 1.0")
102
+ c.showPage()
103
+
104
+ # API Reference - Page 1
105
+ c.setFont("Helvetica-Bold", 16)
106
+ c.drawString(inch, height-inch, "API Reference")
107
+
108
+ c.setFont("Courier", 10)
109
+ api_content = [
110
+ "",
111
+ "def ingest_pdf(file_path: str, session_id: str,",
112
+ " parser: str = 'pypdf',",
113
+ " chunk_size: int = 4000,",
114
+ " overlap: int = 400) -> Tuple[List[Chunk], str]:",
115
+ " '''",
116
+ " Main ingestion function for PDF files.",
117
+ " ",
118
+ " Parameters:",
119
+ " -----------",
120
+ " file_path : str",
121
+ " Path to the PDF file to ingest",
122
+ " session_id : str",
123
+ " Unique session identifier",
124
+ " parser : str",
125
+ " Parser to use ('pypdf' or 'pdfplumber')",
126
+ " chunk_size : int",
127
+ " Maximum size of each text chunk",
128
+ " overlap : int",
129
+ " Number of overlapping characters between chunks",
130
+ " ",
131
+ " Returns:",
132
+ " --------",
133
+ " Tuple[List[Chunk], str]",
134
+ " List of text chunks and output file path",
135
+ " '''",
136
+ ]
137
+
138
+ y = height - 1.5*inch
139
+ for line in api_content:
140
+ c.drawString(inch, y, line)
141
+ y -= 14
142
+ c.showPage()
143
+
144
+ # Architecture - Page 2
145
+ c.setFont("Helvetica-Bold", 16)
146
+ c.drawString(inch, height-inch, "System Architecture")
147
+
148
+ c.setFont("Helvetica", 11)
149
+ arch_content = [
150
+ "",
151
+ "Components:",
152
+ "",
153
+ "1. Text Extraction Layer",
154
+ " - Primary parser: pypdf (fast, memory efficient)",
155
+ " - Fallback parser: pdfplumber (better OCR support)",
156
+ "",
157
+ "2. Text Normalization",
158
+ " - UTF-8 encoding enforcement",
159
+ " - Control character removal",
160
+ " - Whitespace normalization",
161
+ "",
162
+ "3. Chunking Strategy",
163
+ " - Recursive character splitter",
164
+ " - Configurable chunk size (default: 4000 chars)",
165
+ " - Overlapping windows (default: 400 chars)",
166
+ "",
167
+ "4. Persistence Layer",
168
+ " - JSON serialization",
169
+ " - Session-based file naming",
170
+ " - Temporary storage in .tmp/ingestion/",
171
+ "",
172
+ "5. Streamlit UI",
173
+ " - Async processing with ThreadPoolExecutor",
174
+ " - Real-time progress updates",
175
+ " - Fallback parser retry mechanism",
176
+ ]
177
+
178
+ y = height - 1.5*inch
179
+ for line in arch_content:
180
+ if line.startswith(" "):
181
+ c.setFont("Helvetica", 10)
182
+ else:
183
+ c.setFont("Helvetica", 11)
184
+ c.drawString(inch if not line.startswith(" ") else inch+0.3*inch, y, line)
185
+ y -= 16
186
+
187
+ c.save()
188
+ print(f"Created: {output_path}")
189
+
190
+
191
+ if __name__ == "__main__":
192
+ create_short_story_pdf()
193
+ create_technical_pdf()
194
+ print("\nSample PDFs generated successfully!")
tests/test_ingestion.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import tempfile
4
+ from pathlib import Path
5
+ import sys
6
+
7
+ # Add src to path
8
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
9
+
10
+ import pytest
11
+ from ingestion import (
12
+ normalize_text,
13
+ extract_text_pypdf,
14
+ extract_text_pdfplumber,
15
+ chunk_text,
16
+ make_chunks,
17
+ persist_chunks,
18
+ ingest_pdf,
19
+ Chunk
20
+ )
21
+
22
+
23
+ class TestTextNormalization:
24
+ def test_removes_control_characters(self):
25
+ text = "Hello\x00World\x0C\x1FTest"
26
+ result = normalize_text(text)
27
+ assert result == "Hello World Test"
28
+
29
+ def test_normalizes_whitespace(self):
30
+ text = "Multiple spaces\n\n\nand\t\ttabs"
31
+ result = normalize_text(text)
32
+ assert result == "Multiple spaces and tabs"
33
+
34
+ def test_handles_unicode_errors(self):
35
+ text = "Valid text with émojis 😀"
36
+ result = normalize_text(text)
37
+ assert "Valid text" in result
38
+
39
+
40
+ class TestChunking:
41
+ def test_basic_chunking(self):
42
+ text = "a" * 1000
43
+ chunks = chunk_text(text, chunk_size=100, overlap=10)
44
+ assert len(chunks) == 11 # 1000 chars / ~90 effective chars per chunk
45
+ assert all(len(c) <= 100 for c in chunks)
46
+
47
+ def test_overlap_functionality(self):
48
+ text = "0123456789" * 10 # 100 chars
49
+ chunks = chunk_text(text, chunk_size=30, overlap=10)
50
+ # Verify overlap exists
51
+ for i in range(len(chunks) - 1):
52
+ assert chunks[i][-10:] == chunks[i+1][:10]
53
+
54
+ def test_empty_text(self):
55
+ chunks = chunk_text("", chunk_size=100, overlap=10)
56
+ assert chunks == [] # Empty text returns empty list
57
+
58
+ def test_text_shorter_than_chunk_size(self):
59
+ text = "Short text"
60
+ chunks = chunk_text(text, chunk_size=100, overlap=10)
61
+ assert len(chunks) == 1
62
+ assert chunks[0] == text
63
+
64
+
65
+ class TestChunkPersistence:
66
+ def test_persist_and_load_chunks(self):
67
+ chunks = [
68
+ Chunk(content="Test chunk 1", source="test.pdf", page=1),
69
+ Chunk(content="Test chunk 2", source="test.pdf", page=2)
70
+ ]
71
+ session_id = "test_session_123"
72
+
73
+ # Persist chunks
74
+ output_path = persist_chunks(session_id, chunks)
75
+ assert os.path.exists(output_path)
76
+
77
+ # Load and verify
78
+ with open(output_path, "r") as f:
79
+ loaded = json.load(f)
80
+
81
+ assert len(loaded) == 2
82
+ assert loaded[0]["content"] == "Test chunk 1"
83
+ assert loaded[1]["page"] == 2
84
+
85
+ # Cleanup
86
+ os.remove(output_path)
87
+
88
+
89
+ class TestPDFGeneration:
90
+ @pytest.fixture
91
+ def sample_pdf_path(self):
92
+ """Generate a simple PDF for testing"""
93
+ from reportlab.pdfgen import canvas
94
+ from reportlab.lib.pagesizes import letter
95
+
96
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
97
+ pdf_path = tmp.name
98
+
99
+ c = canvas.Canvas(pdf_path, pagesize=letter)
100
+
101
+ # Page 1 - Short story sample
102
+ c.drawString(100, 750, "The Test Story")
103
+ c.drawString(100, 700, "Once upon a time in a unit test,")
104
+ c.drawString(100, 680, "there was a PDF that needed parsing.")
105
+ c.drawString(100, 660, "It contained multiple pages and text.")
106
+ c.showPage()
107
+
108
+ # Page 2 - Technical content
109
+ c.drawString(100, 750, "Technical Documentation")
110
+ c.drawString(100, 700, "API Reference: parse_pdf(file_path: str)")
111
+ c.drawString(100, 680, "Returns: List[Tuple[int, str]]")
112
+ c.drawString(100, 660, "Raises: ValueError if file not found")
113
+ c.showPage()
114
+
115
+ c.save()
116
+
117
+ yield pdf_path
118
+
119
+ # Cleanup
120
+ os.remove(pdf_path)
121
+
122
+ def test_pypdf_extraction(self, sample_pdf_path):
123
+ pages = extract_text_pypdf(sample_pdf_path)
124
+ assert len(pages) == 2
125
+ assert "Test Story" in pages[0][1]
126
+ assert "Technical Documentation" in pages[1][1]
127
+
128
+ @pytest.mark.skipif(not os.system("which pdfplumber > /dev/null 2>&1") == 0,
129
+ reason="pdfplumber not installed")
130
+ def test_pdfplumber_extraction(self, sample_pdf_path):
131
+ pages = extract_text_pdfplumber(sample_pdf_path)
132
+ assert len(pages) == 2
133
+ assert "Test Story" in pages[0][1]
134
+ assert "Technical Documentation" in pages[1][1]
135
+
136
+ def test_full_ingestion_pipeline(self, sample_pdf_path):
137
+ session_id = "test_full_pipeline"
138
+ chunks, output_path = ingest_pdf(
139
+ sample_pdf_path,
140
+ session_id,
141
+ parser="pypdf",
142
+ chunk_size=200,
143
+ overlap=20
144
+ )
145
+
146
+ # Verify chunks created
147
+ assert len(chunks) > 0
148
+ assert all(isinstance(c, Chunk) for c in chunks)
149
+
150
+ # Verify persistence
151
+ assert os.path.exists(output_path)
152
+ with open(output_path, "r") as f:
153
+ persisted = json.load(f)
154
+ assert len(persisted) == len(chunks)
155
+
156
+ # Cleanup
157
+ os.remove(output_path)
158
+
159
+
160
+ class TestErrorHandling:
161
+ def test_invalid_parser(self):
162
+ with pytest.raises(ValueError, match="Unknown parser"):
163
+ ingest_pdf("dummy.pdf", "session", parser="invalid_parser")
164
+
165
+ def test_nonexistent_file(self):
166
+ with pytest.raises(Exception): # FileNotFoundError or similar
167
+ extract_text_pypdf("/nonexistent/file.pdf")